##// END OF EJS Templates
errors: raise InputError when given non-existent paths etc...
Martin von Zweigbergk -
r46450:96ca817e default
parent child Browse files
Show More
@@ -1,3913 +1,3913 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 short,
19 short,
20 )
20 )
21 from .pycompat import (
21 from .pycompat import (
22 getattr,
22 getattr,
23 open,
23 open,
24 setattr,
24 setattr,
25 )
25 )
26 from .thirdparty import attr
26 from .thirdparty import attr
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 mergestate as mergestatemod,
40 mergestate as mergestatemod,
41 mergeutil,
41 mergeutil,
42 obsolete,
42 obsolete,
43 patch,
43 patch,
44 pathutil,
44 pathutil,
45 phases,
45 phases,
46 pycompat,
46 pycompat,
47 repair,
47 repair,
48 revlog,
48 revlog,
49 rewriteutil,
49 rewriteutil,
50 scmutil,
50 scmutil,
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 pycompat.TYPE_CHECKING:
64 if pycompat.TYPE_CHECKING:
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 (
172 (
173 b'g',
173 b'g',
174 b'git',
174 b'git',
175 None,
175 None,
176 _(b'use git extended diff format (DEFAULT: diff.git)'),
176 _(b'use git extended diff format (DEFAULT: diff.git)'),
177 ),
177 ),
178 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
178 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
179 (b'', b'nodates', None, _(b'omit dates from diff headers')),
179 (b'', b'nodates', None, _(b'omit dates from diff headers')),
180 ]
180 ]
181
181
182 diffwsopts = [
182 diffwsopts = [
183 (
183 (
184 b'w',
184 b'w',
185 b'ignore-all-space',
185 b'ignore-all-space',
186 None,
186 None,
187 _(b'ignore white space when comparing lines'),
187 _(b'ignore white space when comparing lines'),
188 ),
188 ),
189 (
189 (
190 b'b',
190 b'b',
191 b'ignore-space-change',
191 b'ignore-space-change',
192 None,
192 None,
193 _(b'ignore changes in the amount of white space'),
193 _(b'ignore changes in the amount of white space'),
194 ),
194 ),
195 (
195 (
196 b'B',
196 b'B',
197 b'ignore-blank-lines',
197 b'ignore-blank-lines',
198 None,
198 None,
199 _(b'ignore changes whose lines are all blank'),
199 _(b'ignore changes whose lines are all blank'),
200 ),
200 ),
201 (
201 (
202 b'Z',
202 b'Z',
203 b'ignore-space-at-eol',
203 b'ignore-space-at-eol',
204 None,
204 None,
205 _(b'ignore changes in whitespace at EOL'),
205 _(b'ignore changes in whitespace at EOL'),
206 ),
206 ),
207 ]
207 ]
208
208
209 diffopts2 = (
209 diffopts2 = (
210 [
210 [
211 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
211 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
212 (
212 (
213 b'p',
213 b'p',
214 b'show-function',
214 b'show-function',
215 None,
215 None,
216 _(
216 _(
217 b'show which function each change is in (DEFAULT: diff.showfunc)'
217 b'show which function each change is in (DEFAULT: diff.showfunc)'
218 ),
218 ),
219 ),
219 ),
220 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
220 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
221 ]
221 ]
222 + diffwsopts
222 + diffwsopts
223 + [
223 + [
224 (
224 (
225 b'U',
225 b'U',
226 b'unified',
226 b'unified',
227 b'',
227 b'',
228 _(b'number of lines of context to show'),
228 _(b'number of lines of context to show'),
229 _(b'NUM'),
229 _(b'NUM'),
230 ),
230 ),
231 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
231 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
232 (
232 (
233 b'',
233 b'',
234 b'root',
234 b'root',
235 b'',
235 b'',
236 _(b'produce diffs relative to subdirectory'),
236 _(b'produce diffs relative to subdirectory'),
237 _(b'DIR'),
237 _(b'DIR'),
238 ),
238 ),
239 ]
239 ]
240 )
240 )
241
241
242 mergetoolopts = [
242 mergetoolopts = [
243 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
243 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
244 ]
244 ]
245
245
246 similarityopts = [
246 similarityopts = [
247 (
247 (
248 b's',
248 b's',
249 b'similarity',
249 b'similarity',
250 b'',
250 b'',
251 _(b'guess renamed files by similarity (0<=s<=100)'),
251 _(b'guess renamed files by similarity (0<=s<=100)'),
252 _(b'SIMILARITY'),
252 _(b'SIMILARITY'),
253 )
253 )
254 ]
254 ]
255
255
256 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
256 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
257
257
258 debugrevlogopts = [
258 debugrevlogopts = [
259 (b'c', b'changelog', False, _(b'open changelog')),
259 (b'c', b'changelog', False, _(b'open changelog')),
260 (b'm', b'manifest', False, _(b'open manifest')),
260 (b'm', b'manifest', False, _(b'open manifest')),
261 (b'', b'dir', b'', _(b'open directory manifest')),
261 (b'', b'dir', b'', _(b'open directory manifest')),
262 ]
262 ]
263
263
264 # special string such that everything below this line will be ingored in the
264 # special string such that everything below this line will be ingored in the
265 # editor text
265 # editor text
266 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
266 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
267
267
268
268
269 def check_at_most_one_arg(opts, *args):
269 def check_at_most_one_arg(opts, *args):
270 """abort if more than one of the arguments are in opts
270 """abort if more than one of the arguments are in opts
271
271
272 Returns the unique argument or None if none of them were specified.
272 Returns the unique argument or None if none of them were specified.
273 """
273 """
274
274
275 def to_display(name):
275 def to_display(name):
276 return pycompat.sysbytes(name).replace(b'_', b'-')
276 return pycompat.sysbytes(name).replace(b'_', b'-')
277
277
278 previous = None
278 previous = None
279 for x in args:
279 for x in args:
280 if opts.get(x):
280 if opts.get(x):
281 if previous:
281 if previous:
282 raise error.InputError(
282 raise error.InputError(
283 _(b'cannot specify both --%s and --%s')
283 _(b'cannot specify both --%s and --%s')
284 % (to_display(previous), to_display(x))
284 % (to_display(previous), to_display(x))
285 )
285 )
286 previous = x
286 previous = x
287 return previous
287 return previous
288
288
289
289
290 def check_incompatible_arguments(opts, first, others):
290 def check_incompatible_arguments(opts, first, others):
291 """abort if the first argument is given along with any of the others
291 """abort if the first argument is given along with any of the others
292
292
293 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
293 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
294 among themselves, and they're passed as a single collection.
294 among themselves, and they're passed as a single collection.
295 """
295 """
296 for other in others:
296 for other in others:
297 check_at_most_one_arg(opts, first, other)
297 check_at_most_one_arg(opts, first, other)
298
298
299
299
300 def resolvecommitoptions(ui, opts):
300 def resolvecommitoptions(ui, opts):
301 """modify commit options dict to handle related options
301 """modify commit options dict to handle related options
302
302
303 The return value indicates that ``rewrite.update-timestamp`` is the reason
303 The return value indicates that ``rewrite.update-timestamp`` is the reason
304 the ``date`` option is set.
304 the ``date`` option is set.
305 """
305 """
306 check_at_most_one_arg(opts, b'date', b'currentdate')
306 check_at_most_one_arg(opts, b'date', b'currentdate')
307 check_at_most_one_arg(opts, b'user', b'currentuser')
307 check_at_most_one_arg(opts, b'user', b'currentuser')
308
308
309 datemaydiffer = False # date-only change should be ignored?
309 datemaydiffer = False # date-only change should be ignored?
310
310
311 if opts.get(b'currentdate'):
311 if opts.get(b'currentdate'):
312 opts[b'date'] = b'%d %d' % dateutil.makedate()
312 opts[b'date'] = b'%d %d' % dateutil.makedate()
313 elif (
313 elif (
314 not opts.get(b'date')
314 not opts.get(b'date')
315 and ui.configbool(b'rewrite', b'update-timestamp')
315 and ui.configbool(b'rewrite', b'update-timestamp')
316 and opts.get(b'currentdate') is None
316 and opts.get(b'currentdate') is None
317 ):
317 ):
318 opts[b'date'] = b'%d %d' % dateutil.makedate()
318 opts[b'date'] = b'%d %d' % dateutil.makedate()
319 datemaydiffer = True
319 datemaydiffer = True
320
320
321 if opts.get(b'currentuser'):
321 if opts.get(b'currentuser'):
322 opts[b'user'] = ui.username()
322 opts[b'user'] = ui.username()
323
323
324 return datemaydiffer
324 return datemaydiffer
325
325
326
326
327 def checknotesize(ui, opts):
327 def checknotesize(ui, opts):
328 """ make sure note is of valid format """
328 """ make sure note is of valid format """
329
329
330 note = opts.get(b'note')
330 note = opts.get(b'note')
331 if not note:
331 if not note:
332 return
332 return
333
333
334 if len(note) > 255:
334 if len(note) > 255:
335 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
335 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
336 if b'\n' in note:
336 if b'\n' in note:
337 raise error.InputError(_(b"note cannot contain a newline"))
337 raise error.InputError(_(b"note cannot contain a newline"))
338
338
339
339
340 def ishunk(x):
340 def ishunk(x):
341 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
341 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
342 return isinstance(x, hunkclasses)
342 return isinstance(x, hunkclasses)
343
343
344
344
345 def newandmodified(chunks, originalchunks):
345 def newandmodified(chunks, originalchunks):
346 newlyaddedandmodifiedfiles = set()
346 newlyaddedandmodifiedfiles = set()
347 alsorestore = set()
347 alsorestore = set()
348 for chunk in chunks:
348 for chunk in chunks:
349 if (
349 if (
350 ishunk(chunk)
350 ishunk(chunk)
351 and chunk.header.isnewfile()
351 and chunk.header.isnewfile()
352 and chunk not in originalchunks
352 and chunk not in originalchunks
353 ):
353 ):
354 newlyaddedandmodifiedfiles.add(chunk.header.filename())
354 newlyaddedandmodifiedfiles.add(chunk.header.filename())
355 alsorestore.update(
355 alsorestore.update(
356 set(chunk.header.files()) - {chunk.header.filename()}
356 set(chunk.header.files()) - {chunk.header.filename()}
357 )
357 )
358 return newlyaddedandmodifiedfiles, alsorestore
358 return newlyaddedandmodifiedfiles, alsorestore
359
359
360
360
361 def parsealiases(cmd):
361 def parsealiases(cmd):
362 return cmd.split(b"|")
362 return cmd.split(b"|")
363
363
364
364
365 def setupwrapcolorwrite(ui):
365 def setupwrapcolorwrite(ui):
366 # wrap ui.write so diff output can be labeled/colorized
366 # wrap ui.write so diff output can be labeled/colorized
367 def wrapwrite(orig, *args, **kw):
367 def wrapwrite(orig, *args, **kw):
368 label = kw.pop('label', b'')
368 label = kw.pop('label', b'')
369 for chunk, l in patch.difflabel(lambda: args):
369 for chunk, l in patch.difflabel(lambda: args):
370 orig(chunk, label=label + l)
370 orig(chunk, label=label + l)
371
371
372 oldwrite = ui.write
372 oldwrite = ui.write
373
373
374 def wrap(*args, **kwargs):
374 def wrap(*args, **kwargs):
375 return wrapwrite(oldwrite, *args, **kwargs)
375 return wrapwrite(oldwrite, *args, **kwargs)
376
376
377 setattr(ui, 'write', wrap)
377 setattr(ui, 'write', wrap)
378 return oldwrite
378 return oldwrite
379
379
380
380
381 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
381 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
382 try:
382 try:
383 if usecurses:
383 if usecurses:
384 if testfile:
384 if testfile:
385 recordfn = crecordmod.testdecorator(
385 recordfn = crecordmod.testdecorator(
386 testfile, crecordmod.testchunkselector
386 testfile, crecordmod.testchunkselector
387 )
387 )
388 else:
388 else:
389 recordfn = crecordmod.chunkselector
389 recordfn = crecordmod.chunkselector
390
390
391 return crecordmod.filterpatch(
391 return crecordmod.filterpatch(
392 ui, originalhunks, recordfn, operation
392 ui, originalhunks, recordfn, operation
393 )
393 )
394 except crecordmod.fallbackerror as e:
394 except crecordmod.fallbackerror as e:
395 ui.warn(b'%s\n' % e)
395 ui.warn(b'%s\n' % e)
396 ui.warn(_(b'falling back to text mode\n'))
396 ui.warn(_(b'falling back to text mode\n'))
397
397
398 return patch.filterpatch(ui, originalhunks, match, operation)
398 return patch.filterpatch(ui, originalhunks, match, operation)
399
399
400
400
401 def recordfilter(ui, originalhunks, match, operation=None):
401 def recordfilter(ui, originalhunks, match, operation=None):
402 """ Prompts the user to filter the originalhunks and return a list of
402 """ Prompts the user to filter the originalhunks and return a list of
403 selected hunks.
403 selected hunks.
404 *operation* is used for to build ui messages to indicate the user what
404 *operation* is used for to build ui messages to indicate the user what
405 kind of filtering they are doing: reverting, committing, shelving, etc.
405 kind of filtering they are doing: reverting, committing, shelving, etc.
406 (see patch.filterpatch).
406 (see patch.filterpatch).
407 """
407 """
408 usecurses = crecordmod.checkcurses(ui)
408 usecurses = crecordmod.checkcurses(ui)
409 testfile = ui.config(b'experimental', b'crecordtest')
409 testfile = ui.config(b'experimental', b'crecordtest')
410 oldwrite = setupwrapcolorwrite(ui)
410 oldwrite = setupwrapcolorwrite(ui)
411 try:
411 try:
412 newchunks, newopts = filterchunks(
412 newchunks, newopts = filterchunks(
413 ui, originalhunks, usecurses, testfile, match, operation
413 ui, originalhunks, usecurses, testfile, match, operation
414 )
414 )
415 finally:
415 finally:
416 ui.write = oldwrite
416 ui.write = oldwrite
417 return newchunks, newopts
417 return newchunks, newopts
418
418
419
419
420 def dorecord(
420 def dorecord(
421 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
421 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
422 ):
422 ):
423 opts = pycompat.byteskwargs(opts)
423 opts = pycompat.byteskwargs(opts)
424 if not ui.interactive():
424 if not ui.interactive():
425 if cmdsuggest:
425 if cmdsuggest:
426 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
426 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
427 else:
427 else:
428 msg = _(b'running non-interactively')
428 msg = _(b'running non-interactively')
429 raise error.InputError(msg)
429 raise error.InputError(msg)
430
430
431 # make sure username is set before going interactive
431 # make sure username is set before going interactive
432 if not opts.get(b'user'):
432 if not opts.get(b'user'):
433 ui.username() # raise exception, username not provided
433 ui.username() # raise exception, username not provided
434
434
435 def recordfunc(ui, repo, message, match, opts):
435 def recordfunc(ui, repo, message, match, opts):
436 """This is generic record driver.
436 """This is generic record driver.
437
437
438 Its job is to interactively filter local changes, and
438 Its job is to interactively filter local changes, and
439 accordingly prepare working directory into a state in which the
439 accordingly prepare working directory into a state in which the
440 job can be delegated to a non-interactive commit command such as
440 job can be delegated to a non-interactive commit command such as
441 'commit' or 'qrefresh'.
441 'commit' or 'qrefresh'.
442
442
443 After the actual job is done by non-interactive command, the
443 After the actual job is done by non-interactive command, the
444 working directory is restored to its original state.
444 working directory is restored to its original state.
445
445
446 In the end we'll record interesting changes, and everything else
446 In the end we'll record interesting changes, and everything else
447 will be left in place, so the user can continue working.
447 will be left in place, so the user can continue working.
448 """
448 """
449 if not opts.get(b'interactive-unshelve'):
449 if not opts.get(b'interactive-unshelve'):
450 checkunfinished(repo, commit=True)
450 checkunfinished(repo, commit=True)
451 wctx = repo[None]
451 wctx = repo[None]
452 merge = len(wctx.parents()) > 1
452 merge = len(wctx.parents()) > 1
453 if merge:
453 if merge:
454 raise error.InputError(
454 raise error.InputError(
455 _(
455 _(
456 b'cannot partially commit a merge '
456 b'cannot partially commit a merge '
457 b'(use "hg commit" instead)'
457 b'(use "hg commit" instead)'
458 )
458 )
459 )
459 )
460
460
461 def fail(f, msg):
461 def fail(f, msg):
462 raise error.Abort(b'%s: %s' % (f, msg))
462 raise error.InputError(b'%s: %s' % (f, msg))
463
463
464 force = opts.get(b'force')
464 force = opts.get(b'force')
465 if not force:
465 if not force:
466 match = matchmod.badmatch(match, fail)
466 match = matchmod.badmatch(match, fail)
467
467
468 status = repo.status(match=match)
468 status = repo.status(match=match)
469
469
470 overrides = {(b'ui', b'commitsubrepos'): True}
470 overrides = {(b'ui', b'commitsubrepos'): True}
471
471
472 with repo.ui.configoverride(overrides, b'record'):
472 with repo.ui.configoverride(overrides, b'record'):
473 # subrepoutil.precommit() modifies the status
473 # subrepoutil.precommit() modifies the status
474 tmpstatus = scmutil.status(
474 tmpstatus = scmutil.status(
475 copymod.copy(status.modified),
475 copymod.copy(status.modified),
476 copymod.copy(status.added),
476 copymod.copy(status.added),
477 copymod.copy(status.removed),
477 copymod.copy(status.removed),
478 copymod.copy(status.deleted),
478 copymod.copy(status.deleted),
479 copymod.copy(status.unknown),
479 copymod.copy(status.unknown),
480 copymod.copy(status.ignored),
480 copymod.copy(status.ignored),
481 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
481 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
482 )
482 )
483
483
484 # Force allows -X subrepo to skip the subrepo.
484 # Force allows -X subrepo to skip the subrepo.
485 subs, commitsubs, newstate = subrepoutil.precommit(
485 subs, commitsubs, newstate = subrepoutil.precommit(
486 repo.ui, wctx, tmpstatus, match, force=True
486 repo.ui, wctx, tmpstatus, match, force=True
487 )
487 )
488 for s in subs:
488 for s in subs:
489 if s in commitsubs:
489 if s in commitsubs:
490 dirtyreason = wctx.sub(s).dirtyreason(True)
490 dirtyreason = wctx.sub(s).dirtyreason(True)
491 raise error.Abort(dirtyreason)
491 raise error.Abort(dirtyreason)
492
492
493 if not force:
493 if not force:
494 repo.checkcommitpatterns(wctx, match, status, fail)
494 repo.checkcommitpatterns(wctx, match, status, fail)
495 diffopts = patch.difffeatureopts(
495 diffopts = patch.difffeatureopts(
496 ui,
496 ui,
497 opts=opts,
497 opts=opts,
498 whitespace=True,
498 whitespace=True,
499 section=b'commands',
499 section=b'commands',
500 configprefix=b'commit.interactive.',
500 configprefix=b'commit.interactive.',
501 )
501 )
502 diffopts.nodates = True
502 diffopts.nodates = True
503 diffopts.git = True
503 diffopts.git = True
504 diffopts.showfunc = True
504 diffopts.showfunc = True
505 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
505 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
506 originalchunks = patch.parsepatch(originaldiff)
506 originalchunks = patch.parsepatch(originaldiff)
507 match = scmutil.match(repo[None], pats)
507 match = scmutil.match(repo[None], pats)
508
508
509 # 1. filter patch, since we are intending to apply subset of it
509 # 1. filter patch, since we are intending to apply subset of it
510 try:
510 try:
511 chunks, newopts = filterfn(ui, originalchunks, match)
511 chunks, newopts = filterfn(ui, originalchunks, match)
512 except error.PatchError as err:
512 except error.PatchError as err:
513 raise error.InputError(_(b'error parsing patch: %s') % err)
513 raise error.InputError(_(b'error parsing patch: %s') % err)
514 opts.update(newopts)
514 opts.update(newopts)
515
515
516 # We need to keep a backup of files that have been newly added and
516 # We need to keep a backup of files that have been newly added and
517 # modified during the recording process because there is a previous
517 # modified during the recording process because there is a previous
518 # version without the edit in the workdir. We also will need to restore
518 # version without the edit in the workdir. We also will need to restore
519 # files that were the sources of renames so that the patch application
519 # files that were the sources of renames so that the patch application
520 # works.
520 # works.
521 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
521 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
522 chunks, originalchunks
522 chunks, originalchunks
523 )
523 )
524 contenders = set()
524 contenders = set()
525 for h in chunks:
525 for h in chunks:
526 try:
526 try:
527 contenders.update(set(h.files()))
527 contenders.update(set(h.files()))
528 except AttributeError:
528 except AttributeError:
529 pass
529 pass
530
530
531 changed = status.modified + status.added + status.removed
531 changed = status.modified + status.added + status.removed
532 newfiles = [f for f in changed if f in contenders]
532 newfiles = [f for f in changed if f in contenders]
533 if not newfiles:
533 if not newfiles:
534 ui.status(_(b'no changes to record\n'))
534 ui.status(_(b'no changes to record\n'))
535 return 0
535 return 0
536
536
537 modified = set(status.modified)
537 modified = set(status.modified)
538
538
539 # 2. backup changed files, so we can restore them in the end
539 # 2. backup changed files, so we can restore them in the end
540
540
541 if backupall:
541 if backupall:
542 tobackup = changed
542 tobackup = changed
543 else:
543 else:
544 tobackup = [
544 tobackup = [
545 f
545 f
546 for f in newfiles
546 for f in newfiles
547 if f in modified or f in newlyaddedandmodifiedfiles
547 if f in modified or f in newlyaddedandmodifiedfiles
548 ]
548 ]
549 backups = {}
549 backups = {}
550 if tobackup:
550 if tobackup:
551 backupdir = repo.vfs.join(b'record-backups')
551 backupdir = repo.vfs.join(b'record-backups')
552 try:
552 try:
553 os.mkdir(backupdir)
553 os.mkdir(backupdir)
554 except OSError as err:
554 except OSError as err:
555 if err.errno != errno.EEXIST:
555 if err.errno != errno.EEXIST:
556 raise
556 raise
557 try:
557 try:
558 # backup continues
558 # backup continues
559 for f in tobackup:
559 for f in tobackup:
560 fd, tmpname = pycompat.mkstemp(
560 fd, tmpname = pycompat.mkstemp(
561 prefix=os.path.basename(f) + b'.', dir=backupdir
561 prefix=os.path.basename(f) + b'.', dir=backupdir
562 )
562 )
563 os.close(fd)
563 os.close(fd)
564 ui.debug(b'backup %r as %r\n' % (f, tmpname))
564 ui.debug(b'backup %r as %r\n' % (f, tmpname))
565 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
565 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
566 backups[f] = tmpname
566 backups[f] = tmpname
567
567
568 fp = stringio()
568 fp = stringio()
569 for c in chunks:
569 for c in chunks:
570 fname = c.filename()
570 fname = c.filename()
571 if fname in backups:
571 if fname in backups:
572 c.write(fp)
572 c.write(fp)
573 dopatch = fp.tell()
573 dopatch = fp.tell()
574 fp.seek(0)
574 fp.seek(0)
575
575
576 # 2.5 optionally review / modify patch in text editor
576 # 2.5 optionally review / modify patch in text editor
577 if opts.get(b'review', False):
577 if opts.get(b'review', False):
578 patchtext = (
578 patchtext = (
579 crecordmod.diffhelptext
579 crecordmod.diffhelptext
580 + crecordmod.patchhelptext
580 + crecordmod.patchhelptext
581 + fp.read()
581 + fp.read()
582 )
582 )
583 reviewedpatch = ui.edit(
583 reviewedpatch = ui.edit(
584 patchtext, b"", action=b"diff", repopath=repo.path
584 patchtext, b"", action=b"diff", repopath=repo.path
585 )
585 )
586 fp.truncate(0)
586 fp.truncate(0)
587 fp.write(reviewedpatch)
587 fp.write(reviewedpatch)
588 fp.seek(0)
588 fp.seek(0)
589
589
590 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
590 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
591 # 3a. apply filtered patch to clean repo (clean)
591 # 3a. apply filtered patch to clean repo (clean)
592 if backups:
592 if backups:
593 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
593 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
594 mergemod.revert_to(repo[b'.'], matcher=m)
594 mergemod.revert_to(repo[b'.'], matcher=m)
595
595
596 # 3b. (apply)
596 # 3b. (apply)
597 if dopatch:
597 if dopatch:
598 try:
598 try:
599 ui.debug(b'applying patch\n')
599 ui.debug(b'applying patch\n')
600 ui.debug(fp.getvalue())
600 ui.debug(fp.getvalue())
601 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
601 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
602 except error.PatchError as err:
602 except error.PatchError as err:
603 raise error.InputError(pycompat.bytestr(err))
603 raise error.InputError(pycompat.bytestr(err))
604 del fp
604 del fp
605
605
606 # 4. We prepared working directory according to filtered
606 # 4. We prepared working directory according to filtered
607 # patch. Now is the time to delegate the job to
607 # patch. Now is the time to delegate the job to
608 # commit/qrefresh or the like!
608 # commit/qrefresh or the like!
609
609
610 # Make all of the pathnames absolute.
610 # Make all of the pathnames absolute.
611 newfiles = [repo.wjoin(nf) for nf in newfiles]
611 newfiles = [repo.wjoin(nf) for nf in newfiles]
612 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
612 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
613 finally:
613 finally:
614 # 5. finally restore backed-up files
614 # 5. finally restore backed-up files
615 try:
615 try:
616 dirstate = repo.dirstate
616 dirstate = repo.dirstate
617 for realname, tmpname in pycompat.iteritems(backups):
617 for realname, tmpname in pycompat.iteritems(backups):
618 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
618 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
619
619
620 if dirstate[realname] == b'n':
620 if dirstate[realname] == b'n':
621 # without normallookup, restoring timestamp
621 # without normallookup, restoring timestamp
622 # may cause partially committed files
622 # may cause partially committed files
623 # to be treated as unmodified
623 # to be treated as unmodified
624 dirstate.normallookup(realname)
624 dirstate.normallookup(realname)
625
625
626 # copystat=True here and above are a hack to trick any
626 # copystat=True here and above are a hack to trick any
627 # editors that have f open that we haven't modified them.
627 # editors that have f open that we haven't modified them.
628 #
628 #
629 # Also note that this racy as an editor could notice the
629 # Also note that this racy as an editor could notice the
630 # file's mtime before we've finished writing it.
630 # file's mtime before we've finished writing it.
631 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
631 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
632 os.unlink(tmpname)
632 os.unlink(tmpname)
633 if tobackup:
633 if tobackup:
634 os.rmdir(backupdir)
634 os.rmdir(backupdir)
635 except OSError:
635 except OSError:
636 pass
636 pass
637
637
638 def recordinwlock(ui, repo, message, match, opts):
638 def recordinwlock(ui, repo, message, match, opts):
639 with repo.wlock():
639 with repo.wlock():
640 return recordfunc(ui, repo, message, match, opts)
640 return recordfunc(ui, repo, message, match, opts)
641
641
642 return commit(ui, repo, recordinwlock, pats, opts)
642 return commit(ui, repo, recordinwlock, pats, opts)
643
643
644
644
645 class dirnode(object):
645 class dirnode(object):
646 """
646 """
647 Represent a directory in user working copy with information required for
647 Represent a directory in user working copy with information required for
648 the purpose of tersing its status.
648 the purpose of tersing its status.
649
649
650 path is the path to the directory, without a trailing '/'
650 path is the path to the directory, without a trailing '/'
651
651
652 statuses is a set of statuses of all files in this directory (this includes
652 statuses is a set of statuses of all files in this directory (this includes
653 all the files in all the subdirectories too)
653 all the files in all the subdirectories too)
654
654
655 files is a list of files which are direct child of this directory
655 files is a list of files which are direct child of this directory
656
656
657 subdirs is a dictionary of sub-directory name as the key and it's own
657 subdirs is a dictionary of sub-directory name as the key and it's own
658 dirnode object as the value
658 dirnode object as the value
659 """
659 """
660
660
661 def __init__(self, dirpath):
661 def __init__(self, dirpath):
662 self.path = dirpath
662 self.path = dirpath
663 self.statuses = set()
663 self.statuses = set()
664 self.files = []
664 self.files = []
665 self.subdirs = {}
665 self.subdirs = {}
666
666
667 def _addfileindir(self, filename, status):
667 def _addfileindir(self, filename, status):
668 """Add a file in this directory as a direct child."""
668 """Add a file in this directory as a direct child."""
669 self.files.append((filename, status))
669 self.files.append((filename, status))
670
670
671 def addfile(self, filename, status):
671 def addfile(self, filename, status):
672 """
672 """
673 Add a file to this directory or to its direct parent directory.
673 Add a file to this directory or to its direct parent directory.
674
674
675 If the file is not direct child of this directory, we traverse to the
675 If the file is not direct child of this directory, we traverse to the
676 directory of which this file is a direct child of and add the file
676 directory of which this file is a direct child of and add the file
677 there.
677 there.
678 """
678 """
679
679
680 # the filename contains a path separator, it means it's not the direct
680 # the filename contains a path separator, it means it's not the direct
681 # child of this directory
681 # child of this directory
682 if b'/' in filename:
682 if b'/' in filename:
683 subdir, filep = filename.split(b'/', 1)
683 subdir, filep = filename.split(b'/', 1)
684
684
685 # does the dirnode object for subdir exists
685 # does the dirnode object for subdir exists
686 if subdir not in self.subdirs:
686 if subdir not in self.subdirs:
687 subdirpath = pathutil.join(self.path, subdir)
687 subdirpath = pathutil.join(self.path, subdir)
688 self.subdirs[subdir] = dirnode(subdirpath)
688 self.subdirs[subdir] = dirnode(subdirpath)
689
689
690 # try adding the file in subdir
690 # try adding the file in subdir
691 self.subdirs[subdir].addfile(filep, status)
691 self.subdirs[subdir].addfile(filep, status)
692
692
693 else:
693 else:
694 self._addfileindir(filename, status)
694 self._addfileindir(filename, status)
695
695
696 if status not in self.statuses:
696 if status not in self.statuses:
697 self.statuses.add(status)
697 self.statuses.add(status)
698
698
699 def iterfilepaths(self):
699 def iterfilepaths(self):
700 """Yield (status, path) for files directly under this directory."""
700 """Yield (status, path) for files directly under this directory."""
701 for f, st in self.files:
701 for f, st in self.files:
702 yield st, pathutil.join(self.path, f)
702 yield st, pathutil.join(self.path, f)
703
703
704 def tersewalk(self, terseargs):
704 def tersewalk(self, terseargs):
705 """
705 """
706 Yield (status, path) obtained by processing the status of this
706 Yield (status, path) obtained by processing the status of this
707 dirnode.
707 dirnode.
708
708
709 terseargs is the string of arguments passed by the user with `--terse`
709 terseargs is the string of arguments passed by the user with `--terse`
710 flag.
710 flag.
711
711
712 Following are the cases which can happen:
712 Following are the cases which can happen:
713
713
714 1) All the files in the directory (including all the files in its
714 1) All the files in the directory (including all the files in its
715 subdirectories) share the same status and the user has asked us to terse
715 subdirectories) share the same status and the user has asked us to terse
716 that status. -> yield (status, dirpath). dirpath will end in '/'.
716 that status. -> yield (status, dirpath). dirpath will end in '/'.
717
717
718 2) Otherwise, we do following:
718 2) Otherwise, we do following:
719
719
720 a) Yield (status, filepath) for all the files which are in this
720 a) Yield (status, filepath) for all the files which are in this
721 directory (only the ones in this directory, not the subdirs)
721 directory (only the ones in this directory, not the subdirs)
722
722
723 b) Recurse the function on all the subdirectories of this
723 b) Recurse the function on all the subdirectories of this
724 directory
724 directory
725 """
725 """
726
726
727 if len(self.statuses) == 1:
727 if len(self.statuses) == 1:
728 onlyst = self.statuses.pop()
728 onlyst = self.statuses.pop()
729
729
730 # Making sure we terse only when the status abbreviation is
730 # Making sure we terse only when the status abbreviation is
731 # passed as terse argument
731 # passed as terse argument
732 if onlyst in terseargs:
732 if onlyst in terseargs:
733 yield onlyst, self.path + b'/'
733 yield onlyst, self.path + b'/'
734 return
734 return
735
735
736 # add the files to status list
736 # add the files to status list
737 for st, fpath in self.iterfilepaths():
737 for st, fpath in self.iterfilepaths():
738 yield st, fpath
738 yield st, fpath
739
739
740 # recurse on the subdirs
740 # recurse on the subdirs
741 for dirobj in self.subdirs.values():
741 for dirobj in self.subdirs.values():
742 for st, fpath in dirobj.tersewalk(terseargs):
742 for st, fpath in dirobj.tersewalk(terseargs):
743 yield st, fpath
743 yield st, fpath
744
744
745
745
746 def tersedir(statuslist, terseargs):
746 def tersedir(statuslist, terseargs):
747 """
747 """
748 Terse the status if all the files in a directory shares the same status.
748 Terse the status if all the files in a directory shares the same status.
749
749
750 statuslist is scmutil.status() object which contains a list of files for
750 statuslist is scmutil.status() object which contains a list of files for
751 each status.
751 each status.
752 terseargs is string which is passed by the user as the argument to `--terse`
752 terseargs is string which is passed by the user as the argument to `--terse`
753 flag.
753 flag.
754
754
755 The function makes a tree of objects of dirnode class, and at each node it
755 The function makes a tree of objects of dirnode class, and at each node it
756 stores the information required to know whether we can terse a certain
756 stores the information required to know whether we can terse a certain
757 directory or not.
757 directory or not.
758 """
758 """
759 # the order matters here as that is used to produce final list
759 # the order matters here as that is used to produce final list
760 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
760 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
761
761
762 # checking the argument validity
762 # checking the argument validity
763 for s in pycompat.bytestr(terseargs):
763 for s in pycompat.bytestr(terseargs):
764 if s not in allst:
764 if s not in allst:
765 raise error.InputError(_(b"'%s' not recognized") % s)
765 raise error.InputError(_(b"'%s' not recognized") % s)
766
766
767 # creating a dirnode object for the root of the repo
767 # creating a dirnode object for the root of the repo
768 rootobj = dirnode(b'')
768 rootobj = dirnode(b'')
769 pstatus = (
769 pstatus = (
770 b'modified',
770 b'modified',
771 b'added',
771 b'added',
772 b'deleted',
772 b'deleted',
773 b'clean',
773 b'clean',
774 b'unknown',
774 b'unknown',
775 b'ignored',
775 b'ignored',
776 b'removed',
776 b'removed',
777 )
777 )
778
778
779 tersedict = {}
779 tersedict = {}
780 for attrname in pstatus:
780 for attrname in pstatus:
781 statuschar = attrname[0:1]
781 statuschar = attrname[0:1]
782 for f in getattr(statuslist, attrname):
782 for f in getattr(statuslist, attrname):
783 rootobj.addfile(f, statuschar)
783 rootobj.addfile(f, statuschar)
784 tersedict[statuschar] = []
784 tersedict[statuschar] = []
785
785
786 # we won't be tersing the root dir, so add files in it
786 # we won't be tersing the root dir, so add files in it
787 for st, fpath in rootobj.iterfilepaths():
787 for st, fpath in rootobj.iterfilepaths():
788 tersedict[st].append(fpath)
788 tersedict[st].append(fpath)
789
789
790 # process each sub-directory and build tersedict
790 # process each sub-directory and build tersedict
791 for subdir in rootobj.subdirs.values():
791 for subdir in rootobj.subdirs.values():
792 for st, f in subdir.tersewalk(terseargs):
792 for st, f in subdir.tersewalk(terseargs):
793 tersedict[st].append(f)
793 tersedict[st].append(f)
794
794
795 tersedlist = []
795 tersedlist = []
796 for st in allst:
796 for st in allst:
797 tersedict[st].sort()
797 tersedict[st].sort()
798 tersedlist.append(tersedict[st])
798 tersedlist.append(tersedict[st])
799
799
800 return scmutil.status(*tersedlist)
800 return scmutil.status(*tersedlist)
801
801
802
802
803 def _commentlines(raw):
803 def _commentlines(raw):
804 '''Surround lineswith a comment char and a new line'''
804 '''Surround lineswith a comment char and a new line'''
805 lines = raw.splitlines()
805 lines = raw.splitlines()
806 commentedlines = [b'# %s' % line for line in lines]
806 commentedlines = [b'# %s' % line for line in lines]
807 return b'\n'.join(commentedlines) + b'\n'
807 return b'\n'.join(commentedlines) + b'\n'
808
808
809
809
810 @attr.s(frozen=True)
810 @attr.s(frozen=True)
811 class morestatus(object):
811 class morestatus(object):
812 reporoot = attr.ib()
812 reporoot = attr.ib()
813 unfinishedop = attr.ib()
813 unfinishedop = attr.ib()
814 unfinishedmsg = attr.ib()
814 unfinishedmsg = attr.ib()
815 activemerge = attr.ib()
815 activemerge = attr.ib()
816 unresolvedpaths = attr.ib()
816 unresolvedpaths = attr.ib()
817 _formattedpaths = attr.ib(init=False, default=set())
817 _formattedpaths = attr.ib(init=False, default=set())
818 _label = b'status.morestatus'
818 _label = b'status.morestatus'
819
819
820 def formatfile(self, path, fm):
820 def formatfile(self, path, fm):
821 self._formattedpaths.add(path)
821 self._formattedpaths.add(path)
822 if self.activemerge and path in self.unresolvedpaths:
822 if self.activemerge and path in self.unresolvedpaths:
823 fm.data(unresolved=True)
823 fm.data(unresolved=True)
824
824
825 def formatfooter(self, fm):
825 def formatfooter(self, fm):
826 if self.unfinishedop or self.unfinishedmsg:
826 if self.unfinishedop or self.unfinishedmsg:
827 fm.startitem()
827 fm.startitem()
828 fm.data(itemtype=b'morestatus')
828 fm.data(itemtype=b'morestatus')
829
829
830 if self.unfinishedop:
830 if self.unfinishedop:
831 fm.data(unfinished=self.unfinishedop)
831 fm.data(unfinished=self.unfinishedop)
832 statemsg = (
832 statemsg = (
833 _(b'The repository is in an unfinished *%s* state.')
833 _(b'The repository is in an unfinished *%s* state.')
834 % self.unfinishedop
834 % self.unfinishedop
835 )
835 )
836 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
836 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
837 if self.unfinishedmsg:
837 if self.unfinishedmsg:
838 fm.data(unfinishedmsg=self.unfinishedmsg)
838 fm.data(unfinishedmsg=self.unfinishedmsg)
839
839
840 # May also start new data items.
840 # May also start new data items.
841 self._formatconflicts(fm)
841 self._formatconflicts(fm)
842
842
843 if self.unfinishedmsg:
843 if self.unfinishedmsg:
844 fm.plain(
844 fm.plain(
845 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
845 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
846 )
846 )
847
847
848 def _formatconflicts(self, fm):
848 def _formatconflicts(self, fm):
849 if not self.activemerge:
849 if not self.activemerge:
850 return
850 return
851
851
852 if self.unresolvedpaths:
852 if self.unresolvedpaths:
853 mergeliststr = b'\n'.join(
853 mergeliststr = b'\n'.join(
854 [
854 [
855 b' %s'
855 b' %s'
856 % util.pathto(self.reporoot, encoding.getcwd(), path)
856 % util.pathto(self.reporoot, encoding.getcwd(), path)
857 for path in self.unresolvedpaths
857 for path in self.unresolvedpaths
858 ]
858 ]
859 )
859 )
860 msg = (
860 msg = (
861 _(
861 _(
862 '''Unresolved merge conflicts:
862 '''Unresolved merge conflicts:
863
863
864 %s
864 %s
865
865
866 To mark files as resolved: hg resolve --mark FILE'''
866 To mark files as resolved: hg resolve --mark FILE'''
867 )
867 )
868 % mergeliststr
868 % mergeliststr
869 )
869 )
870
870
871 # If any paths with unresolved conflicts were not previously
871 # If any paths with unresolved conflicts were not previously
872 # formatted, output them now.
872 # formatted, output them now.
873 for f in self.unresolvedpaths:
873 for f in self.unresolvedpaths:
874 if f in self._formattedpaths:
874 if f in self._formattedpaths:
875 # Already output.
875 # Already output.
876 continue
876 continue
877 fm.startitem()
877 fm.startitem()
878 # We can't claim to know the status of the file - it may just
878 # We can't claim to know the status of the file - it may just
879 # have been in one of the states that were not requested for
879 # have been in one of the states that were not requested for
880 # display, so it could be anything.
880 # display, so it could be anything.
881 fm.data(itemtype=b'file', path=f, unresolved=True)
881 fm.data(itemtype=b'file', path=f, unresolved=True)
882
882
883 else:
883 else:
884 msg = _(b'No unresolved merge conflicts.')
884 msg = _(b'No unresolved merge conflicts.')
885
885
886 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
886 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
887
887
888
888
889 def readmorestatus(repo):
889 def readmorestatus(repo):
890 """Returns a morestatus object if the repo has unfinished state."""
890 """Returns a morestatus object if the repo has unfinished state."""
891 statetuple = statemod.getrepostate(repo)
891 statetuple = statemod.getrepostate(repo)
892 mergestate = mergestatemod.mergestate.read(repo)
892 mergestate = mergestatemod.mergestate.read(repo)
893 activemerge = mergestate.active()
893 activemerge = mergestate.active()
894 if not statetuple and not activemerge:
894 if not statetuple and not activemerge:
895 return None
895 return None
896
896
897 unfinishedop = unfinishedmsg = unresolved = None
897 unfinishedop = unfinishedmsg = unresolved = None
898 if statetuple:
898 if statetuple:
899 unfinishedop, unfinishedmsg = statetuple
899 unfinishedop, unfinishedmsg = statetuple
900 if activemerge:
900 if activemerge:
901 unresolved = sorted(mergestate.unresolved())
901 unresolved = sorted(mergestate.unresolved())
902 return morestatus(
902 return morestatus(
903 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
903 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
904 )
904 )
905
905
906
906
907 def findpossible(cmd, table, strict=False):
907 def findpossible(cmd, table, strict=False):
908 """
908 """
909 Return cmd -> (aliases, command table entry)
909 Return cmd -> (aliases, command table entry)
910 for each matching command.
910 for each matching command.
911 Return debug commands (or their aliases) only if no normal command matches.
911 Return debug commands (or their aliases) only if no normal command matches.
912 """
912 """
913 choice = {}
913 choice = {}
914 debugchoice = {}
914 debugchoice = {}
915
915
916 if cmd in table:
916 if cmd in table:
917 # short-circuit exact matches, "log" alias beats "log|history"
917 # short-circuit exact matches, "log" alias beats "log|history"
918 keys = [cmd]
918 keys = [cmd]
919 else:
919 else:
920 keys = table.keys()
920 keys = table.keys()
921
921
922 allcmds = []
922 allcmds = []
923 for e in keys:
923 for e in keys:
924 aliases = parsealiases(e)
924 aliases = parsealiases(e)
925 allcmds.extend(aliases)
925 allcmds.extend(aliases)
926 found = None
926 found = None
927 if cmd in aliases:
927 if cmd in aliases:
928 found = cmd
928 found = cmd
929 elif not strict:
929 elif not strict:
930 for a in aliases:
930 for a in aliases:
931 if a.startswith(cmd):
931 if a.startswith(cmd):
932 found = a
932 found = a
933 break
933 break
934 if found is not None:
934 if found is not None:
935 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
935 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
936 debugchoice[found] = (aliases, table[e])
936 debugchoice[found] = (aliases, table[e])
937 else:
937 else:
938 choice[found] = (aliases, table[e])
938 choice[found] = (aliases, table[e])
939
939
940 if not choice and debugchoice:
940 if not choice and debugchoice:
941 choice = debugchoice
941 choice = debugchoice
942
942
943 return choice, allcmds
943 return choice, allcmds
944
944
945
945
946 def findcmd(cmd, table, strict=True):
946 def findcmd(cmd, table, strict=True):
947 """Return (aliases, command table entry) for command string."""
947 """Return (aliases, command table entry) for command string."""
948 choice, allcmds = findpossible(cmd, table, strict)
948 choice, allcmds = findpossible(cmd, table, strict)
949
949
950 if cmd in choice:
950 if cmd in choice:
951 return choice[cmd]
951 return choice[cmd]
952
952
953 if len(choice) > 1:
953 if len(choice) > 1:
954 clist = sorted(choice)
954 clist = sorted(choice)
955 raise error.AmbiguousCommand(cmd, clist)
955 raise error.AmbiguousCommand(cmd, clist)
956
956
957 if choice:
957 if choice:
958 return list(choice.values())[0]
958 return list(choice.values())[0]
959
959
960 raise error.UnknownCommand(cmd, allcmds)
960 raise error.UnknownCommand(cmd, allcmds)
961
961
962
962
963 def changebranch(ui, repo, revs, label, opts):
963 def changebranch(ui, repo, revs, label, opts):
964 """ Change the branch name of given revs to label """
964 """ Change the branch name of given revs to label """
965
965
966 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
966 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
967 # abort in case of uncommitted merge or dirty wdir
967 # abort in case of uncommitted merge or dirty wdir
968 bailifchanged(repo)
968 bailifchanged(repo)
969 revs = scmutil.revrange(repo, revs)
969 revs = scmutil.revrange(repo, revs)
970 if not revs:
970 if not revs:
971 raise error.InputError(b"empty revision set")
971 raise error.InputError(b"empty revision set")
972 roots = repo.revs(b'roots(%ld)', revs)
972 roots = repo.revs(b'roots(%ld)', revs)
973 if len(roots) > 1:
973 if len(roots) > 1:
974 raise error.InputError(
974 raise error.InputError(
975 _(b"cannot change branch of non-linear revisions")
975 _(b"cannot change branch of non-linear revisions")
976 )
976 )
977 rewriteutil.precheck(repo, revs, b'change branch of')
977 rewriteutil.precheck(repo, revs, b'change branch of')
978
978
979 root = repo[roots.first()]
979 root = repo[roots.first()]
980 rpb = {parent.branch() for parent in root.parents()}
980 rpb = {parent.branch() for parent in root.parents()}
981 if (
981 if (
982 not opts.get(b'force')
982 not opts.get(b'force')
983 and label not in rpb
983 and label not in rpb
984 and label in repo.branchmap()
984 and label in repo.branchmap()
985 ):
985 ):
986 raise error.InputError(
986 raise error.InputError(
987 _(b"a branch of the same name already exists")
987 _(b"a branch of the same name already exists")
988 )
988 )
989
989
990 if repo.revs(b'obsolete() and %ld', revs):
990 if repo.revs(b'obsolete() and %ld', revs):
991 raise error.InputError(
991 raise error.InputError(
992 _(b"cannot change branch of a obsolete changeset")
992 _(b"cannot change branch of a obsolete changeset")
993 )
993 )
994
994
995 # make sure only topological heads
995 # make sure only topological heads
996 if repo.revs(b'heads(%ld) - head()', revs):
996 if repo.revs(b'heads(%ld) - head()', revs):
997 raise error.InputError(
997 raise error.InputError(
998 _(b"cannot change branch in middle of a stack")
998 _(b"cannot change branch in middle of a stack")
999 )
999 )
1000
1000
1001 replacements = {}
1001 replacements = {}
1002 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1002 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1003 # mercurial.subrepo -> mercurial.cmdutil
1003 # mercurial.subrepo -> mercurial.cmdutil
1004 from . import context
1004 from . import context
1005
1005
1006 for rev in revs:
1006 for rev in revs:
1007 ctx = repo[rev]
1007 ctx = repo[rev]
1008 oldbranch = ctx.branch()
1008 oldbranch = ctx.branch()
1009 # check if ctx has same branch
1009 # check if ctx has same branch
1010 if oldbranch == label:
1010 if oldbranch == label:
1011 continue
1011 continue
1012
1012
1013 def filectxfn(repo, newctx, path):
1013 def filectxfn(repo, newctx, path):
1014 try:
1014 try:
1015 return ctx[path]
1015 return ctx[path]
1016 except error.ManifestLookupError:
1016 except error.ManifestLookupError:
1017 return None
1017 return None
1018
1018
1019 ui.debug(
1019 ui.debug(
1020 b"changing branch of '%s' from '%s' to '%s'\n"
1020 b"changing branch of '%s' from '%s' to '%s'\n"
1021 % (hex(ctx.node()), oldbranch, label)
1021 % (hex(ctx.node()), oldbranch, label)
1022 )
1022 )
1023 extra = ctx.extra()
1023 extra = ctx.extra()
1024 extra[b'branch_change'] = hex(ctx.node())
1024 extra[b'branch_change'] = hex(ctx.node())
1025 # While changing branch of set of linear commits, make sure that
1025 # While changing branch of set of linear commits, make sure that
1026 # we base our commits on new parent rather than old parent which
1026 # we base our commits on new parent rather than old parent which
1027 # was obsoleted while changing the branch
1027 # was obsoleted while changing the branch
1028 p1 = ctx.p1().node()
1028 p1 = ctx.p1().node()
1029 p2 = ctx.p2().node()
1029 p2 = ctx.p2().node()
1030 if p1 in replacements:
1030 if p1 in replacements:
1031 p1 = replacements[p1][0]
1031 p1 = replacements[p1][0]
1032 if p2 in replacements:
1032 if p2 in replacements:
1033 p2 = replacements[p2][0]
1033 p2 = replacements[p2][0]
1034
1034
1035 mc = context.memctx(
1035 mc = context.memctx(
1036 repo,
1036 repo,
1037 (p1, p2),
1037 (p1, p2),
1038 ctx.description(),
1038 ctx.description(),
1039 ctx.files(),
1039 ctx.files(),
1040 filectxfn,
1040 filectxfn,
1041 user=ctx.user(),
1041 user=ctx.user(),
1042 date=ctx.date(),
1042 date=ctx.date(),
1043 extra=extra,
1043 extra=extra,
1044 branch=label,
1044 branch=label,
1045 )
1045 )
1046
1046
1047 newnode = repo.commitctx(mc)
1047 newnode = repo.commitctx(mc)
1048 replacements[ctx.node()] = (newnode,)
1048 replacements[ctx.node()] = (newnode,)
1049 ui.debug(b'new node id is %s\n' % hex(newnode))
1049 ui.debug(b'new node id is %s\n' % hex(newnode))
1050
1050
1051 # create obsmarkers and move bookmarks
1051 # create obsmarkers and move bookmarks
1052 scmutil.cleanupnodes(
1052 scmutil.cleanupnodes(
1053 repo, replacements, b'branch-change', fixphase=True
1053 repo, replacements, b'branch-change', fixphase=True
1054 )
1054 )
1055
1055
1056 # move the working copy too
1056 # move the working copy too
1057 wctx = repo[None]
1057 wctx = repo[None]
1058 # in-progress merge is a bit too complex for now.
1058 # in-progress merge is a bit too complex for now.
1059 if len(wctx.parents()) == 1:
1059 if len(wctx.parents()) == 1:
1060 newid = replacements.get(wctx.p1().node())
1060 newid = replacements.get(wctx.p1().node())
1061 if newid is not None:
1061 if newid is not None:
1062 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1062 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1063 # mercurial.cmdutil
1063 # mercurial.cmdutil
1064 from . import hg
1064 from . import hg
1065
1065
1066 hg.update(repo, newid[0], quietempty=True)
1066 hg.update(repo, newid[0], quietempty=True)
1067
1067
1068 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1068 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1069
1069
1070
1070
1071 def findrepo(p):
1071 def findrepo(p):
1072 while not os.path.isdir(os.path.join(p, b".hg")):
1072 while not os.path.isdir(os.path.join(p, b".hg")):
1073 oldp, p = p, os.path.dirname(p)
1073 oldp, p = p, os.path.dirname(p)
1074 if p == oldp:
1074 if p == oldp:
1075 return None
1075 return None
1076
1076
1077 return p
1077 return p
1078
1078
1079
1079
1080 def bailifchanged(repo, merge=True, hint=None):
1080 def bailifchanged(repo, merge=True, hint=None):
1081 """ enforce the precondition that working directory must be clean.
1081 """ enforce the precondition that working directory must be clean.
1082
1082
1083 'merge' can be set to false if a pending uncommitted merge should be
1083 'merge' can be set to false if a pending uncommitted merge should be
1084 ignored (such as when 'update --check' runs).
1084 ignored (such as when 'update --check' runs).
1085
1085
1086 'hint' is the usual hint given to Abort exception.
1086 'hint' is the usual hint given to Abort exception.
1087 """
1087 """
1088
1088
1089 if merge and repo.dirstate.p2() != nullid:
1089 if merge and repo.dirstate.p2() != nullid:
1090 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1090 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1091 st = repo.status()
1091 st = repo.status()
1092 if st.modified or st.added or st.removed or st.deleted:
1092 if st.modified or st.added or st.removed or st.deleted:
1093 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1093 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1094 ctx = repo[None]
1094 ctx = repo[None]
1095 for s in sorted(ctx.substate):
1095 for s in sorted(ctx.substate):
1096 ctx.sub(s).bailifchanged(hint=hint)
1096 ctx.sub(s).bailifchanged(hint=hint)
1097
1097
1098
1098
1099 def logmessage(ui, opts):
1099 def logmessage(ui, opts):
1100 """ get the log message according to -m and -l option """
1100 """ get the log message according to -m and -l option """
1101
1101
1102 check_at_most_one_arg(opts, b'message', b'logfile')
1102 check_at_most_one_arg(opts, b'message', b'logfile')
1103
1103
1104 message = opts.get(b'message')
1104 message = opts.get(b'message')
1105 logfile = opts.get(b'logfile')
1105 logfile = opts.get(b'logfile')
1106
1106
1107 if not message and logfile:
1107 if not message and logfile:
1108 try:
1108 try:
1109 if isstdiofilename(logfile):
1109 if isstdiofilename(logfile):
1110 message = ui.fin.read()
1110 message = ui.fin.read()
1111 else:
1111 else:
1112 message = b'\n'.join(util.readfile(logfile).splitlines())
1112 message = b'\n'.join(util.readfile(logfile).splitlines())
1113 except IOError as inst:
1113 except IOError as inst:
1114 raise error.Abort(
1114 raise error.Abort(
1115 _(b"can't read commit message '%s': %s")
1115 _(b"can't read commit message '%s': %s")
1116 % (logfile, encoding.strtolocal(inst.strerror))
1116 % (logfile, encoding.strtolocal(inst.strerror))
1117 )
1117 )
1118 return message
1118 return message
1119
1119
1120
1120
1121 def mergeeditform(ctxorbool, baseformname):
1121 def mergeeditform(ctxorbool, baseformname):
1122 """return appropriate editform name (referencing a committemplate)
1122 """return appropriate editform name (referencing a committemplate)
1123
1123
1124 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1124 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1125 merging is committed.
1125 merging is committed.
1126
1126
1127 This returns baseformname with '.merge' appended if it is a merge,
1127 This returns baseformname with '.merge' appended if it is a merge,
1128 otherwise '.normal' is appended.
1128 otherwise '.normal' is appended.
1129 """
1129 """
1130 if isinstance(ctxorbool, bool):
1130 if isinstance(ctxorbool, bool):
1131 if ctxorbool:
1131 if ctxorbool:
1132 return baseformname + b".merge"
1132 return baseformname + b".merge"
1133 elif len(ctxorbool.parents()) > 1:
1133 elif len(ctxorbool.parents()) > 1:
1134 return baseformname + b".merge"
1134 return baseformname + b".merge"
1135
1135
1136 return baseformname + b".normal"
1136 return baseformname + b".normal"
1137
1137
1138
1138
1139 def getcommiteditor(
1139 def getcommiteditor(
1140 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1140 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1141 ):
1141 ):
1142 """get appropriate commit message editor according to '--edit' option
1142 """get appropriate commit message editor according to '--edit' option
1143
1143
1144 'finishdesc' is a function to be called with edited commit message
1144 'finishdesc' is a function to be called with edited commit message
1145 (= 'description' of the new changeset) just after editing, but
1145 (= 'description' of the new changeset) just after editing, but
1146 before checking empty-ness. It should return actual text to be
1146 before checking empty-ness. It should return actual text to be
1147 stored into history. This allows to change description before
1147 stored into history. This allows to change description before
1148 storing.
1148 storing.
1149
1149
1150 'extramsg' is a extra message to be shown in the editor instead of
1150 'extramsg' is a extra message to be shown in the editor instead of
1151 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1151 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1152 is automatically added.
1152 is automatically added.
1153
1153
1154 'editform' is a dot-separated list of names, to distinguish
1154 'editform' is a dot-separated list of names, to distinguish
1155 the purpose of commit text editing.
1155 the purpose of commit text editing.
1156
1156
1157 'getcommiteditor' returns 'commitforceeditor' regardless of
1157 'getcommiteditor' returns 'commitforceeditor' regardless of
1158 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1158 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1159 they are specific for usage in MQ.
1159 they are specific for usage in MQ.
1160 """
1160 """
1161 if edit or finishdesc or extramsg:
1161 if edit or finishdesc or extramsg:
1162 return lambda r, c, s: commitforceeditor(
1162 return lambda r, c, s: commitforceeditor(
1163 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1163 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1164 )
1164 )
1165 elif editform:
1165 elif editform:
1166 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1166 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1167 else:
1167 else:
1168 return commiteditor
1168 return commiteditor
1169
1169
1170
1170
1171 def _escapecommandtemplate(tmpl):
1171 def _escapecommandtemplate(tmpl):
1172 parts = []
1172 parts = []
1173 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1173 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1174 if typ == b'string':
1174 if typ == b'string':
1175 parts.append(stringutil.escapestr(tmpl[start:end]))
1175 parts.append(stringutil.escapestr(tmpl[start:end]))
1176 else:
1176 else:
1177 parts.append(tmpl[start:end])
1177 parts.append(tmpl[start:end])
1178 return b''.join(parts)
1178 return b''.join(parts)
1179
1179
1180
1180
1181 def rendercommandtemplate(ui, tmpl, props):
1181 def rendercommandtemplate(ui, tmpl, props):
1182 r"""Expand a literal template 'tmpl' in a way suitable for command line
1182 r"""Expand a literal template 'tmpl' in a way suitable for command line
1183
1183
1184 '\' in outermost string is not taken as an escape character because it
1184 '\' in outermost string is not taken as an escape character because it
1185 is a directory separator on Windows.
1185 is a directory separator on Windows.
1186
1186
1187 >>> from . import ui as uimod
1187 >>> from . import ui as uimod
1188 >>> ui = uimod.ui()
1188 >>> ui = uimod.ui()
1189 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1189 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1190 'c:\\foo'
1190 'c:\\foo'
1191 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1191 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1192 'c:{path}'
1192 'c:{path}'
1193 """
1193 """
1194 if not tmpl:
1194 if not tmpl:
1195 return tmpl
1195 return tmpl
1196 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1196 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1197 return t.renderdefault(props)
1197 return t.renderdefault(props)
1198
1198
1199
1199
1200 def rendertemplate(ctx, tmpl, props=None):
1200 def rendertemplate(ctx, tmpl, props=None):
1201 """Expand a literal template 'tmpl' byte-string against one changeset
1201 """Expand a literal template 'tmpl' byte-string against one changeset
1202
1202
1203 Each props item must be a stringify-able value or a callable returning
1203 Each props item must be a stringify-able value or a callable returning
1204 such value, i.e. no bare list nor dict should be passed.
1204 such value, i.e. no bare list nor dict should be passed.
1205 """
1205 """
1206 repo = ctx.repo()
1206 repo = ctx.repo()
1207 tres = formatter.templateresources(repo.ui, repo)
1207 tres = formatter.templateresources(repo.ui, repo)
1208 t = formatter.maketemplater(
1208 t = formatter.maketemplater(
1209 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1209 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1210 )
1210 )
1211 mapping = {b'ctx': ctx}
1211 mapping = {b'ctx': ctx}
1212 if props:
1212 if props:
1213 mapping.update(props)
1213 mapping.update(props)
1214 return t.renderdefault(mapping)
1214 return t.renderdefault(mapping)
1215
1215
1216
1216
1217 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1217 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1218 """Format a changeset summary (one line)."""
1218 """Format a changeset summary (one line)."""
1219 spec = None
1219 spec = None
1220 if command:
1220 if command:
1221 spec = ui.config(
1221 spec = ui.config(
1222 b'command-templates', b'oneline-summary.%s' % command, None
1222 b'command-templates', b'oneline-summary.%s' % command, None
1223 )
1223 )
1224 if not spec:
1224 if not spec:
1225 spec = ui.config(b'command-templates', b'oneline-summary')
1225 spec = ui.config(b'command-templates', b'oneline-summary')
1226 if not spec:
1226 if not spec:
1227 spec = default_spec
1227 spec = default_spec
1228 if not spec:
1228 if not spec:
1229 spec = (
1229 spec = (
1230 b'{separate(" ", '
1230 b'{separate(" ", '
1231 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1231 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1232 b', '
1232 b', '
1233 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1233 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1234 b')} '
1234 b')} '
1235 b'"{label("oneline-summary.desc", desc|firstline)}"'
1235 b'"{label("oneline-summary.desc", desc|firstline)}"'
1236 )
1236 )
1237 text = rendertemplate(ctx, spec)
1237 text = rendertemplate(ctx, spec)
1238 return text.split(b'\n')[0]
1238 return text.split(b'\n')[0]
1239
1239
1240
1240
1241 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1241 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1242 r"""Convert old-style filename format string to template string
1242 r"""Convert old-style filename format string to template string
1243
1243
1244 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1244 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1245 'foo-{reporoot|basename}-{seqno}.patch'
1245 'foo-{reporoot|basename}-{seqno}.patch'
1246 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1246 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1247 '{rev}{tags % "{tag}"}{node}'
1247 '{rev}{tags % "{tag}"}{node}'
1248
1248
1249 '\' in outermost strings has to be escaped because it is a directory
1249 '\' in outermost strings has to be escaped because it is a directory
1250 separator on Windows:
1250 separator on Windows:
1251
1251
1252 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1252 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1253 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1253 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1254 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1254 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1255 '\\\\\\\\foo\\\\bar.patch'
1255 '\\\\\\\\foo\\\\bar.patch'
1256 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1256 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1257 '\\\\{tags % "{tag}"}'
1257 '\\\\{tags % "{tag}"}'
1258
1258
1259 but inner strings follow the template rules (i.e. '\' is taken as an
1259 but inner strings follow the template rules (i.e. '\' is taken as an
1260 escape character):
1260 escape character):
1261
1261
1262 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1262 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1263 '{"c:\\tmp"}'
1263 '{"c:\\tmp"}'
1264 """
1264 """
1265 expander = {
1265 expander = {
1266 b'H': b'{node}',
1266 b'H': b'{node}',
1267 b'R': b'{rev}',
1267 b'R': b'{rev}',
1268 b'h': b'{node|short}',
1268 b'h': b'{node|short}',
1269 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1269 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1270 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1270 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1271 b'%': b'%',
1271 b'%': b'%',
1272 b'b': b'{reporoot|basename}',
1272 b'b': b'{reporoot|basename}',
1273 }
1273 }
1274 if total is not None:
1274 if total is not None:
1275 expander[b'N'] = b'{total}'
1275 expander[b'N'] = b'{total}'
1276 if seqno is not None:
1276 if seqno is not None:
1277 expander[b'n'] = b'{seqno}'
1277 expander[b'n'] = b'{seqno}'
1278 if total is not None and seqno is not None:
1278 if total is not None and seqno is not None:
1279 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1279 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1280 if pathname is not None:
1280 if pathname is not None:
1281 expander[b's'] = b'{pathname|basename}'
1281 expander[b's'] = b'{pathname|basename}'
1282 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1282 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1283 expander[b'p'] = b'{pathname}'
1283 expander[b'p'] = b'{pathname}'
1284
1284
1285 newname = []
1285 newname = []
1286 for typ, start, end in templater.scantemplate(pat, raw=True):
1286 for typ, start, end in templater.scantemplate(pat, raw=True):
1287 if typ != b'string':
1287 if typ != b'string':
1288 newname.append(pat[start:end])
1288 newname.append(pat[start:end])
1289 continue
1289 continue
1290 i = start
1290 i = start
1291 while i < end:
1291 while i < end:
1292 n = pat.find(b'%', i, end)
1292 n = pat.find(b'%', i, end)
1293 if n < 0:
1293 if n < 0:
1294 newname.append(stringutil.escapestr(pat[i:end]))
1294 newname.append(stringutil.escapestr(pat[i:end]))
1295 break
1295 break
1296 newname.append(stringutil.escapestr(pat[i:n]))
1296 newname.append(stringutil.escapestr(pat[i:n]))
1297 if n + 2 > end:
1297 if n + 2 > end:
1298 raise error.Abort(
1298 raise error.Abort(
1299 _(b"incomplete format spec in output filename")
1299 _(b"incomplete format spec in output filename")
1300 )
1300 )
1301 c = pat[n + 1 : n + 2]
1301 c = pat[n + 1 : n + 2]
1302 i = n + 2
1302 i = n + 2
1303 try:
1303 try:
1304 newname.append(expander[c])
1304 newname.append(expander[c])
1305 except KeyError:
1305 except KeyError:
1306 raise error.Abort(
1306 raise error.Abort(
1307 _(b"invalid format spec '%%%s' in output filename") % c
1307 _(b"invalid format spec '%%%s' in output filename") % c
1308 )
1308 )
1309 return b''.join(newname)
1309 return b''.join(newname)
1310
1310
1311
1311
1312 def makefilename(ctx, pat, **props):
1312 def makefilename(ctx, pat, **props):
1313 if not pat:
1313 if not pat:
1314 return pat
1314 return pat
1315 tmpl = _buildfntemplate(pat, **props)
1315 tmpl = _buildfntemplate(pat, **props)
1316 # BUG: alias expansion shouldn't be made against template fragments
1316 # BUG: alias expansion shouldn't be made against template fragments
1317 # rewritten from %-format strings, but we have no easy way to partially
1317 # rewritten from %-format strings, but we have no easy way to partially
1318 # disable the expansion.
1318 # disable the expansion.
1319 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1319 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1320
1320
1321
1321
1322 def isstdiofilename(pat):
1322 def isstdiofilename(pat):
1323 """True if the given pat looks like a filename denoting stdin/stdout"""
1323 """True if the given pat looks like a filename denoting stdin/stdout"""
1324 return not pat or pat == b'-'
1324 return not pat or pat == b'-'
1325
1325
1326
1326
1327 class _unclosablefile(object):
1327 class _unclosablefile(object):
1328 def __init__(self, fp):
1328 def __init__(self, fp):
1329 self._fp = fp
1329 self._fp = fp
1330
1330
1331 def close(self):
1331 def close(self):
1332 pass
1332 pass
1333
1333
1334 def __iter__(self):
1334 def __iter__(self):
1335 return iter(self._fp)
1335 return iter(self._fp)
1336
1336
1337 def __getattr__(self, attr):
1337 def __getattr__(self, attr):
1338 return getattr(self._fp, attr)
1338 return getattr(self._fp, attr)
1339
1339
1340 def __enter__(self):
1340 def __enter__(self):
1341 return self
1341 return self
1342
1342
1343 def __exit__(self, exc_type, exc_value, exc_tb):
1343 def __exit__(self, exc_type, exc_value, exc_tb):
1344 pass
1344 pass
1345
1345
1346
1346
1347 def makefileobj(ctx, pat, mode=b'wb', **props):
1347 def makefileobj(ctx, pat, mode=b'wb', **props):
1348 writable = mode not in (b'r', b'rb')
1348 writable = mode not in (b'r', b'rb')
1349
1349
1350 if isstdiofilename(pat):
1350 if isstdiofilename(pat):
1351 repo = ctx.repo()
1351 repo = ctx.repo()
1352 if writable:
1352 if writable:
1353 fp = repo.ui.fout
1353 fp = repo.ui.fout
1354 else:
1354 else:
1355 fp = repo.ui.fin
1355 fp = repo.ui.fin
1356 return _unclosablefile(fp)
1356 return _unclosablefile(fp)
1357 fn = makefilename(ctx, pat, **props)
1357 fn = makefilename(ctx, pat, **props)
1358 return open(fn, mode)
1358 return open(fn, mode)
1359
1359
1360
1360
1361 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1361 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1362 """opens the changelog, manifest, a filelog or a given revlog"""
1362 """opens the changelog, manifest, a filelog or a given revlog"""
1363 cl = opts[b'changelog']
1363 cl = opts[b'changelog']
1364 mf = opts[b'manifest']
1364 mf = opts[b'manifest']
1365 dir = opts[b'dir']
1365 dir = opts[b'dir']
1366 msg = None
1366 msg = None
1367 if cl and mf:
1367 if cl and mf:
1368 msg = _(b'cannot specify --changelog and --manifest at the same time')
1368 msg = _(b'cannot specify --changelog and --manifest at the same time')
1369 elif cl and dir:
1369 elif cl and dir:
1370 msg = _(b'cannot specify --changelog and --dir at the same time')
1370 msg = _(b'cannot specify --changelog and --dir at the same time')
1371 elif cl or mf or dir:
1371 elif cl or mf or dir:
1372 if file_:
1372 if file_:
1373 msg = _(b'cannot specify filename with --changelog or --manifest')
1373 msg = _(b'cannot specify filename with --changelog or --manifest')
1374 elif not repo:
1374 elif not repo:
1375 msg = _(
1375 msg = _(
1376 b'cannot specify --changelog or --manifest or --dir '
1376 b'cannot specify --changelog or --manifest or --dir '
1377 b'without a repository'
1377 b'without a repository'
1378 )
1378 )
1379 if msg:
1379 if msg:
1380 raise error.InputError(msg)
1380 raise error.InputError(msg)
1381
1381
1382 r = None
1382 r = None
1383 if repo:
1383 if repo:
1384 if cl:
1384 if cl:
1385 r = repo.unfiltered().changelog
1385 r = repo.unfiltered().changelog
1386 elif dir:
1386 elif dir:
1387 if not scmutil.istreemanifest(repo):
1387 if not scmutil.istreemanifest(repo):
1388 raise error.InputError(
1388 raise error.InputError(
1389 _(
1389 _(
1390 b"--dir can only be used on repos with "
1390 b"--dir can only be used on repos with "
1391 b"treemanifest enabled"
1391 b"treemanifest enabled"
1392 )
1392 )
1393 )
1393 )
1394 if not dir.endswith(b'/'):
1394 if not dir.endswith(b'/'):
1395 dir = dir + b'/'
1395 dir = dir + b'/'
1396 dirlog = repo.manifestlog.getstorage(dir)
1396 dirlog = repo.manifestlog.getstorage(dir)
1397 if len(dirlog):
1397 if len(dirlog):
1398 r = dirlog
1398 r = dirlog
1399 elif mf:
1399 elif mf:
1400 r = repo.manifestlog.getstorage(b'')
1400 r = repo.manifestlog.getstorage(b'')
1401 elif file_:
1401 elif file_:
1402 filelog = repo.file(file_)
1402 filelog = repo.file(file_)
1403 if len(filelog):
1403 if len(filelog):
1404 r = filelog
1404 r = filelog
1405
1405
1406 # Not all storage may be revlogs. If requested, try to return an actual
1406 # Not all storage may be revlogs. If requested, try to return an actual
1407 # revlog instance.
1407 # revlog instance.
1408 if returnrevlog:
1408 if returnrevlog:
1409 if isinstance(r, revlog.revlog):
1409 if isinstance(r, revlog.revlog):
1410 pass
1410 pass
1411 elif util.safehasattr(r, b'_revlog'):
1411 elif util.safehasattr(r, b'_revlog'):
1412 r = r._revlog # pytype: disable=attribute-error
1412 r = r._revlog # pytype: disable=attribute-error
1413 elif r is not None:
1413 elif r is not None:
1414 raise error.InputError(
1414 raise error.InputError(
1415 _(b'%r does not appear to be a revlog') % r
1415 _(b'%r does not appear to be a revlog') % r
1416 )
1416 )
1417
1417
1418 if not r:
1418 if not r:
1419 if not returnrevlog:
1419 if not returnrevlog:
1420 raise error.InputError(_(b'cannot give path to non-revlog'))
1420 raise error.InputError(_(b'cannot give path to non-revlog'))
1421
1421
1422 if not file_:
1422 if not file_:
1423 raise error.CommandError(cmd, _(b'invalid arguments'))
1423 raise error.CommandError(cmd, _(b'invalid arguments'))
1424 if not os.path.isfile(file_):
1424 if not os.path.isfile(file_):
1425 raise error.InputError(_(b"revlog '%s' not found") % file_)
1425 raise error.InputError(_(b"revlog '%s' not found") % file_)
1426 r = revlog.revlog(
1426 r = revlog.revlog(
1427 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1427 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1428 )
1428 )
1429 return r
1429 return r
1430
1430
1431
1431
1432 def openrevlog(repo, cmd, file_, opts):
1432 def openrevlog(repo, cmd, file_, opts):
1433 """Obtain a revlog backing storage of an item.
1433 """Obtain a revlog backing storage of an item.
1434
1434
1435 This is similar to ``openstorage()`` except it always returns a revlog.
1435 This is similar to ``openstorage()`` except it always returns a revlog.
1436
1436
1437 In most cases, a caller cares about the main storage object - not the
1437 In most cases, a caller cares about the main storage object - not the
1438 revlog backing it. Therefore, this function should only be used by code
1438 revlog backing it. Therefore, this function should only be used by code
1439 that needs to examine low-level revlog implementation details. e.g. debug
1439 that needs to examine low-level revlog implementation details. e.g. debug
1440 commands.
1440 commands.
1441 """
1441 """
1442 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1442 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1443
1443
1444
1444
1445 def copy(ui, repo, pats, opts, rename=False):
1445 def copy(ui, repo, pats, opts, rename=False):
1446 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1446 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1447
1447
1448 # called with the repo lock held
1448 # called with the repo lock held
1449 #
1449 #
1450 # hgsep => pathname that uses "/" to separate directories
1450 # hgsep => pathname that uses "/" to separate directories
1451 # ossep => pathname that uses os.sep to separate directories
1451 # ossep => pathname that uses os.sep to separate directories
1452 cwd = repo.getcwd()
1452 cwd = repo.getcwd()
1453 targets = {}
1453 targets = {}
1454 forget = opts.get(b"forget")
1454 forget = opts.get(b"forget")
1455 after = opts.get(b"after")
1455 after = opts.get(b"after")
1456 dryrun = opts.get(b"dry_run")
1456 dryrun = opts.get(b"dry_run")
1457 rev = opts.get(b'at_rev')
1457 rev = opts.get(b'at_rev')
1458 if rev:
1458 if rev:
1459 if not forget and not after:
1459 if not forget and not after:
1460 # TODO: Remove this restriction and make it also create the copy
1460 # TODO: Remove this restriction and make it also create the copy
1461 # targets (and remove the rename source if rename==True).
1461 # targets (and remove the rename source if rename==True).
1462 raise error.InputError(_(b'--at-rev requires --after'))
1462 raise error.InputError(_(b'--at-rev requires --after'))
1463 ctx = scmutil.revsingle(repo, rev)
1463 ctx = scmutil.revsingle(repo, rev)
1464 if len(ctx.parents()) > 1:
1464 if len(ctx.parents()) > 1:
1465 raise error.InputError(
1465 raise error.InputError(
1466 _(b'cannot mark/unmark copy in merge commit')
1466 _(b'cannot mark/unmark copy in merge commit')
1467 )
1467 )
1468 else:
1468 else:
1469 ctx = repo[None]
1469 ctx = repo[None]
1470
1470
1471 pctx = ctx.p1()
1471 pctx = ctx.p1()
1472
1472
1473 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1473 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1474
1474
1475 if forget:
1475 if forget:
1476 if ctx.rev() is None:
1476 if ctx.rev() is None:
1477 new_ctx = ctx
1477 new_ctx = ctx
1478 else:
1478 else:
1479 if len(ctx.parents()) > 1:
1479 if len(ctx.parents()) > 1:
1480 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1480 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1481 # avoid cycle context -> subrepo -> cmdutil
1481 # avoid cycle context -> subrepo -> cmdutil
1482 from . import context
1482 from . import context
1483
1483
1484 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1484 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1485 new_ctx = context.overlayworkingctx(repo)
1485 new_ctx = context.overlayworkingctx(repo)
1486 new_ctx.setbase(ctx.p1())
1486 new_ctx.setbase(ctx.p1())
1487 mergemod.graft(repo, ctx, wctx=new_ctx)
1487 mergemod.graft(repo, ctx, wctx=new_ctx)
1488
1488
1489 match = scmutil.match(ctx, pats, opts)
1489 match = scmutil.match(ctx, pats, opts)
1490
1490
1491 current_copies = ctx.p1copies()
1491 current_copies = ctx.p1copies()
1492 current_copies.update(ctx.p2copies())
1492 current_copies.update(ctx.p2copies())
1493
1493
1494 uipathfn = scmutil.getuipathfn(repo)
1494 uipathfn = scmutil.getuipathfn(repo)
1495 for f in ctx.walk(match):
1495 for f in ctx.walk(match):
1496 if f in current_copies:
1496 if f in current_copies:
1497 new_ctx[f].markcopied(None)
1497 new_ctx[f].markcopied(None)
1498 elif match.exact(f):
1498 elif match.exact(f):
1499 ui.warn(
1499 ui.warn(
1500 _(
1500 _(
1501 b'%s: not unmarking as copy - file is not marked as copied\n'
1501 b'%s: not unmarking as copy - file is not marked as copied\n'
1502 )
1502 )
1503 % uipathfn(f)
1503 % uipathfn(f)
1504 )
1504 )
1505
1505
1506 if ctx.rev() is not None:
1506 if ctx.rev() is not None:
1507 with repo.lock():
1507 with repo.lock():
1508 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1508 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1509 new_node = mem_ctx.commit()
1509 new_node = mem_ctx.commit()
1510
1510
1511 if repo.dirstate.p1() == ctx.node():
1511 if repo.dirstate.p1() == ctx.node():
1512 with repo.dirstate.parentchange():
1512 with repo.dirstate.parentchange():
1513 scmutil.movedirstate(repo, repo[new_node])
1513 scmutil.movedirstate(repo, repo[new_node])
1514 replacements = {ctx.node(): [new_node]}
1514 replacements = {ctx.node(): [new_node]}
1515 scmutil.cleanupnodes(
1515 scmutil.cleanupnodes(
1516 repo, replacements, b'uncopy', fixphase=True
1516 repo, replacements, b'uncopy', fixphase=True
1517 )
1517 )
1518
1518
1519 return
1519 return
1520
1520
1521 pats = scmutil.expandpats(pats)
1521 pats = scmutil.expandpats(pats)
1522 if not pats:
1522 if not pats:
1523 raise error.InputError(_(b'no source or destination specified'))
1523 raise error.InputError(_(b'no source or destination specified'))
1524 if len(pats) == 1:
1524 if len(pats) == 1:
1525 raise error.InputError(_(b'no destination specified'))
1525 raise error.InputError(_(b'no destination specified'))
1526 dest = pats.pop()
1526 dest = pats.pop()
1527
1527
1528 def walkpat(pat):
1528 def walkpat(pat):
1529 srcs = []
1529 srcs = []
1530 # TODO: Inline and simplify the non-working-copy version of this code
1530 # TODO: Inline and simplify the non-working-copy version of this code
1531 # since it shares very little with the working-copy version of it.
1531 # since it shares very little with the working-copy version of it.
1532 ctx_to_walk = ctx if ctx.rev() is None else pctx
1532 ctx_to_walk = ctx if ctx.rev() is None else pctx
1533 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1533 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1534 for abs in ctx_to_walk.walk(m):
1534 for abs in ctx_to_walk.walk(m):
1535 rel = uipathfn(abs)
1535 rel = uipathfn(abs)
1536 exact = m.exact(abs)
1536 exact = m.exact(abs)
1537 if abs not in ctx:
1537 if abs not in ctx:
1538 if abs in pctx:
1538 if abs in pctx:
1539 if not after:
1539 if not after:
1540 if exact:
1540 if exact:
1541 ui.warn(
1541 ui.warn(
1542 _(
1542 _(
1543 b'%s: not copying - file has been marked '
1543 b'%s: not copying - file has been marked '
1544 b'for remove\n'
1544 b'for remove\n'
1545 )
1545 )
1546 % rel
1546 % rel
1547 )
1547 )
1548 continue
1548 continue
1549 else:
1549 else:
1550 if exact:
1550 if exact:
1551 ui.warn(
1551 ui.warn(
1552 _(b'%s: not copying - file is not managed\n') % rel
1552 _(b'%s: not copying - file is not managed\n') % rel
1553 )
1553 )
1554 continue
1554 continue
1555
1555
1556 # abs: hgsep
1556 # abs: hgsep
1557 # rel: ossep
1557 # rel: ossep
1558 srcs.append((abs, rel, exact))
1558 srcs.append((abs, rel, exact))
1559 return srcs
1559 return srcs
1560
1560
1561 if ctx.rev() is not None:
1561 if ctx.rev() is not None:
1562 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1562 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1563 absdest = pathutil.canonpath(repo.root, cwd, dest)
1563 absdest = pathutil.canonpath(repo.root, cwd, dest)
1564 if ctx.hasdir(absdest):
1564 if ctx.hasdir(absdest):
1565 raise error.InputError(
1565 raise error.InputError(
1566 _(b'%s: --at-rev does not support a directory as destination')
1566 _(b'%s: --at-rev does not support a directory as destination')
1567 % uipathfn(absdest)
1567 % uipathfn(absdest)
1568 )
1568 )
1569 if absdest not in ctx:
1569 if absdest not in ctx:
1570 raise error.InputError(
1570 raise error.InputError(
1571 _(b'%s: copy destination does not exist in %s')
1571 _(b'%s: copy destination does not exist in %s')
1572 % (uipathfn(absdest), ctx)
1572 % (uipathfn(absdest), ctx)
1573 )
1573 )
1574
1574
1575 # avoid cycle context -> subrepo -> cmdutil
1575 # avoid cycle context -> subrepo -> cmdutil
1576 from . import context
1576 from . import context
1577
1577
1578 copylist = []
1578 copylist = []
1579 for pat in pats:
1579 for pat in pats:
1580 srcs = walkpat(pat)
1580 srcs = walkpat(pat)
1581 if not srcs:
1581 if not srcs:
1582 continue
1582 continue
1583 for abs, rel, exact in srcs:
1583 for abs, rel, exact in srcs:
1584 copylist.append(abs)
1584 copylist.append(abs)
1585
1585
1586 if not copylist:
1586 if not copylist:
1587 raise error.InputError(_(b'no files to copy'))
1587 raise error.InputError(_(b'no files to copy'))
1588 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1588 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1589 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1589 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1590 # existing functions below.
1590 # existing functions below.
1591 if len(copylist) != 1:
1591 if len(copylist) != 1:
1592 raise error.InputError(_(b'--at-rev requires a single source'))
1592 raise error.InputError(_(b'--at-rev requires a single source'))
1593
1593
1594 new_ctx = context.overlayworkingctx(repo)
1594 new_ctx = context.overlayworkingctx(repo)
1595 new_ctx.setbase(ctx.p1())
1595 new_ctx.setbase(ctx.p1())
1596 mergemod.graft(repo, ctx, wctx=new_ctx)
1596 mergemod.graft(repo, ctx, wctx=new_ctx)
1597
1597
1598 new_ctx.markcopied(absdest, copylist[0])
1598 new_ctx.markcopied(absdest, copylist[0])
1599
1599
1600 with repo.lock():
1600 with repo.lock():
1601 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1601 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1602 new_node = mem_ctx.commit()
1602 new_node = mem_ctx.commit()
1603
1603
1604 if repo.dirstate.p1() == ctx.node():
1604 if repo.dirstate.p1() == ctx.node():
1605 with repo.dirstate.parentchange():
1605 with repo.dirstate.parentchange():
1606 scmutil.movedirstate(repo, repo[new_node])
1606 scmutil.movedirstate(repo, repo[new_node])
1607 replacements = {ctx.node(): [new_node]}
1607 replacements = {ctx.node(): [new_node]}
1608 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1608 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1609
1609
1610 return
1610 return
1611
1611
1612 # abssrc: hgsep
1612 # abssrc: hgsep
1613 # relsrc: ossep
1613 # relsrc: ossep
1614 # otarget: ossep
1614 # otarget: ossep
1615 def copyfile(abssrc, relsrc, otarget, exact):
1615 def copyfile(abssrc, relsrc, otarget, exact):
1616 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1616 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1617 if b'/' in abstarget:
1617 if b'/' in abstarget:
1618 # We cannot normalize abstarget itself, this would prevent
1618 # We cannot normalize abstarget itself, this would prevent
1619 # case only renames, like a => A.
1619 # case only renames, like a => A.
1620 abspath, absname = abstarget.rsplit(b'/', 1)
1620 abspath, absname = abstarget.rsplit(b'/', 1)
1621 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1621 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1622 reltarget = repo.pathto(abstarget, cwd)
1622 reltarget = repo.pathto(abstarget, cwd)
1623 target = repo.wjoin(abstarget)
1623 target = repo.wjoin(abstarget)
1624 src = repo.wjoin(abssrc)
1624 src = repo.wjoin(abssrc)
1625 state = repo.dirstate[abstarget]
1625 state = repo.dirstate[abstarget]
1626
1626
1627 scmutil.checkportable(ui, abstarget)
1627 scmutil.checkportable(ui, abstarget)
1628
1628
1629 # check for collisions
1629 # check for collisions
1630 prevsrc = targets.get(abstarget)
1630 prevsrc = targets.get(abstarget)
1631 if prevsrc is not None:
1631 if prevsrc is not None:
1632 ui.warn(
1632 ui.warn(
1633 _(b'%s: not overwriting - %s collides with %s\n')
1633 _(b'%s: not overwriting - %s collides with %s\n')
1634 % (
1634 % (
1635 reltarget,
1635 reltarget,
1636 repo.pathto(abssrc, cwd),
1636 repo.pathto(abssrc, cwd),
1637 repo.pathto(prevsrc, cwd),
1637 repo.pathto(prevsrc, cwd),
1638 )
1638 )
1639 )
1639 )
1640 return True # report a failure
1640 return True # report a failure
1641
1641
1642 # check for overwrites
1642 # check for overwrites
1643 exists = os.path.lexists(target)
1643 exists = os.path.lexists(target)
1644 samefile = False
1644 samefile = False
1645 if exists and abssrc != abstarget:
1645 if exists and abssrc != abstarget:
1646 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1646 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1647 abstarget
1647 abstarget
1648 ):
1648 ):
1649 if not rename:
1649 if not rename:
1650 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1650 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1651 return True # report a failure
1651 return True # report a failure
1652 exists = False
1652 exists = False
1653 samefile = True
1653 samefile = True
1654
1654
1655 if not after and exists or after and state in b'mn':
1655 if not after and exists or after and state in b'mn':
1656 if not opts[b'force']:
1656 if not opts[b'force']:
1657 if state in b'mn':
1657 if state in b'mn':
1658 msg = _(b'%s: not overwriting - file already committed\n')
1658 msg = _(b'%s: not overwriting - file already committed\n')
1659 if after:
1659 if after:
1660 flags = b'--after --force'
1660 flags = b'--after --force'
1661 else:
1661 else:
1662 flags = b'--force'
1662 flags = b'--force'
1663 if rename:
1663 if rename:
1664 hint = (
1664 hint = (
1665 _(
1665 _(
1666 b"('hg rename %s' to replace the file by "
1666 b"('hg rename %s' to replace the file by "
1667 b'recording a rename)\n'
1667 b'recording a rename)\n'
1668 )
1668 )
1669 % flags
1669 % flags
1670 )
1670 )
1671 else:
1671 else:
1672 hint = (
1672 hint = (
1673 _(
1673 _(
1674 b"('hg copy %s' to replace the file by "
1674 b"('hg copy %s' to replace the file by "
1675 b'recording a copy)\n'
1675 b'recording a copy)\n'
1676 )
1676 )
1677 % flags
1677 % flags
1678 )
1678 )
1679 else:
1679 else:
1680 msg = _(b'%s: not overwriting - file exists\n')
1680 msg = _(b'%s: not overwriting - file exists\n')
1681 if rename:
1681 if rename:
1682 hint = _(
1682 hint = _(
1683 b"('hg rename --after' to record the rename)\n"
1683 b"('hg rename --after' to record the rename)\n"
1684 )
1684 )
1685 else:
1685 else:
1686 hint = _(b"('hg copy --after' to record the copy)\n")
1686 hint = _(b"('hg copy --after' to record the copy)\n")
1687 ui.warn(msg % reltarget)
1687 ui.warn(msg % reltarget)
1688 ui.warn(hint)
1688 ui.warn(hint)
1689 return True # report a failure
1689 return True # report a failure
1690
1690
1691 if after:
1691 if after:
1692 if not exists:
1692 if not exists:
1693 if rename:
1693 if rename:
1694 ui.warn(
1694 ui.warn(
1695 _(b'%s: not recording move - %s does not exist\n')
1695 _(b'%s: not recording move - %s does not exist\n')
1696 % (relsrc, reltarget)
1696 % (relsrc, reltarget)
1697 )
1697 )
1698 else:
1698 else:
1699 ui.warn(
1699 ui.warn(
1700 _(b'%s: not recording copy - %s does not exist\n')
1700 _(b'%s: not recording copy - %s does not exist\n')
1701 % (relsrc, reltarget)
1701 % (relsrc, reltarget)
1702 )
1702 )
1703 return True # report a failure
1703 return True # report a failure
1704 elif not dryrun:
1704 elif not dryrun:
1705 try:
1705 try:
1706 if exists:
1706 if exists:
1707 os.unlink(target)
1707 os.unlink(target)
1708 targetdir = os.path.dirname(target) or b'.'
1708 targetdir = os.path.dirname(target) or b'.'
1709 if not os.path.isdir(targetdir):
1709 if not os.path.isdir(targetdir):
1710 os.makedirs(targetdir)
1710 os.makedirs(targetdir)
1711 if samefile:
1711 if samefile:
1712 tmp = target + b"~hgrename"
1712 tmp = target + b"~hgrename"
1713 os.rename(src, tmp)
1713 os.rename(src, tmp)
1714 os.rename(tmp, target)
1714 os.rename(tmp, target)
1715 else:
1715 else:
1716 # Preserve stat info on renames, not on copies; this matches
1716 # Preserve stat info on renames, not on copies; this matches
1717 # Linux CLI behavior.
1717 # Linux CLI behavior.
1718 util.copyfile(src, target, copystat=rename)
1718 util.copyfile(src, target, copystat=rename)
1719 srcexists = True
1719 srcexists = True
1720 except IOError as inst:
1720 except IOError as inst:
1721 if inst.errno == errno.ENOENT:
1721 if inst.errno == errno.ENOENT:
1722 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1722 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1723 srcexists = False
1723 srcexists = False
1724 else:
1724 else:
1725 ui.warn(
1725 ui.warn(
1726 _(b'%s: cannot copy - %s\n')
1726 _(b'%s: cannot copy - %s\n')
1727 % (relsrc, encoding.strtolocal(inst.strerror))
1727 % (relsrc, encoding.strtolocal(inst.strerror))
1728 )
1728 )
1729 return True # report a failure
1729 return True # report a failure
1730
1730
1731 if ui.verbose or not exact:
1731 if ui.verbose or not exact:
1732 if rename:
1732 if rename:
1733 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1733 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1734 else:
1734 else:
1735 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1735 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1736
1736
1737 targets[abstarget] = abssrc
1737 targets[abstarget] = abssrc
1738
1738
1739 # fix up dirstate
1739 # fix up dirstate
1740 scmutil.dirstatecopy(
1740 scmutil.dirstatecopy(
1741 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1741 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1742 )
1742 )
1743 if rename and not dryrun:
1743 if rename and not dryrun:
1744 if not after and srcexists and not samefile:
1744 if not after and srcexists and not samefile:
1745 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1745 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1746 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1746 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1747 ctx.forget([abssrc])
1747 ctx.forget([abssrc])
1748
1748
1749 # pat: ossep
1749 # pat: ossep
1750 # dest ossep
1750 # dest ossep
1751 # srcs: list of (hgsep, hgsep, ossep, bool)
1751 # srcs: list of (hgsep, hgsep, ossep, bool)
1752 # return: function that takes hgsep and returns ossep
1752 # return: function that takes hgsep and returns ossep
1753 def targetpathfn(pat, dest, srcs):
1753 def targetpathfn(pat, dest, srcs):
1754 if os.path.isdir(pat):
1754 if os.path.isdir(pat):
1755 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1755 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1756 abspfx = util.localpath(abspfx)
1756 abspfx = util.localpath(abspfx)
1757 if destdirexists:
1757 if destdirexists:
1758 striplen = len(os.path.split(abspfx)[0])
1758 striplen = len(os.path.split(abspfx)[0])
1759 else:
1759 else:
1760 striplen = len(abspfx)
1760 striplen = len(abspfx)
1761 if striplen:
1761 if striplen:
1762 striplen += len(pycompat.ossep)
1762 striplen += len(pycompat.ossep)
1763 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1763 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1764 elif destdirexists:
1764 elif destdirexists:
1765 res = lambda p: os.path.join(
1765 res = lambda p: os.path.join(
1766 dest, os.path.basename(util.localpath(p))
1766 dest, os.path.basename(util.localpath(p))
1767 )
1767 )
1768 else:
1768 else:
1769 res = lambda p: dest
1769 res = lambda p: dest
1770 return res
1770 return res
1771
1771
1772 # pat: ossep
1772 # pat: ossep
1773 # dest ossep
1773 # dest ossep
1774 # srcs: list of (hgsep, hgsep, ossep, bool)
1774 # srcs: list of (hgsep, hgsep, ossep, bool)
1775 # return: function that takes hgsep and returns ossep
1775 # return: function that takes hgsep and returns ossep
1776 def targetpathafterfn(pat, dest, srcs):
1776 def targetpathafterfn(pat, dest, srcs):
1777 if matchmod.patkind(pat):
1777 if matchmod.patkind(pat):
1778 # a mercurial pattern
1778 # a mercurial pattern
1779 res = lambda p: os.path.join(
1779 res = lambda p: os.path.join(
1780 dest, os.path.basename(util.localpath(p))
1780 dest, os.path.basename(util.localpath(p))
1781 )
1781 )
1782 else:
1782 else:
1783 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1783 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1784 if len(abspfx) < len(srcs[0][0]):
1784 if len(abspfx) < len(srcs[0][0]):
1785 # A directory. Either the target path contains the last
1785 # A directory. Either the target path contains the last
1786 # component of the source path or it does not.
1786 # component of the source path or it does not.
1787 def evalpath(striplen):
1787 def evalpath(striplen):
1788 score = 0
1788 score = 0
1789 for s in srcs:
1789 for s in srcs:
1790 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1790 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1791 if os.path.lexists(t):
1791 if os.path.lexists(t):
1792 score += 1
1792 score += 1
1793 return score
1793 return score
1794
1794
1795 abspfx = util.localpath(abspfx)
1795 abspfx = util.localpath(abspfx)
1796 striplen = len(abspfx)
1796 striplen = len(abspfx)
1797 if striplen:
1797 if striplen:
1798 striplen += len(pycompat.ossep)
1798 striplen += len(pycompat.ossep)
1799 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1799 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1800 score = evalpath(striplen)
1800 score = evalpath(striplen)
1801 striplen1 = len(os.path.split(abspfx)[0])
1801 striplen1 = len(os.path.split(abspfx)[0])
1802 if striplen1:
1802 if striplen1:
1803 striplen1 += len(pycompat.ossep)
1803 striplen1 += len(pycompat.ossep)
1804 if evalpath(striplen1) > score:
1804 if evalpath(striplen1) > score:
1805 striplen = striplen1
1805 striplen = striplen1
1806 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1806 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1807 else:
1807 else:
1808 # a file
1808 # a file
1809 if destdirexists:
1809 if destdirexists:
1810 res = lambda p: os.path.join(
1810 res = lambda p: os.path.join(
1811 dest, os.path.basename(util.localpath(p))
1811 dest, os.path.basename(util.localpath(p))
1812 )
1812 )
1813 else:
1813 else:
1814 res = lambda p: dest
1814 res = lambda p: dest
1815 return res
1815 return res
1816
1816
1817 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1817 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1818 if not destdirexists:
1818 if not destdirexists:
1819 if len(pats) > 1 or matchmod.patkind(pats[0]):
1819 if len(pats) > 1 or matchmod.patkind(pats[0]):
1820 raise error.InputError(
1820 raise error.InputError(
1821 _(
1821 _(
1822 b'with multiple sources, destination must be an '
1822 b'with multiple sources, destination must be an '
1823 b'existing directory'
1823 b'existing directory'
1824 )
1824 )
1825 )
1825 )
1826 if util.endswithsep(dest):
1826 if util.endswithsep(dest):
1827 raise error.InputError(
1827 raise error.InputError(
1828 _(b'destination %s is not a directory') % dest
1828 _(b'destination %s is not a directory') % dest
1829 )
1829 )
1830
1830
1831 tfn = targetpathfn
1831 tfn = targetpathfn
1832 if after:
1832 if after:
1833 tfn = targetpathafterfn
1833 tfn = targetpathafterfn
1834 copylist = []
1834 copylist = []
1835 for pat in pats:
1835 for pat in pats:
1836 srcs = walkpat(pat)
1836 srcs = walkpat(pat)
1837 if not srcs:
1837 if not srcs:
1838 continue
1838 continue
1839 copylist.append((tfn(pat, dest, srcs), srcs))
1839 copylist.append((tfn(pat, dest, srcs), srcs))
1840 if not copylist:
1840 if not copylist:
1841 raise error.InputError(_(b'no files to copy'))
1841 raise error.InputError(_(b'no files to copy'))
1842
1842
1843 errors = 0
1843 errors = 0
1844 for targetpath, srcs in copylist:
1844 for targetpath, srcs in copylist:
1845 for abssrc, relsrc, exact in srcs:
1845 for abssrc, relsrc, exact in srcs:
1846 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1846 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1847 errors += 1
1847 errors += 1
1848
1848
1849 return errors != 0
1849 return errors != 0
1850
1850
1851
1851
1852 ## facility to let extension process additional data into an import patch
1852 ## facility to let extension process additional data into an import patch
1853 # list of identifier to be executed in order
1853 # list of identifier to be executed in order
1854 extrapreimport = [] # run before commit
1854 extrapreimport = [] # run before commit
1855 extrapostimport = [] # run after commit
1855 extrapostimport = [] # run after commit
1856 # mapping from identifier to actual import function
1856 # mapping from identifier to actual import function
1857 #
1857 #
1858 # 'preimport' are run before the commit is made and are provided the following
1858 # 'preimport' are run before the commit is made and are provided the following
1859 # arguments:
1859 # arguments:
1860 # - repo: the localrepository instance,
1860 # - repo: the localrepository instance,
1861 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1861 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1862 # - extra: the future extra dictionary of the changeset, please mutate it,
1862 # - extra: the future extra dictionary of the changeset, please mutate it,
1863 # - opts: the import options.
1863 # - opts: the import options.
1864 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1864 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1865 # mutation of in memory commit and more. Feel free to rework the code to get
1865 # mutation of in memory commit and more. Feel free to rework the code to get
1866 # there.
1866 # there.
1867 extrapreimportmap = {}
1867 extrapreimportmap = {}
1868 # 'postimport' are run after the commit is made and are provided the following
1868 # 'postimport' are run after the commit is made and are provided the following
1869 # argument:
1869 # argument:
1870 # - ctx: the changectx created by import.
1870 # - ctx: the changectx created by import.
1871 extrapostimportmap = {}
1871 extrapostimportmap = {}
1872
1872
1873
1873
1874 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1874 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1875 """Utility function used by commands.import to import a single patch
1875 """Utility function used by commands.import to import a single patch
1876
1876
1877 This function is explicitly defined here to help the evolve extension to
1877 This function is explicitly defined here to help the evolve extension to
1878 wrap this part of the import logic.
1878 wrap this part of the import logic.
1879
1879
1880 The API is currently a bit ugly because it a simple code translation from
1880 The API is currently a bit ugly because it a simple code translation from
1881 the import command. Feel free to make it better.
1881 the import command. Feel free to make it better.
1882
1882
1883 :patchdata: a dictionary containing parsed patch data (such as from
1883 :patchdata: a dictionary containing parsed patch data (such as from
1884 ``patch.extract()``)
1884 ``patch.extract()``)
1885 :parents: nodes that will be parent of the created commit
1885 :parents: nodes that will be parent of the created commit
1886 :opts: the full dict of option passed to the import command
1886 :opts: the full dict of option passed to the import command
1887 :msgs: list to save commit message to.
1887 :msgs: list to save commit message to.
1888 (used in case we need to save it when failing)
1888 (used in case we need to save it when failing)
1889 :updatefunc: a function that update a repo to a given node
1889 :updatefunc: a function that update a repo to a given node
1890 updatefunc(<repo>, <node>)
1890 updatefunc(<repo>, <node>)
1891 """
1891 """
1892 # avoid cycle context -> subrepo -> cmdutil
1892 # avoid cycle context -> subrepo -> cmdutil
1893 from . import context
1893 from . import context
1894
1894
1895 tmpname = patchdata.get(b'filename')
1895 tmpname = patchdata.get(b'filename')
1896 message = patchdata.get(b'message')
1896 message = patchdata.get(b'message')
1897 user = opts.get(b'user') or patchdata.get(b'user')
1897 user = opts.get(b'user') or patchdata.get(b'user')
1898 date = opts.get(b'date') or patchdata.get(b'date')
1898 date = opts.get(b'date') or patchdata.get(b'date')
1899 branch = patchdata.get(b'branch')
1899 branch = patchdata.get(b'branch')
1900 nodeid = patchdata.get(b'nodeid')
1900 nodeid = patchdata.get(b'nodeid')
1901 p1 = patchdata.get(b'p1')
1901 p1 = patchdata.get(b'p1')
1902 p2 = patchdata.get(b'p2')
1902 p2 = patchdata.get(b'p2')
1903
1903
1904 nocommit = opts.get(b'no_commit')
1904 nocommit = opts.get(b'no_commit')
1905 importbranch = opts.get(b'import_branch')
1905 importbranch = opts.get(b'import_branch')
1906 update = not opts.get(b'bypass')
1906 update = not opts.get(b'bypass')
1907 strip = opts[b"strip"]
1907 strip = opts[b"strip"]
1908 prefix = opts[b"prefix"]
1908 prefix = opts[b"prefix"]
1909 sim = float(opts.get(b'similarity') or 0)
1909 sim = float(opts.get(b'similarity') or 0)
1910
1910
1911 if not tmpname:
1911 if not tmpname:
1912 return None, None, False
1912 return None, None, False
1913
1913
1914 rejects = False
1914 rejects = False
1915
1915
1916 cmdline_message = logmessage(ui, opts)
1916 cmdline_message = logmessage(ui, opts)
1917 if cmdline_message:
1917 if cmdline_message:
1918 # pickup the cmdline msg
1918 # pickup the cmdline msg
1919 message = cmdline_message
1919 message = cmdline_message
1920 elif message:
1920 elif message:
1921 # pickup the patch msg
1921 # pickup the patch msg
1922 message = message.strip()
1922 message = message.strip()
1923 else:
1923 else:
1924 # launch the editor
1924 # launch the editor
1925 message = None
1925 message = None
1926 ui.debug(b'message:\n%s\n' % (message or b''))
1926 ui.debug(b'message:\n%s\n' % (message or b''))
1927
1927
1928 if len(parents) == 1:
1928 if len(parents) == 1:
1929 parents.append(repo[nullid])
1929 parents.append(repo[nullid])
1930 if opts.get(b'exact'):
1930 if opts.get(b'exact'):
1931 if not nodeid or not p1:
1931 if not nodeid or not p1:
1932 raise error.InputError(_(b'not a Mercurial patch'))
1932 raise error.InputError(_(b'not a Mercurial patch'))
1933 p1 = repo[p1]
1933 p1 = repo[p1]
1934 p2 = repo[p2 or nullid]
1934 p2 = repo[p2 or nullid]
1935 elif p2:
1935 elif p2:
1936 try:
1936 try:
1937 p1 = repo[p1]
1937 p1 = repo[p1]
1938 p2 = repo[p2]
1938 p2 = repo[p2]
1939 # Without any options, consider p2 only if the
1939 # Without any options, consider p2 only if the
1940 # patch is being applied on top of the recorded
1940 # patch is being applied on top of the recorded
1941 # first parent.
1941 # first parent.
1942 if p1 != parents[0]:
1942 if p1 != parents[0]:
1943 p1 = parents[0]
1943 p1 = parents[0]
1944 p2 = repo[nullid]
1944 p2 = repo[nullid]
1945 except error.RepoError:
1945 except error.RepoError:
1946 p1, p2 = parents
1946 p1, p2 = parents
1947 if p2.node() == nullid:
1947 if p2.node() == nullid:
1948 ui.warn(
1948 ui.warn(
1949 _(
1949 _(
1950 b"warning: import the patch as a normal revision\n"
1950 b"warning: import the patch as a normal revision\n"
1951 b"(use --exact to import the patch as a merge)\n"
1951 b"(use --exact to import the patch as a merge)\n"
1952 )
1952 )
1953 )
1953 )
1954 else:
1954 else:
1955 p1, p2 = parents
1955 p1, p2 = parents
1956
1956
1957 n = None
1957 n = None
1958 if update:
1958 if update:
1959 if p1 != parents[0]:
1959 if p1 != parents[0]:
1960 updatefunc(repo, p1.node())
1960 updatefunc(repo, p1.node())
1961 if p2 != parents[1]:
1961 if p2 != parents[1]:
1962 repo.setparents(p1.node(), p2.node())
1962 repo.setparents(p1.node(), p2.node())
1963
1963
1964 if opts.get(b'exact') or importbranch:
1964 if opts.get(b'exact') or importbranch:
1965 repo.dirstate.setbranch(branch or b'default')
1965 repo.dirstate.setbranch(branch or b'default')
1966
1966
1967 partial = opts.get(b'partial', False)
1967 partial = opts.get(b'partial', False)
1968 files = set()
1968 files = set()
1969 try:
1969 try:
1970 patch.patch(
1970 patch.patch(
1971 ui,
1971 ui,
1972 repo,
1972 repo,
1973 tmpname,
1973 tmpname,
1974 strip=strip,
1974 strip=strip,
1975 prefix=prefix,
1975 prefix=prefix,
1976 files=files,
1976 files=files,
1977 eolmode=None,
1977 eolmode=None,
1978 similarity=sim / 100.0,
1978 similarity=sim / 100.0,
1979 )
1979 )
1980 except error.PatchError as e:
1980 except error.PatchError as e:
1981 if not partial:
1981 if not partial:
1982 raise error.Abort(pycompat.bytestr(e))
1982 raise error.Abort(pycompat.bytestr(e))
1983 if partial:
1983 if partial:
1984 rejects = True
1984 rejects = True
1985
1985
1986 files = list(files)
1986 files = list(files)
1987 if nocommit:
1987 if nocommit:
1988 if message:
1988 if message:
1989 msgs.append(message)
1989 msgs.append(message)
1990 else:
1990 else:
1991 if opts.get(b'exact') or p2:
1991 if opts.get(b'exact') or p2:
1992 # If you got here, you either use --force and know what
1992 # If you got here, you either use --force and know what
1993 # you are doing or used --exact or a merge patch while
1993 # you are doing or used --exact or a merge patch while
1994 # being updated to its first parent.
1994 # being updated to its first parent.
1995 m = None
1995 m = None
1996 else:
1996 else:
1997 m = scmutil.matchfiles(repo, files or [])
1997 m = scmutil.matchfiles(repo, files or [])
1998 editform = mergeeditform(repo[None], b'import.normal')
1998 editform = mergeeditform(repo[None], b'import.normal')
1999 if opts.get(b'exact'):
1999 if opts.get(b'exact'):
2000 editor = None
2000 editor = None
2001 else:
2001 else:
2002 editor = getcommiteditor(
2002 editor = getcommiteditor(
2003 editform=editform, **pycompat.strkwargs(opts)
2003 editform=editform, **pycompat.strkwargs(opts)
2004 )
2004 )
2005 extra = {}
2005 extra = {}
2006 for idfunc in extrapreimport:
2006 for idfunc in extrapreimport:
2007 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2007 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2008 overrides = {}
2008 overrides = {}
2009 if partial:
2009 if partial:
2010 overrides[(b'ui', b'allowemptycommit')] = True
2010 overrides[(b'ui', b'allowemptycommit')] = True
2011 if opts.get(b'secret'):
2011 if opts.get(b'secret'):
2012 overrides[(b'phases', b'new-commit')] = b'secret'
2012 overrides[(b'phases', b'new-commit')] = b'secret'
2013 with repo.ui.configoverride(overrides, b'import'):
2013 with repo.ui.configoverride(overrides, b'import'):
2014 n = repo.commit(
2014 n = repo.commit(
2015 message, user, date, match=m, editor=editor, extra=extra
2015 message, user, date, match=m, editor=editor, extra=extra
2016 )
2016 )
2017 for idfunc in extrapostimport:
2017 for idfunc in extrapostimport:
2018 extrapostimportmap[idfunc](repo[n])
2018 extrapostimportmap[idfunc](repo[n])
2019 else:
2019 else:
2020 if opts.get(b'exact') or importbranch:
2020 if opts.get(b'exact') or importbranch:
2021 branch = branch or b'default'
2021 branch = branch or b'default'
2022 else:
2022 else:
2023 branch = p1.branch()
2023 branch = p1.branch()
2024 store = patch.filestore()
2024 store = patch.filestore()
2025 try:
2025 try:
2026 files = set()
2026 files = set()
2027 try:
2027 try:
2028 patch.patchrepo(
2028 patch.patchrepo(
2029 ui,
2029 ui,
2030 repo,
2030 repo,
2031 p1,
2031 p1,
2032 store,
2032 store,
2033 tmpname,
2033 tmpname,
2034 strip,
2034 strip,
2035 prefix,
2035 prefix,
2036 files,
2036 files,
2037 eolmode=None,
2037 eolmode=None,
2038 )
2038 )
2039 except error.PatchError as e:
2039 except error.PatchError as e:
2040 raise error.Abort(stringutil.forcebytestr(e))
2040 raise error.Abort(stringutil.forcebytestr(e))
2041 if opts.get(b'exact'):
2041 if opts.get(b'exact'):
2042 editor = None
2042 editor = None
2043 else:
2043 else:
2044 editor = getcommiteditor(editform=b'import.bypass')
2044 editor = getcommiteditor(editform=b'import.bypass')
2045 memctx = context.memctx(
2045 memctx = context.memctx(
2046 repo,
2046 repo,
2047 (p1.node(), p2.node()),
2047 (p1.node(), p2.node()),
2048 message,
2048 message,
2049 files=files,
2049 files=files,
2050 filectxfn=store,
2050 filectxfn=store,
2051 user=user,
2051 user=user,
2052 date=date,
2052 date=date,
2053 branch=branch,
2053 branch=branch,
2054 editor=editor,
2054 editor=editor,
2055 )
2055 )
2056
2056
2057 overrides = {}
2057 overrides = {}
2058 if opts.get(b'secret'):
2058 if opts.get(b'secret'):
2059 overrides[(b'phases', b'new-commit')] = b'secret'
2059 overrides[(b'phases', b'new-commit')] = b'secret'
2060 with repo.ui.configoverride(overrides, b'import'):
2060 with repo.ui.configoverride(overrides, b'import'):
2061 n = memctx.commit()
2061 n = memctx.commit()
2062 finally:
2062 finally:
2063 store.close()
2063 store.close()
2064 if opts.get(b'exact') and nocommit:
2064 if opts.get(b'exact') and nocommit:
2065 # --exact with --no-commit is still useful in that it does merge
2065 # --exact with --no-commit is still useful in that it does merge
2066 # and branch bits
2066 # and branch bits
2067 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2067 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2068 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2068 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2069 raise error.Abort(_(b'patch is damaged or loses information'))
2069 raise error.Abort(_(b'patch is damaged or loses information'))
2070 msg = _(b'applied to working directory')
2070 msg = _(b'applied to working directory')
2071 if n:
2071 if n:
2072 # i18n: refers to a short changeset id
2072 # i18n: refers to a short changeset id
2073 msg = _(b'created %s') % short(n)
2073 msg = _(b'created %s') % short(n)
2074 return msg, n, rejects
2074 return msg, n, rejects
2075
2075
2076
2076
2077 # facility to let extensions include additional data in an exported patch
2077 # facility to let extensions include additional data in an exported patch
2078 # list of identifiers to be executed in order
2078 # list of identifiers to be executed in order
2079 extraexport = []
2079 extraexport = []
2080 # mapping from identifier to actual export function
2080 # mapping from identifier to actual export function
2081 # function as to return a string to be added to the header or None
2081 # function as to return a string to be added to the header or None
2082 # it is given two arguments (sequencenumber, changectx)
2082 # it is given two arguments (sequencenumber, changectx)
2083 extraexportmap = {}
2083 extraexportmap = {}
2084
2084
2085
2085
2086 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2086 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2087 node = scmutil.binnode(ctx)
2087 node = scmutil.binnode(ctx)
2088 parents = [p.node() for p in ctx.parents() if p]
2088 parents = [p.node() for p in ctx.parents() if p]
2089 branch = ctx.branch()
2089 branch = ctx.branch()
2090 if switch_parent:
2090 if switch_parent:
2091 parents.reverse()
2091 parents.reverse()
2092
2092
2093 if parents:
2093 if parents:
2094 prev = parents[0]
2094 prev = parents[0]
2095 else:
2095 else:
2096 prev = nullid
2096 prev = nullid
2097
2097
2098 fm.context(ctx=ctx)
2098 fm.context(ctx=ctx)
2099 fm.plain(b'# HG changeset patch\n')
2099 fm.plain(b'# HG changeset patch\n')
2100 fm.write(b'user', b'# User %s\n', ctx.user())
2100 fm.write(b'user', b'# User %s\n', ctx.user())
2101 fm.plain(b'# Date %d %d\n' % ctx.date())
2101 fm.plain(b'# Date %d %d\n' % ctx.date())
2102 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2102 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2103 fm.condwrite(
2103 fm.condwrite(
2104 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2104 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2105 )
2105 )
2106 fm.write(b'node', b'# Node ID %s\n', hex(node))
2106 fm.write(b'node', b'# Node ID %s\n', hex(node))
2107 fm.plain(b'# Parent %s\n' % hex(prev))
2107 fm.plain(b'# Parent %s\n' % hex(prev))
2108 if len(parents) > 1:
2108 if len(parents) > 1:
2109 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2109 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2110 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2110 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2111
2111
2112 # TODO: redesign extraexportmap function to support formatter
2112 # TODO: redesign extraexportmap function to support formatter
2113 for headerid in extraexport:
2113 for headerid in extraexport:
2114 header = extraexportmap[headerid](seqno, ctx)
2114 header = extraexportmap[headerid](seqno, ctx)
2115 if header is not None:
2115 if header is not None:
2116 fm.plain(b'# %s\n' % header)
2116 fm.plain(b'# %s\n' % header)
2117
2117
2118 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2118 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2119 fm.plain(b'\n')
2119 fm.plain(b'\n')
2120
2120
2121 if fm.isplain():
2121 if fm.isplain():
2122 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2122 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2123 for chunk, label in chunkiter:
2123 for chunk, label in chunkiter:
2124 fm.plain(chunk, label=label)
2124 fm.plain(chunk, label=label)
2125 else:
2125 else:
2126 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2126 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2127 # TODO: make it structured?
2127 # TODO: make it structured?
2128 fm.data(diff=b''.join(chunkiter))
2128 fm.data(diff=b''.join(chunkiter))
2129
2129
2130
2130
2131 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2131 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2132 """Export changesets to stdout or a single file"""
2132 """Export changesets to stdout or a single file"""
2133 for seqno, rev in enumerate(revs, 1):
2133 for seqno, rev in enumerate(revs, 1):
2134 ctx = repo[rev]
2134 ctx = repo[rev]
2135 if not dest.startswith(b'<'):
2135 if not dest.startswith(b'<'):
2136 repo.ui.note(b"%s\n" % dest)
2136 repo.ui.note(b"%s\n" % dest)
2137 fm.startitem()
2137 fm.startitem()
2138 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2138 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2139
2139
2140
2140
2141 def _exportfntemplate(
2141 def _exportfntemplate(
2142 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2142 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2143 ):
2143 ):
2144 """Export changesets to possibly multiple files"""
2144 """Export changesets to possibly multiple files"""
2145 total = len(revs)
2145 total = len(revs)
2146 revwidth = max(len(str(rev)) for rev in revs)
2146 revwidth = max(len(str(rev)) for rev in revs)
2147 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2147 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2148
2148
2149 for seqno, rev in enumerate(revs, 1):
2149 for seqno, rev in enumerate(revs, 1):
2150 ctx = repo[rev]
2150 ctx = repo[rev]
2151 dest = makefilename(
2151 dest = makefilename(
2152 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2152 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2153 )
2153 )
2154 filemap.setdefault(dest, []).append((seqno, rev))
2154 filemap.setdefault(dest, []).append((seqno, rev))
2155
2155
2156 for dest in filemap:
2156 for dest in filemap:
2157 with formatter.maybereopen(basefm, dest) as fm:
2157 with formatter.maybereopen(basefm, dest) as fm:
2158 repo.ui.note(b"%s\n" % dest)
2158 repo.ui.note(b"%s\n" % dest)
2159 for seqno, rev in filemap[dest]:
2159 for seqno, rev in filemap[dest]:
2160 fm.startitem()
2160 fm.startitem()
2161 ctx = repo[rev]
2161 ctx = repo[rev]
2162 _exportsingle(
2162 _exportsingle(
2163 repo, ctx, fm, match, switch_parent, seqno, diffopts
2163 repo, ctx, fm, match, switch_parent, seqno, diffopts
2164 )
2164 )
2165
2165
2166
2166
2167 def _prefetchchangedfiles(repo, revs, match):
2167 def _prefetchchangedfiles(repo, revs, match):
2168 allfiles = set()
2168 allfiles = set()
2169 for rev in revs:
2169 for rev in revs:
2170 for file in repo[rev].files():
2170 for file in repo[rev].files():
2171 if not match or match(file):
2171 if not match or match(file):
2172 allfiles.add(file)
2172 allfiles.add(file)
2173 match = scmutil.matchfiles(repo, allfiles)
2173 match = scmutil.matchfiles(repo, allfiles)
2174 revmatches = [(rev, match) for rev in revs]
2174 revmatches = [(rev, match) for rev in revs]
2175 scmutil.prefetchfiles(repo, revmatches)
2175 scmutil.prefetchfiles(repo, revmatches)
2176
2176
2177
2177
2178 def export(
2178 def export(
2179 repo,
2179 repo,
2180 revs,
2180 revs,
2181 basefm,
2181 basefm,
2182 fntemplate=b'hg-%h.patch',
2182 fntemplate=b'hg-%h.patch',
2183 switch_parent=False,
2183 switch_parent=False,
2184 opts=None,
2184 opts=None,
2185 match=None,
2185 match=None,
2186 ):
2186 ):
2187 '''export changesets as hg patches
2187 '''export changesets as hg patches
2188
2188
2189 Args:
2189 Args:
2190 repo: The repository from which we're exporting revisions.
2190 repo: The repository from which we're exporting revisions.
2191 revs: A list of revisions to export as revision numbers.
2191 revs: A list of revisions to export as revision numbers.
2192 basefm: A formatter to which patches should be written.
2192 basefm: A formatter to which patches should be written.
2193 fntemplate: An optional string to use for generating patch file names.
2193 fntemplate: An optional string to use for generating patch file names.
2194 switch_parent: If True, show diffs against second parent when not nullid.
2194 switch_parent: If True, show diffs against second parent when not nullid.
2195 Default is false, which always shows diff against p1.
2195 Default is false, which always shows diff against p1.
2196 opts: diff options to use for generating the patch.
2196 opts: diff options to use for generating the patch.
2197 match: If specified, only export changes to files matching this matcher.
2197 match: If specified, only export changes to files matching this matcher.
2198
2198
2199 Returns:
2199 Returns:
2200 Nothing.
2200 Nothing.
2201
2201
2202 Side Effect:
2202 Side Effect:
2203 "HG Changeset Patch" data is emitted to one of the following
2203 "HG Changeset Patch" data is emitted to one of the following
2204 destinations:
2204 destinations:
2205 fntemplate specified: Each rev is written to a unique file named using
2205 fntemplate specified: Each rev is written to a unique file named using
2206 the given template.
2206 the given template.
2207 Otherwise: All revs will be written to basefm.
2207 Otherwise: All revs will be written to basefm.
2208 '''
2208 '''
2209 _prefetchchangedfiles(repo, revs, match)
2209 _prefetchchangedfiles(repo, revs, match)
2210
2210
2211 if not fntemplate:
2211 if not fntemplate:
2212 _exportfile(
2212 _exportfile(
2213 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2213 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2214 )
2214 )
2215 else:
2215 else:
2216 _exportfntemplate(
2216 _exportfntemplate(
2217 repo, revs, basefm, fntemplate, switch_parent, opts, match
2217 repo, revs, basefm, fntemplate, switch_parent, opts, match
2218 )
2218 )
2219
2219
2220
2220
2221 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2221 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2222 """Export changesets to the given file stream"""
2222 """Export changesets to the given file stream"""
2223 _prefetchchangedfiles(repo, revs, match)
2223 _prefetchchangedfiles(repo, revs, match)
2224
2224
2225 dest = getattr(fp, 'name', b'<unnamed>')
2225 dest = getattr(fp, 'name', b'<unnamed>')
2226 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2226 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2227 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2227 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2228
2228
2229
2229
2230 def showmarker(fm, marker, index=None):
2230 def showmarker(fm, marker, index=None):
2231 """utility function to display obsolescence marker in a readable way
2231 """utility function to display obsolescence marker in a readable way
2232
2232
2233 To be used by debug function."""
2233 To be used by debug function."""
2234 if index is not None:
2234 if index is not None:
2235 fm.write(b'index', b'%i ', index)
2235 fm.write(b'index', b'%i ', index)
2236 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2236 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2237 succs = marker.succnodes()
2237 succs = marker.succnodes()
2238 fm.condwrite(
2238 fm.condwrite(
2239 succs,
2239 succs,
2240 b'succnodes',
2240 b'succnodes',
2241 b'%s ',
2241 b'%s ',
2242 fm.formatlist(map(hex, succs), name=b'node'),
2242 fm.formatlist(map(hex, succs), name=b'node'),
2243 )
2243 )
2244 fm.write(b'flag', b'%X ', marker.flags())
2244 fm.write(b'flag', b'%X ', marker.flags())
2245 parents = marker.parentnodes()
2245 parents = marker.parentnodes()
2246 if parents is not None:
2246 if parents is not None:
2247 fm.write(
2247 fm.write(
2248 b'parentnodes',
2248 b'parentnodes',
2249 b'{%s} ',
2249 b'{%s} ',
2250 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2250 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2251 )
2251 )
2252 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2252 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2253 meta = marker.metadata().copy()
2253 meta = marker.metadata().copy()
2254 meta.pop(b'date', None)
2254 meta.pop(b'date', None)
2255 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2255 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2256 fm.write(
2256 fm.write(
2257 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2257 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2258 )
2258 )
2259 fm.plain(b'\n')
2259 fm.plain(b'\n')
2260
2260
2261
2261
2262 def finddate(ui, repo, date):
2262 def finddate(ui, repo, date):
2263 """Find the tipmost changeset that matches the given date spec"""
2263 """Find the tipmost changeset that matches the given date spec"""
2264 mrevs = repo.revs(b'date(%s)', date)
2264 mrevs = repo.revs(b'date(%s)', date)
2265 try:
2265 try:
2266 rev = mrevs.max()
2266 rev = mrevs.max()
2267 except ValueError:
2267 except ValueError:
2268 raise error.InputError(_(b"revision matching date not found"))
2268 raise error.InputError(_(b"revision matching date not found"))
2269
2269
2270 ui.status(
2270 ui.status(
2271 _(b"found revision %d from %s\n")
2271 _(b"found revision %d from %s\n")
2272 % (rev, dateutil.datestr(repo[rev].date()))
2272 % (rev, dateutil.datestr(repo[rev].date()))
2273 )
2273 )
2274 return b'%d' % rev
2274 return b'%d' % rev
2275
2275
2276
2276
2277 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2277 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2278 bad = []
2278 bad = []
2279
2279
2280 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2280 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2281 names = []
2281 names = []
2282 wctx = repo[None]
2282 wctx = repo[None]
2283 cca = None
2283 cca = None
2284 abort, warn = scmutil.checkportabilityalert(ui)
2284 abort, warn = scmutil.checkportabilityalert(ui)
2285 if abort or warn:
2285 if abort or warn:
2286 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2286 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2287
2287
2288 match = repo.narrowmatch(match, includeexact=True)
2288 match = repo.narrowmatch(match, includeexact=True)
2289 badmatch = matchmod.badmatch(match, badfn)
2289 badmatch = matchmod.badmatch(match, badfn)
2290 dirstate = repo.dirstate
2290 dirstate = repo.dirstate
2291 # We don't want to just call wctx.walk here, since it would return a lot of
2291 # We don't want to just call wctx.walk here, since it would return a lot of
2292 # clean files, which we aren't interested in and takes time.
2292 # clean files, which we aren't interested in and takes time.
2293 for f in sorted(
2293 for f in sorted(
2294 dirstate.walk(
2294 dirstate.walk(
2295 badmatch,
2295 badmatch,
2296 subrepos=sorted(wctx.substate),
2296 subrepos=sorted(wctx.substate),
2297 unknown=True,
2297 unknown=True,
2298 ignored=False,
2298 ignored=False,
2299 full=False,
2299 full=False,
2300 )
2300 )
2301 ):
2301 ):
2302 exact = match.exact(f)
2302 exact = match.exact(f)
2303 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2303 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2304 if cca:
2304 if cca:
2305 cca(f)
2305 cca(f)
2306 names.append(f)
2306 names.append(f)
2307 if ui.verbose or not exact:
2307 if ui.verbose or not exact:
2308 ui.status(
2308 ui.status(
2309 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2309 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2310 )
2310 )
2311
2311
2312 for subpath in sorted(wctx.substate):
2312 for subpath in sorted(wctx.substate):
2313 sub = wctx.sub(subpath)
2313 sub = wctx.sub(subpath)
2314 try:
2314 try:
2315 submatch = matchmod.subdirmatcher(subpath, match)
2315 submatch = matchmod.subdirmatcher(subpath, match)
2316 subprefix = repo.wvfs.reljoin(prefix, subpath)
2316 subprefix = repo.wvfs.reljoin(prefix, subpath)
2317 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2317 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2318 if opts.get('subrepos'):
2318 if opts.get('subrepos'):
2319 bad.extend(
2319 bad.extend(
2320 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2320 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2321 )
2321 )
2322 else:
2322 else:
2323 bad.extend(
2323 bad.extend(
2324 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2324 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2325 )
2325 )
2326 except error.LookupError:
2326 except error.LookupError:
2327 ui.status(
2327 ui.status(
2328 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2328 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2329 )
2329 )
2330
2330
2331 if not opts.get('dry_run'):
2331 if not opts.get('dry_run'):
2332 rejected = wctx.add(names, prefix)
2332 rejected = wctx.add(names, prefix)
2333 bad.extend(f for f in rejected if f in match.files())
2333 bad.extend(f for f in rejected if f in match.files())
2334 return bad
2334 return bad
2335
2335
2336
2336
2337 def addwebdirpath(repo, serverpath, webconf):
2337 def addwebdirpath(repo, serverpath, webconf):
2338 webconf[serverpath] = repo.root
2338 webconf[serverpath] = repo.root
2339 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2339 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2340
2340
2341 for r in repo.revs(b'filelog("path:.hgsub")'):
2341 for r in repo.revs(b'filelog("path:.hgsub")'):
2342 ctx = repo[r]
2342 ctx = repo[r]
2343 for subpath in ctx.substate:
2343 for subpath in ctx.substate:
2344 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2344 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2345
2345
2346
2346
2347 def forget(
2347 def forget(
2348 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2348 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2349 ):
2349 ):
2350 if dryrun and interactive:
2350 if dryrun and interactive:
2351 raise error.InputError(
2351 raise error.InputError(
2352 _(b"cannot specify both --dry-run and --interactive")
2352 _(b"cannot specify both --dry-run and --interactive")
2353 )
2353 )
2354 bad = []
2354 bad = []
2355 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2355 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2356 wctx = repo[None]
2356 wctx = repo[None]
2357 forgot = []
2357 forgot = []
2358
2358
2359 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2359 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2360 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2360 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2361 if explicitonly:
2361 if explicitonly:
2362 forget = [f for f in forget if match.exact(f)]
2362 forget = [f for f in forget if match.exact(f)]
2363
2363
2364 for subpath in sorted(wctx.substate):
2364 for subpath in sorted(wctx.substate):
2365 sub = wctx.sub(subpath)
2365 sub = wctx.sub(subpath)
2366 submatch = matchmod.subdirmatcher(subpath, match)
2366 submatch = matchmod.subdirmatcher(subpath, match)
2367 subprefix = repo.wvfs.reljoin(prefix, subpath)
2367 subprefix = repo.wvfs.reljoin(prefix, subpath)
2368 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2368 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2369 try:
2369 try:
2370 subbad, subforgot = sub.forget(
2370 subbad, subforgot = sub.forget(
2371 submatch,
2371 submatch,
2372 subprefix,
2372 subprefix,
2373 subuipathfn,
2373 subuipathfn,
2374 dryrun=dryrun,
2374 dryrun=dryrun,
2375 interactive=interactive,
2375 interactive=interactive,
2376 )
2376 )
2377 bad.extend([subpath + b'/' + f for f in subbad])
2377 bad.extend([subpath + b'/' + f for f in subbad])
2378 forgot.extend([subpath + b'/' + f for f in subforgot])
2378 forgot.extend([subpath + b'/' + f for f in subforgot])
2379 except error.LookupError:
2379 except error.LookupError:
2380 ui.status(
2380 ui.status(
2381 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2381 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2382 )
2382 )
2383
2383
2384 if not explicitonly:
2384 if not explicitonly:
2385 for f in match.files():
2385 for f in match.files():
2386 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2386 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2387 if f not in forgot:
2387 if f not in forgot:
2388 if repo.wvfs.exists(f):
2388 if repo.wvfs.exists(f):
2389 # Don't complain if the exact case match wasn't given.
2389 # Don't complain if the exact case match wasn't given.
2390 # But don't do this until after checking 'forgot', so
2390 # But don't do this until after checking 'forgot', so
2391 # that subrepo files aren't normalized, and this op is
2391 # that subrepo files aren't normalized, and this op is
2392 # purely from data cached by the status walk above.
2392 # purely from data cached by the status walk above.
2393 if repo.dirstate.normalize(f) in repo.dirstate:
2393 if repo.dirstate.normalize(f) in repo.dirstate:
2394 continue
2394 continue
2395 ui.warn(
2395 ui.warn(
2396 _(
2396 _(
2397 b'not removing %s: '
2397 b'not removing %s: '
2398 b'file is already untracked\n'
2398 b'file is already untracked\n'
2399 )
2399 )
2400 % uipathfn(f)
2400 % uipathfn(f)
2401 )
2401 )
2402 bad.append(f)
2402 bad.append(f)
2403
2403
2404 if interactive:
2404 if interactive:
2405 responses = _(
2405 responses = _(
2406 b'[Ynsa?]'
2406 b'[Ynsa?]'
2407 b'$$ &Yes, forget this file'
2407 b'$$ &Yes, forget this file'
2408 b'$$ &No, skip this file'
2408 b'$$ &No, skip this file'
2409 b'$$ &Skip remaining files'
2409 b'$$ &Skip remaining files'
2410 b'$$ Include &all remaining files'
2410 b'$$ Include &all remaining files'
2411 b'$$ &? (display help)'
2411 b'$$ &? (display help)'
2412 )
2412 )
2413 for filename in forget[:]:
2413 for filename in forget[:]:
2414 r = ui.promptchoice(
2414 r = ui.promptchoice(
2415 _(b'forget %s %s') % (uipathfn(filename), responses)
2415 _(b'forget %s %s') % (uipathfn(filename), responses)
2416 )
2416 )
2417 if r == 4: # ?
2417 if r == 4: # ?
2418 while r == 4:
2418 while r == 4:
2419 for c, t in ui.extractchoices(responses)[1]:
2419 for c, t in ui.extractchoices(responses)[1]:
2420 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2420 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2421 r = ui.promptchoice(
2421 r = ui.promptchoice(
2422 _(b'forget %s %s') % (uipathfn(filename), responses)
2422 _(b'forget %s %s') % (uipathfn(filename), responses)
2423 )
2423 )
2424 if r == 0: # yes
2424 if r == 0: # yes
2425 continue
2425 continue
2426 elif r == 1: # no
2426 elif r == 1: # no
2427 forget.remove(filename)
2427 forget.remove(filename)
2428 elif r == 2: # Skip
2428 elif r == 2: # Skip
2429 fnindex = forget.index(filename)
2429 fnindex = forget.index(filename)
2430 del forget[fnindex:]
2430 del forget[fnindex:]
2431 break
2431 break
2432 elif r == 3: # All
2432 elif r == 3: # All
2433 break
2433 break
2434
2434
2435 for f in forget:
2435 for f in forget:
2436 if ui.verbose or not match.exact(f) or interactive:
2436 if ui.verbose or not match.exact(f) or interactive:
2437 ui.status(
2437 ui.status(
2438 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2438 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2439 )
2439 )
2440
2440
2441 if not dryrun:
2441 if not dryrun:
2442 rejected = wctx.forget(forget, prefix)
2442 rejected = wctx.forget(forget, prefix)
2443 bad.extend(f for f in rejected if f in match.files())
2443 bad.extend(f for f in rejected if f in match.files())
2444 forgot.extend(f for f in forget if f not in rejected)
2444 forgot.extend(f for f in forget if f not in rejected)
2445 return bad, forgot
2445 return bad, forgot
2446
2446
2447
2447
2448 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2448 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2449 ret = 1
2449 ret = 1
2450
2450
2451 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2451 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2452 if fm.isplain() and not needsfctx:
2452 if fm.isplain() and not needsfctx:
2453 # Fast path. The speed-up comes from skipping the formatter, and batching
2453 # Fast path. The speed-up comes from skipping the formatter, and batching
2454 # calls to ui.write.
2454 # calls to ui.write.
2455 buf = []
2455 buf = []
2456 for f in ctx.matches(m):
2456 for f in ctx.matches(m):
2457 buf.append(fmt % uipathfn(f))
2457 buf.append(fmt % uipathfn(f))
2458 if len(buf) > 100:
2458 if len(buf) > 100:
2459 ui.write(b''.join(buf))
2459 ui.write(b''.join(buf))
2460 del buf[:]
2460 del buf[:]
2461 ret = 0
2461 ret = 0
2462 if buf:
2462 if buf:
2463 ui.write(b''.join(buf))
2463 ui.write(b''.join(buf))
2464 else:
2464 else:
2465 for f in ctx.matches(m):
2465 for f in ctx.matches(m):
2466 fm.startitem()
2466 fm.startitem()
2467 fm.context(ctx=ctx)
2467 fm.context(ctx=ctx)
2468 if needsfctx:
2468 if needsfctx:
2469 fc = ctx[f]
2469 fc = ctx[f]
2470 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2470 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2471 fm.data(path=f)
2471 fm.data(path=f)
2472 fm.plain(fmt % uipathfn(f))
2472 fm.plain(fmt % uipathfn(f))
2473 ret = 0
2473 ret = 0
2474
2474
2475 for subpath in sorted(ctx.substate):
2475 for subpath in sorted(ctx.substate):
2476 submatch = matchmod.subdirmatcher(subpath, m)
2476 submatch = matchmod.subdirmatcher(subpath, m)
2477 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2477 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2478 if subrepos or m.exact(subpath) or any(submatch.files()):
2478 if subrepos or m.exact(subpath) or any(submatch.files()):
2479 sub = ctx.sub(subpath)
2479 sub = ctx.sub(subpath)
2480 try:
2480 try:
2481 recurse = m.exact(subpath) or subrepos
2481 recurse = m.exact(subpath) or subrepos
2482 if (
2482 if (
2483 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2483 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2484 == 0
2484 == 0
2485 ):
2485 ):
2486 ret = 0
2486 ret = 0
2487 except error.LookupError:
2487 except error.LookupError:
2488 ui.status(
2488 ui.status(
2489 _(b"skipping missing subrepository: %s\n")
2489 _(b"skipping missing subrepository: %s\n")
2490 % uipathfn(subpath)
2490 % uipathfn(subpath)
2491 )
2491 )
2492
2492
2493 return ret
2493 return ret
2494
2494
2495
2495
2496 def remove(
2496 def remove(
2497 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2497 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2498 ):
2498 ):
2499 ret = 0
2499 ret = 0
2500 s = repo.status(match=m, clean=True)
2500 s = repo.status(match=m, clean=True)
2501 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2501 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2502
2502
2503 wctx = repo[None]
2503 wctx = repo[None]
2504
2504
2505 if warnings is None:
2505 if warnings is None:
2506 warnings = []
2506 warnings = []
2507 warn = True
2507 warn = True
2508 else:
2508 else:
2509 warn = False
2509 warn = False
2510
2510
2511 subs = sorted(wctx.substate)
2511 subs = sorted(wctx.substate)
2512 progress = ui.makeprogress(
2512 progress = ui.makeprogress(
2513 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2513 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2514 )
2514 )
2515 for subpath in subs:
2515 for subpath in subs:
2516 submatch = matchmod.subdirmatcher(subpath, m)
2516 submatch = matchmod.subdirmatcher(subpath, m)
2517 subprefix = repo.wvfs.reljoin(prefix, subpath)
2517 subprefix = repo.wvfs.reljoin(prefix, subpath)
2518 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2518 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2519 if subrepos or m.exact(subpath) or any(submatch.files()):
2519 if subrepos or m.exact(subpath) or any(submatch.files()):
2520 progress.increment()
2520 progress.increment()
2521 sub = wctx.sub(subpath)
2521 sub = wctx.sub(subpath)
2522 try:
2522 try:
2523 if sub.removefiles(
2523 if sub.removefiles(
2524 submatch,
2524 submatch,
2525 subprefix,
2525 subprefix,
2526 subuipathfn,
2526 subuipathfn,
2527 after,
2527 after,
2528 force,
2528 force,
2529 subrepos,
2529 subrepos,
2530 dryrun,
2530 dryrun,
2531 warnings,
2531 warnings,
2532 ):
2532 ):
2533 ret = 1
2533 ret = 1
2534 except error.LookupError:
2534 except error.LookupError:
2535 warnings.append(
2535 warnings.append(
2536 _(b"skipping missing subrepository: %s\n")
2536 _(b"skipping missing subrepository: %s\n")
2537 % uipathfn(subpath)
2537 % uipathfn(subpath)
2538 )
2538 )
2539 progress.complete()
2539 progress.complete()
2540
2540
2541 # warn about failure to delete explicit files/dirs
2541 # warn about failure to delete explicit files/dirs
2542 deleteddirs = pathutil.dirs(deleted)
2542 deleteddirs = pathutil.dirs(deleted)
2543 files = m.files()
2543 files = m.files()
2544 progress = ui.makeprogress(
2544 progress = ui.makeprogress(
2545 _(b'deleting'), total=len(files), unit=_(b'files')
2545 _(b'deleting'), total=len(files), unit=_(b'files')
2546 )
2546 )
2547 for f in files:
2547 for f in files:
2548
2548
2549 def insubrepo():
2549 def insubrepo():
2550 for subpath in wctx.substate:
2550 for subpath in wctx.substate:
2551 if f.startswith(subpath + b'/'):
2551 if f.startswith(subpath + b'/'):
2552 return True
2552 return True
2553 return False
2553 return False
2554
2554
2555 progress.increment()
2555 progress.increment()
2556 isdir = f in deleteddirs or wctx.hasdir(f)
2556 isdir = f in deleteddirs or wctx.hasdir(f)
2557 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2557 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2558 continue
2558 continue
2559
2559
2560 if repo.wvfs.exists(f):
2560 if repo.wvfs.exists(f):
2561 if repo.wvfs.isdir(f):
2561 if repo.wvfs.isdir(f):
2562 warnings.append(
2562 warnings.append(
2563 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2563 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2564 )
2564 )
2565 else:
2565 else:
2566 warnings.append(
2566 warnings.append(
2567 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2567 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2568 )
2568 )
2569 # missing files will generate a warning elsewhere
2569 # missing files will generate a warning elsewhere
2570 ret = 1
2570 ret = 1
2571 progress.complete()
2571 progress.complete()
2572
2572
2573 if force:
2573 if force:
2574 list = modified + deleted + clean + added
2574 list = modified + deleted + clean + added
2575 elif after:
2575 elif after:
2576 list = deleted
2576 list = deleted
2577 remaining = modified + added + clean
2577 remaining = modified + added + clean
2578 progress = ui.makeprogress(
2578 progress = ui.makeprogress(
2579 _(b'skipping'), total=len(remaining), unit=_(b'files')
2579 _(b'skipping'), total=len(remaining), unit=_(b'files')
2580 )
2580 )
2581 for f in remaining:
2581 for f in remaining:
2582 progress.increment()
2582 progress.increment()
2583 if ui.verbose or (f in files):
2583 if ui.verbose or (f in files):
2584 warnings.append(
2584 warnings.append(
2585 _(b'not removing %s: file still exists\n') % uipathfn(f)
2585 _(b'not removing %s: file still exists\n') % uipathfn(f)
2586 )
2586 )
2587 ret = 1
2587 ret = 1
2588 progress.complete()
2588 progress.complete()
2589 else:
2589 else:
2590 list = deleted + clean
2590 list = deleted + clean
2591 progress = ui.makeprogress(
2591 progress = ui.makeprogress(
2592 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2592 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2593 )
2593 )
2594 for f in modified:
2594 for f in modified:
2595 progress.increment()
2595 progress.increment()
2596 warnings.append(
2596 warnings.append(
2597 _(
2597 _(
2598 b'not removing %s: file is modified (use -f'
2598 b'not removing %s: file is modified (use -f'
2599 b' to force removal)\n'
2599 b' to force removal)\n'
2600 )
2600 )
2601 % uipathfn(f)
2601 % uipathfn(f)
2602 )
2602 )
2603 ret = 1
2603 ret = 1
2604 for f in added:
2604 for f in added:
2605 progress.increment()
2605 progress.increment()
2606 warnings.append(
2606 warnings.append(
2607 _(
2607 _(
2608 b"not removing %s: file has been marked for add"
2608 b"not removing %s: file has been marked for add"
2609 b" (use 'hg forget' to undo add)\n"
2609 b" (use 'hg forget' to undo add)\n"
2610 )
2610 )
2611 % uipathfn(f)
2611 % uipathfn(f)
2612 )
2612 )
2613 ret = 1
2613 ret = 1
2614 progress.complete()
2614 progress.complete()
2615
2615
2616 list = sorted(list)
2616 list = sorted(list)
2617 progress = ui.makeprogress(
2617 progress = ui.makeprogress(
2618 _(b'deleting'), total=len(list), unit=_(b'files')
2618 _(b'deleting'), total=len(list), unit=_(b'files')
2619 )
2619 )
2620 for f in list:
2620 for f in list:
2621 if ui.verbose or not m.exact(f):
2621 if ui.verbose or not m.exact(f):
2622 progress.increment()
2622 progress.increment()
2623 ui.status(
2623 ui.status(
2624 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2624 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2625 )
2625 )
2626 progress.complete()
2626 progress.complete()
2627
2627
2628 if not dryrun:
2628 if not dryrun:
2629 with repo.wlock():
2629 with repo.wlock():
2630 if not after:
2630 if not after:
2631 for f in list:
2631 for f in list:
2632 if f in added:
2632 if f in added:
2633 continue # we never unlink added files on remove
2633 continue # we never unlink added files on remove
2634 rmdir = repo.ui.configbool(
2634 rmdir = repo.ui.configbool(
2635 b'experimental', b'removeemptydirs'
2635 b'experimental', b'removeemptydirs'
2636 )
2636 )
2637 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2637 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2638 repo[None].forget(list)
2638 repo[None].forget(list)
2639
2639
2640 if warn:
2640 if warn:
2641 for warning in warnings:
2641 for warning in warnings:
2642 ui.warn(warning)
2642 ui.warn(warning)
2643
2643
2644 return ret
2644 return ret
2645
2645
2646
2646
2647 def _catfmtneedsdata(fm):
2647 def _catfmtneedsdata(fm):
2648 return not fm.datahint() or b'data' in fm.datahint()
2648 return not fm.datahint() or b'data' in fm.datahint()
2649
2649
2650
2650
2651 def _updatecatformatter(fm, ctx, matcher, path, decode):
2651 def _updatecatformatter(fm, ctx, matcher, path, decode):
2652 """Hook for adding data to the formatter used by ``hg cat``.
2652 """Hook for adding data to the formatter used by ``hg cat``.
2653
2653
2654 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2654 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2655 this method first."""
2655 this method first."""
2656
2656
2657 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2657 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2658 # wasn't requested.
2658 # wasn't requested.
2659 data = b''
2659 data = b''
2660 if _catfmtneedsdata(fm):
2660 if _catfmtneedsdata(fm):
2661 data = ctx[path].data()
2661 data = ctx[path].data()
2662 if decode:
2662 if decode:
2663 data = ctx.repo().wwritedata(path, data)
2663 data = ctx.repo().wwritedata(path, data)
2664 fm.startitem()
2664 fm.startitem()
2665 fm.context(ctx=ctx)
2665 fm.context(ctx=ctx)
2666 fm.write(b'data', b'%s', data)
2666 fm.write(b'data', b'%s', data)
2667 fm.data(path=path)
2667 fm.data(path=path)
2668
2668
2669
2669
2670 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2670 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2671 err = 1
2671 err = 1
2672 opts = pycompat.byteskwargs(opts)
2672 opts = pycompat.byteskwargs(opts)
2673
2673
2674 def write(path):
2674 def write(path):
2675 filename = None
2675 filename = None
2676 if fntemplate:
2676 if fntemplate:
2677 filename = makefilename(
2677 filename = makefilename(
2678 ctx, fntemplate, pathname=os.path.join(prefix, path)
2678 ctx, fntemplate, pathname=os.path.join(prefix, path)
2679 )
2679 )
2680 # attempt to create the directory if it does not already exist
2680 # attempt to create the directory if it does not already exist
2681 try:
2681 try:
2682 os.makedirs(os.path.dirname(filename))
2682 os.makedirs(os.path.dirname(filename))
2683 except OSError:
2683 except OSError:
2684 pass
2684 pass
2685 with formatter.maybereopen(basefm, filename) as fm:
2685 with formatter.maybereopen(basefm, filename) as fm:
2686 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2686 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2687
2687
2688 # Automation often uses hg cat on single files, so special case it
2688 # Automation often uses hg cat on single files, so special case it
2689 # for performance to avoid the cost of parsing the manifest.
2689 # for performance to avoid the cost of parsing the manifest.
2690 if len(matcher.files()) == 1 and not matcher.anypats():
2690 if len(matcher.files()) == 1 and not matcher.anypats():
2691 file = matcher.files()[0]
2691 file = matcher.files()[0]
2692 mfl = repo.manifestlog
2692 mfl = repo.manifestlog
2693 mfnode = ctx.manifestnode()
2693 mfnode = ctx.manifestnode()
2694 try:
2694 try:
2695 if mfnode and mfl[mfnode].find(file)[0]:
2695 if mfnode and mfl[mfnode].find(file)[0]:
2696 if _catfmtneedsdata(basefm):
2696 if _catfmtneedsdata(basefm):
2697 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2697 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2698 write(file)
2698 write(file)
2699 return 0
2699 return 0
2700 except KeyError:
2700 except KeyError:
2701 pass
2701 pass
2702
2702
2703 if _catfmtneedsdata(basefm):
2703 if _catfmtneedsdata(basefm):
2704 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2704 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2705
2705
2706 for abs in ctx.walk(matcher):
2706 for abs in ctx.walk(matcher):
2707 write(abs)
2707 write(abs)
2708 err = 0
2708 err = 0
2709
2709
2710 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2710 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2711 for subpath in sorted(ctx.substate):
2711 for subpath in sorted(ctx.substate):
2712 sub = ctx.sub(subpath)
2712 sub = ctx.sub(subpath)
2713 try:
2713 try:
2714 submatch = matchmod.subdirmatcher(subpath, matcher)
2714 submatch = matchmod.subdirmatcher(subpath, matcher)
2715 subprefix = os.path.join(prefix, subpath)
2715 subprefix = os.path.join(prefix, subpath)
2716 if not sub.cat(
2716 if not sub.cat(
2717 submatch,
2717 submatch,
2718 basefm,
2718 basefm,
2719 fntemplate,
2719 fntemplate,
2720 subprefix,
2720 subprefix,
2721 **pycompat.strkwargs(opts)
2721 **pycompat.strkwargs(opts)
2722 ):
2722 ):
2723 err = 0
2723 err = 0
2724 except error.RepoLookupError:
2724 except error.RepoLookupError:
2725 ui.status(
2725 ui.status(
2726 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2726 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2727 )
2727 )
2728
2728
2729 return err
2729 return err
2730
2730
2731
2731
2732 def commit(ui, repo, commitfunc, pats, opts):
2732 def commit(ui, repo, commitfunc, pats, opts):
2733 '''commit the specified files or all outstanding changes'''
2733 '''commit the specified files or all outstanding changes'''
2734 date = opts.get(b'date')
2734 date = opts.get(b'date')
2735 if date:
2735 if date:
2736 opts[b'date'] = dateutil.parsedate(date)
2736 opts[b'date'] = dateutil.parsedate(date)
2737 message = logmessage(ui, opts)
2737 message = logmessage(ui, opts)
2738 matcher = scmutil.match(repo[None], pats, opts)
2738 matcher = scmutil.match(repo[None], pats, opts)
2739
2739
2740 dsguard = None
2740 dsguard = None
2741 # extract addremove carefully -- this function can be called from a command
2741 # extract addremove carefully -- this function can be called from a command
2742 # that doesn't support addremove
2742 # that doesn't support addremove
2743 if opts.get(b'addremove'):
2743 if opts.get(b'addremove'):
2744 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2744 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2745 with dsguard or util.nullcontextmanager():
2745 with dsguard or util.nullcontextmanager():
2746 if dsguard:
2746 if dsguard:
2747 relative = scmutil.anypats(pats, opts)
2747 relative = scmutil.anypats(pats, opts)
2748 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2748 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2749 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2749 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2750 raise error.Abort(
2750 raise error.Abort(
2751 _(b"failed to mark all new/missing files as added/removed")
2751 _(b"failed to mark all new/missing files as added/removed")
2752 )
2752 )
2753
2753
2754 return commitfunc(ui, repo, message, matcher, opts)
2754 return commitfunc(ui, repo, message, matcher, opts)
2755
2755
2756
2756
2757 def samefile(f, ctx1, ctx2):
2757 def samefile(f, ctx1, ctx2):
2758 if f in ctx1.manifest():
2758 if f in ctx1.manifest():
2759 a = ctx1.filectx(f)
2759 a = ctx1.filectx(f)
2760 if f in ctx2.manifest():
2760 if f in ctx2.manifest():
2761 b = ctx2.filectx(f)
2761 b = ctx2.filectx(f)
2762 return not a.cmp(b) and a.flags() == b.flags()
2762 return not a.cmp(b) and a.flags() == b.flags()
2763 else:
2763 else:
2764 return False
2764 return False
2765 else:
2765 else:
2766 return f not in ctx2.manifest()
2766 return f not in ctx2.manifest()
2767
2767
2768
2768
2769 def amend(ui, repo, old, extra, pats, opts):
2769 def amend(ui, repo, old, extra, pats, opts):
2770 # avoid cycle context -> subrepo -> cmdutil
2770 # avoid cycle context -> subrepo -> cmdutil
2771 from . import context
2771 from . import context
2772
2772
2773 # amend will reuse the existing user if not specified, but the obsolete
2773 # amend will reuse the existing user if not specified, but the obsolete
2774 # marker creation requires that the current user's name is specified.
2774 # marker creation requires that the current user's name is specified.
2775 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2775 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2776 ui.username() # raise exception if username not set
2776 ui.username() # raise exception if username not set
2777
2777
2778 ui.note(_(b'amending changeset %s\n') % old)
2778 ui.note(_(b'amending changeset %s\n') % old)
2779 base = old.p1()
2779 base = old.p1()
2780
2780
2781 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2781 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2782 # Participating changesets:
2782 # Participating changesets:
2783 #
2783 #
2784 # wctx o - workingctx that contains changes from working copy
2784 # wctx o - workingctx that contains changes from working copy
2785 # | to go into amending commit
2785 # | to go into amending commit
2786 # |
2786 # |
2787 # old o - changeset to amend
2787 # old o - changeset to amend
2788 # |
2788 # |
2789 # base o - first parent of the changeset to amend
2789 # base o - first parent of the changeset to amend
2790 wctx = repo[None]
2790 wctx = repo[None]
2791
2791
2792 # Copy to avoid mutating input
2792 # Copy to avoid mutating input
2793 extra = extra.copy()
2793 extra = extra.copy()
2794 # Update extra dict from amended commit (e.g. to preserve graft
2794 # Update extra dict from amended commit (e.g. to preserve graft
2795 # source)
2795 # source)
2796 extra.update(old.extra())
2796 extra.update(old.extra())
2797
2797
2798 # Also update it from the from the wctx
2798 # Also update it from the from the wctx
2799 extra.update(wctx.extra())
2799 extra.update(wctx.extra())
2800
2800
2801 # date-only change should be ignored?
2801 # date-only change should be ignored?
2802 datemaydiffer = resolvecommitoptions(ui, opts)
2802 datemaydiffer = resolvecommitoptions(ui, opts)
2803
2803
2804 date = old.date()
2804 date = old.date()
2805 if opts.get(b'date'):
2805 if opts.get(b'date'):
2806 date = dateutil.parsedate(opts.get(b'date'))
2806 date = dateutil.parsedate(opts.get(b'date'))
2807 user = opts.get(b'user') or old.user()
2807 user = opts.get(b'user') or old.user()
2808
2808
2809 if len(old.parents()) > 1:
2809 if len(old.parents()) > 1:
2810 # ctx.files() isn't reliable for merges, so fall back to the
2810 # ctx.files() isn't reliable for merges, so fall back to the
2811 # slower repo.status() method
2811 # slower repo.status() method
2812 st = base.status(old)
2812 st = base.status(old)
2813 files = set(st.modified) | set(st.added) | set(st.removed)
2813 files = set(st.modified) | set(st.added) | set(st.removed)
2814 else:
2814 else:
2815 files = set(old.files())
2815 files = set(old.files())
2816
2816
2817 # add/remove the files to the working copy if the "addremove" option
2817 # add/remove the files to the working copy if the "addremove" option
2818 # was specified.
2818 # was specified.
2819 matcher = scmutil.match(wctx, pats, opts)
2819 matcher = scmutil.match(wctx, pats, opts)
2820 relative = scmutil.anypats(pats, opts)
2820 relative = scmutil.anypats(pats, opts)
2821 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2821 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2822 if opts.get(b'addremove') and scmutil.addremove(
2822 if opts.get(b'addremove') and scmutil.addremove(
2823 repo, matcher, b"", uipathfn, opts
2823 repo, matcher, b"", uipathfn, opts
2824 ):
2824 ):
2825 raise error.Abort(
2825 raise error.Abort(
2826 _(b"failed to mark all new/missing files as added/removed")
2826 _(b"failed to mark all new/missing files as added/removed")
2827 )
2827 )
2828
2828
2829 # Check subrepos. This depends on in-place wctx._status update in
2829 # Check subrepos. This depends on in-place wctx._status update in
2830 # subrepo.precommit(). To minimize the risk of this hack, we do
2830 # subrepo.precommit(). To minimize the risk of this hack, we do
2831 # nothing if .hgsub does not exist.
2831 # nothing if .hgsub does not exist.
2832 if b'.hgsub' in wctx or b'.hgsub' in old:
2832 if b'.hgsub' in wctx or b'.hgsub' in old:
2833 subs, commitsubs, newsubstate = subrepoutil.precommit(
2833 subs, commitsubs, newsubstate = subrepoutil.precommit(
2834 ui, wctx, wctx._status, matcher
2834 ui, wctx, wctx._status, matcher
2835 )
2835 )
2836 # amend should abort if commitsubrepos is enabled
2836 # amend should abort if commitsubrepos is enabled
2837 assert not commitsubs
2837 assert not commitsubs
2838 if subs:
2838 if subs:
2839 subrepoutil.writestate(repo, newsubstate)
2839 subrepoutil.writestate(repo, newsubstate)
2840
2840
2841 ms = mergestatemod.mergestate.read(repo)
2841 ms = mergestatemod.mergestate.read(repo)
2842 mergeutil.checkunresolved(ms)
2842 mergeutil.checkunresolved(ms)
2843
2843
2844 filestoamend = {f for f in wctx.files() if matcher(f)}
2844 filestoamend = {f for f in wctx.files() if matcher(f)}
2845
2845
2846 changes = len(filestoamend) > 0
2846 changes = len(filestoamend) > 0
2847 if changes:
2847 if changes:
2848 # Recompute copies (avoid recording a -> b -> a)
2848 # Recompute copies (avoid recording a -> b -> a)
2849 copied = copies.pathcopies(base, wctx, matcher)
2849 copied = copies.pathcopies(base, wctx, matcher)
2850 if old.p2:
2850 if old.p2:
2851 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2851 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2852
2852
2853 # Prune files which were reverted by the updates: if old
2853 # Prune files which were reverted by the updates: if old
2854 # introduced file X and the file was renamed in the working
2854 # introduced file X and the file was renamed in the working
2855 # copy, then those two files are the same and
2855 # copy, then those two files are the same and
2856 # we can discard X from our list of files. Likewise if X
2856 # we can discard X from our list of files. Likewise if X
2857 # was removed, it's no longer relevant. If X is missing (aka
2857 # was removed, it's no longer relevant. If X is missing (aka
2858 # deleted), old X must be preserved.
2858 # deleted), old X must be preserved.
2859 files.update(filestoamend)
2859 files.update(filestoamend)
2860 files = [
2860 files = [
2861 f
2861 f
2862 for f in files
2862 for f in files
2863 if (f not in filestoamend or not samefile(f, wctx, base))
2863 if (f not in filestoamend or not samefile(f, wctx, base))
2864 ]
2864 ]
2865
2865
2866 def filectxfn(repo, ctx_, path):
2866 def filectxfn(repo, ctx_, path):
2867 try:
2867 try:
2868 # If the file being considered is not amongst the files
2868 # If the file being considered is not amongst the files
2869 # to be amended, we should return the file context from the
2869 # to be amended, we should return the file context from the
2870 # old changeset. This avoids issues when only some files in
2870 # old changeset. This avoids issues when only some files in
2871 # the working copy are being amended but there are also
2871 # the working copy are being amended but there are also
2872 # changes to other files from the old changeset.
2872 # changes to other files from the old changeset.
2873 if path not in filestoamend:
2873 if path not in filestoamend:
2874 return old.filectx(path)
2874 return old.filectx(path)
2875
2875
2876 # Return None for removed files.
2876 # Return None for removed files.
2877 if path in wctx.removed():
2877 if path in wctx.removed():
2878 return None
2878 return None
2879
2879
2880 fctx = wctx[path]
2880 fctx = wctx[path]
2881 flags = fctx.flags()
2881 flags = fctx.flags()
2882 mctx = context.memfilectx(
2882 mctx = context.memfilectx(
2883 repo,
2883 repo,
2884 ctx_,
2884 ctx_,
2885 fctx.path(),
2885 fctx.path(),
2886 fctx.data(),
2886 fctx.data(),
2887 islink=b'l' in flags,
2887 islink=b'l' in flags,
2888 isexec=b'x' in flags,
2888 isexec=b'x' in flags,
2889 copysource=copied.get(path),
2889 copysource=copied.get(path),
2890 )
2890 )
2891 return mctx
2891 return mctx
2892 except KeyError:
2892 except KeyError:
2893 return None
2893 return None
2894
2894
2895 else:
2895 else:
2896 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2896 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2897
2897
2898 # Use version of files as in the old cset
2898 # Use version of files as in the old cset
2899 def filectxfn(repo, ctx_, path):
2899 def filectxfn(repo, ctx_, path):
2900 try:
2900 try:
2901 return old.filectx(path)
2901 return old.filectx(path)
2902 except KeyError:
2902 except KeyError:
2903 return None
2903 return None
2904
2904
2905 # See if we got a message from -m or -l, if not, open the editor with
2905 # See if we got a message from -m or -l, if not, open the editor with
2906 # the message of the changeset to amend.
2906 # the message of the changeset to amend.
2907 message = logmessage(ui, opts)
2907 message = logmessage(ui, opts)
2908
2908
2909 editform = mergeeditform(old, b'commit.amend')
2909 editform = mergeeditform(old, b'commit.amend')
2910
2910
2911 if not message:
2911 if not message:
2912 message = old.description()
2912 message = old.description()
2913 # Default if message isn't provided and --edit is not passed is to
2913 # Default if message isn't provided and --edit is not passed is to
2914 # invoke editor, but allow --no-edit. If somehow we don't have any
2914 # invoke editor, but allow --no-edit. If somehow we don't have any
2915 # description, let's always start the editor.
2915 # description, let's always start the editor.
2916 doedit = not message or opts.get(b'edit') in [True, None]
2916 doedit = not message or opts.get(b'edit') in [True, None]
2917 else:
2917 else:
2918 # Default if message is provided is to not invoke editor, but allow
2918 # Default if message is provided is to not invoke editor, but allow
2919 # --edit.
2919 # --edit.
2920 doedit = opts.get(b'edit') is True
2920 doedit = opts.get(b'edit') is True
2921 editor = getcommiteditor(edit=doedit, editform=editform)
2921 editor = getcommiteditor(edit=doedit, editform=editform)
2922
2922
2923 pureextra = extra.copy()
2923 pureextra = extra.copy()
2924 extra[b'amend_source'] = old.hex()
2924 extra[b'amend_source'] = old.hex()
2925
2925
2926 new = context.memctx(
2926 new = context.memctx(
2927 repo,
2927 repo,
2928 parents=[base.node(), old.p2().node()],
2928 parents=[base.node(), old.p2().node()],
2929 text=message,
2929 text=message,
2930 files=files,
2930 files=files,
2931 filectxfn=filectxfn,
2931 filectxfn=filectxfn,
2932 user=user,
2932 user=user,
2933 date=date,
2933 date=date,
2934 extra=extra,
2934 extra=extra,
2935 editor=editor,
2935 editor=editor,
2936 )
2936 )
2937
2937
2938 newdesc = changelog.stripdesc(new.description())
2938 newdesc = changelog.stripdesc(new.description())
2939 if (
2939 if (
2940 (not changes)
2940 (not changes)
2941 and newdesc == old.description()
2941 and newdesc == old.description()
2942 and user == old.user()
2942 and user == old.user()
2943 and (date == old.date() or datemaydiffer)
2943 and (date == old.date() or datemaydiffer)
2944 and pureextra == old.extra()
2944 and pureextra == old.extra()
2945 ):
2945 ):
2946 # nothing changed. continuing here would create a new node
2946 # nothing changed. continuing here would create a new node
2947 # anyway because of the amend_source noise.
2947 # anyway because of the amend_source noise.
2948 #
2948 #
2949 # This not what we expect from amend.
2949 # This not what we expect from amend.
2950 return old.node()
2950 return old.node()
2951
2951
2952 commitphase = None
2952 commitphase = None
2953 if opts.get(b'secret'):
2953 if opts.get(b'secret'):
2954 commitphase = phases.secret
2954 commitphase = phases.secret
2955 newid = repo.commitctx(new)
2955 newid = repo.commitctx(new)
2956 ms.reset()
2956 ms.reset()
2957
2957
2958 # Reroute the working copy parent to the new changeset
2958 # Reroute the working copy parent to the new changeset
2959 repo.setparents(newid, nullid)
2959 repo.setparents(newid, nullid)
2960 mapping = {old.node(): (newid,)}
2960 mapping = {old.node(): (newid,)}
2961 obsmetadata = None
2961 obsmetadata = None
2962 if opts.get(b'note'):
2962 if opts.get(b'note'):
2963 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
2963 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
2964 backup = ui.configbool(b'rewrite', b'backup-bundle')
2964 backup = ui.configbool(b'rewrite', b'backup-bundle')
2965 scmutil.cleanupnodes(
2965 scmutil.cleanupnodes(
2966 repo,
2966 repo,
2967 mapping,
2967 mapping,
2968 b'amend',
2968 b'amend',
2969 metadata=obsmetadata,
2969 metadata=obsmetadata,
2970 fixphase=True,
2970 fixphase=True,
2971 targetphase=commitphase,
2971 targetphase=commitphase,
2972 backup=backup,
2972 backup=backup,
2973 )
2973 )
2974
2974
2975 # Fixing the dirstate because localrepo.commitctx does not update
2975 # Fixing the dirstate because localrepo.commitctx does not update
2976 # it. This is rather convenient because we did not need to update
2976 # it. This is rather convenient because we did not need to update
2977 # the dirstate for all the files in the new commit which commitctx
2977 # the dirstate for all the files in the new commit which commitctx
2978 # could have done if it updated the dirstate. Now, we can
2978 # could have done if it updated the dirstate. Now, we can
2979 # selectively update the dirstate only for the amended files.
2979 # selectively update the dirstate only for the amended files.
2980 dirstate = repo.dirstate
2980 dirstate = repo.dirstate
2981
2981
2982 # Update the state of the files which were added and modified in the
2982 # Update the state of the files which were added and modified in the
2983 # amend to "normal" in the dirstate. We need to use "normallookup" since
2983 # amend to "normal" in the dirstate. We need to use "normallookup" since
2984 # the files may have changed since the command started; using "normal"
2984 # the files may have changed since the command started; using "normal"
2985 # would mark them as clean but with uncommitted contents.
2985 # would mark them as clean but with uncommitted contents.
2986 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2986 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2987 for f in normalfiles:
2987 for f in normalfiles:
2988 dirstate.normallookup(f)
2988 dirstate.normallookup(f)
2989
2989
2990 # Update the state of files which were removed in the amend
2990 # Update the state of files which were removed in the amend
2991 # to "removed" in the dirstate.
2991 # to "removed" in the dirstate.
2992 removedfiles = set(wctx.removed()) & filestoamend
2992 removedfiles = set(wctx.removed()) & filestoamend
2993 for f in removedfiles:
2993 for f in removedfiles:
2994 dirstate.drop(f)
2994 dirstate.drop(f)
2995
2995
2996 return newid
2996 return newid
2997
2997
2998
2998
2999 def commiteditor(repo, ctx, subs, editform=b''):
2999 def commiteditor(repo, ctx, subs, editform=b''):
3000 if ctx.description():
3000 if ctx.description():
3001 return ctx.description()
3001 return ctx.description()
3002 return commitforceeditor(
3002 return commitforceeditor(
3003 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3003 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3004 )
3004 )
3005
3005
3006
3006
3007 def commitforceeditor(
3007 def commitforceeditor(
3008 repo,
3008 repo,
3009 ctx,
3009 ctx,
3010 subs,
3010 subs,
3011 finishdesc=None,
3011 finishdesc=None,
3012 extramsg=None,
3012 extramsg=None,
3013 editform=b'',
3013 editform=b'',
3014 unchangedmessagedetection=False,
3014 unchangedmessagedetection=False,
3015 ):
3015 ):
3016 if not extramsg:
3016 if not extramsg:
3017 extramsg = _(b"Leave message empty to abort commit.")
3017 extramsg = _(b"Leave message empty to abort commit.")
3018
3018
3019 forms = [e for e in editform.split(b'.') if e]
3019 forms = [e for e in editform.split(b'.') if e]
3020 forms.insert(0, b'changeset')
3020 forms.insert(0, b'changeset')
3021 templatetext = None
3021 templatetext = None
3022 while forms:
3022 while forms:
3023 ref = b'.'.join(forms)
3023 ref = b'.'.join(forms)
3024 if repo.ui.config(b'committemplate', ref):
3024 if repo.ui.config(b'committemplate', ref):
3025 templatetext = committext = buildcommittemplate(
3025 templatetext = committext = buildcommittemplate(
3026 repo, ctx, subs, extramsg, ref
3026 repo, ctx, subs, extramsg, ref
3027 )
3027 )
3028 break
3028 break
3029 forms.pop()
3029 forms.pop()
3030 else:
3030 else:
3031 committext = buildcommittext(repo, ctx, subs, extramsg)
3031 committext = buildcommittext(repo, ctx, subs, extramsg)
3032
3032
3033 # run editor in the repository root
3033 # run editor in the repository root
3034 olddir = encoding.getcwd()
3034 olddir = encoding.getcwd()
3035 os.chdir(repo.root)
3035 os.chdir(repo.root)
3036
3036
3037 # make in-memory changes visible to external process
3037 # make in-memory changes visible to external process
3038 tr = repo.currenttransaction()
3038 tr = repo.currenttransaction()
3039 repo.dirstate.write(tr)
3039 repo.dirstate.write(tr)
3040 pending = tr and tr.writepending() and repo.root
3040 pending = tr and tr.writepending() and repo.root
3041
3041
3042 editortext = repo.ui.edit(
3042 editortext = repo.ui.edit(
3043 committext,
3043 committext,
3044 ctx.user(),
3044 ctx.user(),
3045 ctx.extra(),
3045 ctx.extra(),
3046 editform=editform,
3046 editform=editform,
3047 pending=pending,
3047 pending=pending,
3048 repopath=repo.path,
3048 repopath=repo.path,
3049 action=b'commit',
3049 action=b'commit',
3050 )
3050 )
3051 text = editortext
3051 text = editortext
3052
3052
3053 # strip away anything below this special string (used for editors that want
3053 # strip away anything below this special string (used for editors that want
3054 # to display the diff)
3054 # to display the diff)
3055 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3055 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3056 if stripbelow:
3056 if stripbelow:
3057 text = text[: stripbelow.start()]
3057 text = text[: stripbelow.start()]
3058
3058
3059 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3059 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3060 os.chdir(olddir)
3060 os.chdir(olddir)
3061
3061
3062 if finishdesc:
3062 if finishdesc:
3063 text = finishdesc(text)
3063 text = finishdesc(text)
3064 if not text.strip():
3064 if not text.strip():
3065 raise error.InputError(_(b"empty commit message"))
3065 raise error.InputError(_(b"empty commit message"))
3066 if unchangedmessagedetection and editortext == templatetext:
3066 if unchangedmessagedetection and editortext == templatetext:
3067 raise error.InputError(_(b"commit message unchanged"))
3067 raise error.InputError(_(b"commit message unchanged"))
3068
3068
3069 return text
3069 return text
3070
3070
3071
3071
3072 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3072 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3073 ui = repo.ui
3073 ui = repo.ui
3074 spec = formatter.reference_templatespec(ref)
3074 spec = formatter.reference_templatespec(ref)
3075 t = logcmdutil.changesettemplater(ui, repo, spec)
3075 t = logcmdutil.changesettemplater(ui, repo, spec)
3076 t.t.cache.update(
3076 t.t.cache.update(
3077 (k, templater.unquotestring(v))
3077 (k, templater.unquotestring(v))
3078 for k, v in repo.ui.configitems(b'committemplate')
3078 for k, v in repo.ui.configitems(b'committemplate')
3079 )
3079 )
3080
3080
3081 if not extramsg:
3081 if not extramsg:
3082 extramsg = b'' # ensure that extramsg is string
3082 extramsg = b'' # ensure that extramsg is string
3083
3083
3084 ui.pushbuffer()
3084 ui.pushbuffer()
3085 t.show(ctx, extramsg=extramsg)
3085 t.show(ctx, extramsg=extramsg)
3086 return ui.popbuffer()
3086 return ui.popbuffer()
3087
3087
3088
3088
3089 def hgprefix(msg):
3089 def hgprefix(msg):
3090 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3090 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3091
3091
3092
3092
3093 def buildcommittext(repo, ctx, subs, extramsg):
3093 def buildcommittext(repo, ctx, subs, extramsg):
3094 edittext = []
3094 edittext = []
3095 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3095 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3096 if ctx.description():
3096 if ctx.description():
3097 edittext.append(ctx.description())
3097 edittext.append(ctx.description())
3098 edittext.append(b"")
3098 edittext.append(b"")
3099 edittext.append(b"") # Empty line between message and comments.
3099 edittext.append(b"") # Empty line between message and comments.
3100 edittext.append(
3100 edittext.append(
3101 hgprefix(
3101 hgprefix(
3102 _(
3102 _(
3103 b"Enter commit message."
3103 b"Enter commit message."
3104 b" Lines beginning with 'HG:' are removed."
3104 b" Lines beginning with 'HG:' are removed."
3105 )
3105 )
3106 )
3106 )
3107 )
3107 )
3108 edittext.append(hgprefix(extramsg))
3108 edittext.append(hgprefix(extramsg))
3109 edittext.append(b"HG: --")
3109 edittext.append(b"HG: --")
3110 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3110 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3111 if ctx.p2():
3111 if ctx.p2():
3112 edittext.append(hgprefix(_(b"branch merge")))
3112 edittext.append(hgprefix(_(b"branch merge")))
3113 if ctx.branch():
3113 if ctx.branch():
3114 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3114 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3115 if bookmarks.isactivewdirparent(repo):
3115 if bookmarks.isactivewdirparent(repo):
3116 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3116 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3117 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3117 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3118 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3118 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3119 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3119 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3120 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3120 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3121 if not added and not modified and not removed:
3121 if not added and not modified and not removed:
3122 edittext.append(hgprefix(_(b"no files changed")))
3122 edittext.append(hgprefix(_(b"no files changed")))
3123 edittext.append(b"")
3123 edittext.append(b"")
3124
3124
3125 return b"\n".join(edittext)
3125 return b"\n".join(edittext)
3126
3126
3127
3127
3128 def commitstatus(repo, node, branch, bheads=None, tip=None, opts=None):
3128 def commitstatus(repo, node, branch, bheads=None, tip=None, opts=None):
3129 if opts is None:
3129 if opts is None:
3130 opts = {}
3130 opts = {}
3131 ctx = repo[node]
3131 ctx = repo[node]
3132 parents = ctx.parents()
3132 parents = ctx.parents()
3133
3133
3134 if tip is not None and repo.changelog.tip() == tip:
3134 if tip is not None and repo.changelog.tip() == tip:
3135 # avoid reporting something like "committed new head" when
3135 # avoid reporting something like "committed new head" when
3136 # recommitting old changesets, and issue a helpful warning
3136 # recommitting old changesets, and issue a helpful warning
3137 # for most instances
3137 # for most instances
3138 repo.ui.warn(_("warning: commit already existed in the repository!\n"))
3138 repo.ui.warn(_("warning: commit already existed in the repository!\n"))
3139 elif (
3139 elif (
3140 not opts.get(b'amend')
3140 not opts.get(b'amend')
3141 and bheads
3141 and bheads
3142 and node not in bheads
3142 and node not in bheads
3143 and not any(
3143 and not any(
3144 p.node() in bheads and p.branch() == branch for p in parents
3144 p.node() in bheads and p.branch() == branch for p in parents
3145 )
3145 )
3146 ):
3146 ):
3147 repo.ui.status(_(b'created new head\n'))
3147 repo.ui.status(_(b'created new head\n'))
3148 # The message is not printed for initial roots. For the other
3148 # The message is not printed for initial roots. For the other
3149 # changesets, it is printed in the following situations:
3149 # changesets, it is printed in the following situations:
3150 #
3150 #
3151 # Par column: for the 2 parents with ...
3151 # Par column: for the 2 parents with ...
3152 # N: null or no parent
3152 # N: null or no parent
3153 # B: parent is on another named branch
3153 # B: parent is on another named branch
3154 # C: parent is a regular non head changeset
3154 # C: parent is a regular non head changeset
3155 # H: parent was a branch head of the current branch
3155 # H: parent was a branch head of the current branch
3156 # Msg column: whether we print "created new head" message
3156 # Msg column: whether we print "created new head" message
3157 # In the following, it is assumed that there already exists some
3157 # In the following, it is assumed that there already exists some
3158 # initial branch heads of the current branch, otherwise nothing is
3158 # initial branch heads of the current branch, otherwise nothing is
3159 # printed anyway.
3159 # printed anyway.
3160 #
3160 #
3161 # Par Msg Comment
3161 # Par Msg Comment
3162 # N N y additional topo root
3162 # N N y additional topo root
3163 #
3163 #
3164 # B N y additional branch root
3164 # B N y additional branch root
3165 # C N y additional topo head
3165 # C N y additional topo head
3166 # H N n usual case
3166 # H N n usual case
3167 #
3167 #
3168 # B B y weird additional branch root
3168 # B B y weird additional branch root
3169 # C B y branch merge
3169 # C B y branch merge
3170 # H B n merge with named branch
3170 # H B n merge with named branch
3171 #
3171 #
3172 # C C y additional head from merge
3172 # C C y additional head from merge
3173 # C H n merge with a head
3173 # C H n merge with a head
3174 #
3174 #
3175 # H H n head merge: head count decreases
3175 # H H n head merge: head count decreases
3176
3176
3177 if not opts.get(b'close_branch'):
3177 if not opts.get(b'close_branch'):
3178 for r in parents:
3178 for r in parents:
3179 if r.closesbranch() and r.branch() == branch:
3179 if r.closesbranch() and r.branch() == branch:
3180 repo.ui.status(
3180 repo.ui.status(
3181 _(b'reopening closed branch head %d\n') % r.rev()
3181 _(b'reopening closed branch head %d\n') % r.rev()
3182 )
3182 )
3183
3183
3184 if repo.ui.debugflag:
3184 if repo.ui.debugflag:
3185 repo.ui.write(
3185 repo.ui.write(
3186 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3186 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3187 )
3187 )
3188 elif repo.ui.verbose:
3188 elif repo.ui.verbose:
3189 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3189 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3190
3190
3191
3191
3192 def postcommitstatus(repo, pats, opts):
3192 def postcommitstatus(repo, pats, opts):
3193 return repo.status(match=scmutil.match(repo[None], pats, opts))
3193 return repo.status(match=scmutil.match(repo[None], pats, opts))
3194
3194
3195
3195
3196 def revert(ui, repo, ctx, *pats, **opts):
3196 def revert(ui, repo, ctx, *pats, **opts):
3197 opts = pycompat.byteskwargs(opts)
3197 opts = pycompat.byteskwargs(opts)
3198 parent, p2 = repo.dirstate.parents()
3198 parent, p2 = repo.dirstate.parents()
3199 node = ctx.node()
3199 node = ctx.node()
3200
3200
3201 mf = ctx.manifest()
3201 mf = ctx.manifest()
3202 if node == p2:
3202 if node == p2:
3203 parent = p2
3203 parent = p2
3204
3204
3205 # need all matching names in dirstate and manifest of target rev,
3205 # need all matching names in dirstate and manifest of target rev,
3206 # so have to walk both. do not print errors if files exist in one
3206 # so have to walk both. do not print errors if files exist in one
3207 # but not other. in both cases, filesets should be evaluated against
3207 # but not other. in both cases, filesets should be evaluated against
3208 # workingctx to get consistent result (issue4497). this means 'set:**'
3208 # workingctx to get consistent result (issue4497). this means 'set:**'
3209 # cannot be used to select missing files from target rev.
3209 # cannot be used to select missing files from target rev.
3210
3210
3211 # `names` is a mapping for all elements in working copy and target revision
3211 # `names` is a mapping for all elements in working copy and target revision
3212 # The mapping is in the form:
3212 # The mapping is in the form:
3213 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3213 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3214 names = {}
3214 names = {}
3215 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3215 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3216
3216
3217 with repo.wlock():
3217 with repo.wlock():
3218 ## filling of the `names` mapping
3218 ## filling of the `names` mapping
3219 # walk dirstate to fill `names`
3219 # walk dirstate to fill `names`
3220
3220
3221 interactive = opts.get(b'interactive', False)
3221 interactive = opts.get(b'interactive', False)
3222 wctx = repo[None]
3222 wctx = repo[None]
3223 m = scmutil.match(wctx, pats, opts)
3223 m = scmutil.match(wctx, pats, opts)
3224
3224
3225 # we'll need this later
3225 # we'll need this later
3226 targetsubs = sorted(s for s in wctx.substate if m(s))
3226 targetsubs = sorted(s for s in wctx.substate if m(s))
3227
3227
3228 if not m.always():
3228 if not m.always():
3229 matcher = matchmod.badmatch(m, lambda x, y: False)
3229 matcher = matchmod.badmatch(m, lambda x, y: False)
3230 for abs in wctx.walk(matcher):
3230 for abs in wctx.walk(matcher):
3231 names[abs] = m.exact(abs)
3231 names[abs] = m.exact(abs)
3232
3232
3233 # walk target manifest to fill `names`
3233 # walk target manifest to fill `names`
3234
3234
3235 def badfn(path, msg):
3235 def badfn(path, msg):
3236 if path in names:
3236 if path in names:
3237 return
3237 return
3238 if path in ctx.substate:
3238 if path in ctx.substate:
3239 return
3239 return
3240 path_ = path + b'/'
3240 path_ = path + b'/'
3241 for f in names:
3241 for f in names:
3242 if f.startswith(path_):
3242 if f.startswith(path_):
3243 return
3243 return
3244 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3244 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3245
3245
3246 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3246 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3247 if abs not in names:
3247 if abs not in names:
3248 names[abs] = m.exact(abs)
3248 names[abs] = m.exact(abs)
3249
3249
3250 # Find status of all file in `names`.
3250 # Find status of all file in `names`.
3251 m = scmutil.matchfiles(repo, names)
3251 m = scmutil.matchfiles(repo, names)
3252
3252
3253 changes = repo.status(
3253 changes = repo.status(
3254 node1=node, match=m, unknown=True, ignored=True, clean=True
3254 node1=node, match=m, unknown=True, ignored=True, clean=True
3255 )
3255 )
3256 else:
3256 else:
3257 changes = repo.status(node1=node, match=m)
3257 changes = repo.status(node1=node, match=m)
3258 for kind in changes:
3258 for kind in changes:
3259 for abs in kind:
3259 for abs in kind:
3260 names[abs] = m.exact(abs)
3260 names[abs] = m.exact(abs)
3261
3261
3262 m = scmutil.matchfiles(repo, names)
3262 m = scmutil.matchfiles(repo, names)
3263
3263
3264 modified = set(changes.modified)
3264 modified = set(changes.modified)
3265 added = set(changes.added)
3265 added = set(changes.added)
3266 removed = set(changes.removed)
3266 removed = set(changes.removed)
3267 _deleted = set(changes.deleted)
3267 _deleted = set(changes.deleted)
3268 unknown = set(changes.unknown)
3268 unknown = set(changes.unknown)
3269 unknown.update(changes.ignored)
3269 unknown.update(changes.ignored)
3270 clean = set(changes.clean)
3270 clean = set(changes.clean)
3271 modadded = set()
3271 modadded = set()
3272
3272
3273 # We need to account for the state of the file in the dirstate,
3273 # We need to account for the state of the file in the dirstate,
3274 # even when we revert against something else than parent. This will
3274 # even when we revert against something else than parent. This will
3275 # slightly alter the behavior of revert (doing back up or not, delete
3275 # slightly alter the behavior of revert (doing back up or not, delete
3276 # or just forget etc).
3276 # or just forget etc).
3277 if parent == node:
3277 if parent == node:
3278 dsmodified = modified
3278 dsmodified = modified
3279 dsadded = added
3279 dsadded = added
3280 dsremoved = removed
3280 dsremoved = removed
3281 # store all local modifications, useful later for rename detection
3281 # store all local modifications, useful later for rename detection
3282 localchanges = dsmodified | dsadded
3282 localchanges = dsmodified | dsadded
3283 modified, added, removed = set(), set(), set()
3283 modified, added, removed = set(), set(), set()
3284 else:
3284 else:
3285 changes = repo.status(node1=parent, match=m)
3285 changes = repo.status(node1=parent, match=m)
3286 dsmodified = set(changes.modified)
3286 dsmodified = set(changes.modified)
3287 dsadded = set(changes.added)
3287 dsadded = set(changes.added)
3288 dsremoved = set(changes.removed)
3288 dsremoved = set(changes.removed)
3289 # store all local modifications, useful later for rename detection
3289 # store all local modifications, useful later for rename detection
3290 localchanges = dsmodified | dsadded
3290 localchanges = dsmodified | dsadded
3291
3291
3292 # only take into account for removes between wc and target
3292 # only take into account for removes between wc and target
3293 clean |= dsremoved - removed
3293 clean |= dsremoved - removed
3294 dsremoved &= removed
3294 dsremoved &= removed
3295 # distinct between dirstate remove and other
3295 # distinct between dirstate remove and other
3296 removed -= dsremoved
3296 removed -= dsremoved
3297
3297
3298 modadded = added & dsmodified
3298 modadded = added & dsmodified
3299 added -= modadded
3299 added -= modadded
3300
3300
3301 # tell newly modified apart.
3301 # tell newly modified apart.
3302 dsmodified &= modified
3302 dsmodified &= modified
3303 dsmodified |= modified & dsadded # dirstate added may need backup
3303 dsmodified |= modified & dsadded # dirstate added may need backup
3304 modified -= dsmodified
3304 modified -= dsmodified
3305
3305
3306 # We need to wait for some post-processing to update this set
3306 # We need to wait for some post-processing to update this set
3307 # before making the distinction. The dirstate will be used for
3307 # before making the distinction. The dirstate will be used for
3308 # that purpose.
3308 # that purpose.
3309 dsadded = added
3309 dsadded = added
3310
3310
3311 # in case of merge, files that are actually added can be reported as
3311 # in case of merge, files that are actually added can be reported as
3312 # modified, we need to post process the result
3312 # modified, we need to post process the result
3313 if p2 != nullid:
3313 if p2 != nullid:
3314 mergeadd = set(dsmodified)
3314 mergeadd = set(dsmodified)
3315 for path in dsmodified:
3315 for path in dsmodified:
3316 if path in mf:
3316 if path in mf:
3317 mergeadd.remove(path)
3317 mergeadd.remove(path)
3318 dsadded |= mergeadd
3318 dsadded |= mergeadd
3319 dsmodified -= mergeadd
3319 dsmodified -= mergeadd
3320
3320
3321 # if f is a rename, update `names` to also revert the source
3321 # if f is a rename, update `names` to also revert the source
3322 for f in localchanges:
3322 for f in localchanges:
3323 src = repo.dirstate.copied(f)
3323 src = repo.dirstate.copied(f)
3324 # XXX should we check for rename down to target node?
3324 # XXX should we check for rename down to target node?
3325 if src and src not in names and repo.dirstate[src] == b'r':
3325 if src and src not in names and repo.dirstate[src] == b'r':
3326 dsremoved.add(src)
3326 dsremoved.add(src)
3327 names[src] = True
3327 names[src] = True
3328
3328
3329 # determine the exact nature of the deleted changesets
3329 # determine the exact nature of the deleted changesets
3330 deladded = set(_deleted)
3330 deladded = set(_deleted)
3331 for path in _deleted:
3331 for path in _deleted:
3332 if path in mf:
3332 if path in mf:
3333 deladded.remove(path)
3333 deladded.remove(path)
3334 deleted = _deleted - deladded
3334 deleted = _deleted - deladded
3335
3335
3336 # distinguish between file to forget and the other
3336 # distinguish between file to forget and the other
3337 added = set()
3337 added = set()
3338 for abs in dsadded:
3338 for abs in dsadded:
3339 if repo.dirstate[abs] != b'a':
3339 if repo.dirstate[abs] != b'a':
3340 added.add(abs)
3340 added.add(abs)
3341 dsadded -= added
3341 dsadded -= added
3342
3342
3343 for abs in deladded:
3343 for abs in deladded:
3344 if repo.dirstate[abs] == b'a':
3344 if repo.dirstate[abs] == b'a':
3345 dsadded.add(abs)
3345 dsadded.add(abs)
3346 deladded -= dsadded
3346 deladded -= dsadded
3347
3347
3348 # For files marked as removed, we check if an unknown file is present at
3348 # For files marked as removed, we check if an unknown file is present at
3349 # the same path. If a such file exists it may need to be backed up.
3349 # the same path. If a such file exists it may need to be backed up.
3350 # Making the distinction at this stage helps have simpler backup
3350 # Making the distinction at this stage helps have simpler backup
3351 # logic.
3351 # logic.
3352 removunk = set()
3352 removunk = set()
3353 for abs in removed:
3353 for abs in removed:
3354 target = repo.wjoin(abs)
3354 target = repo.wjoin(abs)
3355 if os.path.lexists(target):
3355 if os.path.lexists(target):
3356 removunk.add(abs)
3356 removunk.add(abs)
3357 removed -= removunk
3357 removed -= removunk
3358
3358
3359 dsremovunk = set()
3359 dsremovunk = set()
3360 for abs in dsremoved:
3360 for abs in dsremoved:
3361 target = repo.wjoin(abs)
3361 target = repo.wjoin(abs)
3362 if os.path.lexists(target):
3362 if os.path.lexists(target):
3363 dsremovunk.add(abs)
3363 dsremovunk.add(abs)
3364 dsremoved -= dsremovunk
3364 dsremoved -= dsremovunk
3365
3365
3366 # action to be actually performed by revert
3366 # action to be actually performed by revert
3367 # (<list of file>, message>) tuple
3367 # (<list of file>, message>) tuple
3368 actions = {
3368 actions = {
3369 b'revert': ([], _(b'reverting %s\n')),
3369 b'revert': ([], _(b'reverting %s\n')),
3370 b'add': ([], _(b'adding %s\n')),
3370 b'add': ([], _(b'adding %s\n')),
3371 b'remove': ([], _(b'removing %s\n')),
3371 b'remove': ([], _(b'removing %s\n')),
3372 b'drop': ([], _(b'removing %s\n')),
3372 b'drop': ([], _(b'removing %s\n')),
3373 b'forget': ([], _(b'forgetting %s\n')),
3373 b'forget': ([], _(b'forgetting %s\n')),
3374 b'undelete': ([], _(b'undeleting %s\n')),
3374 b'undelete': ([], _(b'undeleting %s\n')),
3375 b'noop': (None, _(b'no changes needed to %s\n')),
3375 b'noop': (None, _(b'no changes needed to %s\n')),
3376 b'unknown': (None, _(b'file not managed: %s\n')),
3376 b'unknown': (None, _(b'file not managed: %s\n')),
3377 }
3377 }
3378
3378
3379 # "constant" that convey the backup strategy.
3379 # "constant" that convey the backup strategy.
3380 # All set to `discard` if `no-backup` is set do avoid checking
3380 # All set to `discard` if `no-backup` is set do avoid checking
3381 # no_backup lower in the code.
3381 # no_backup lower in the code.
3382 # These values are ordered for comparison purposes
3382 # These values are ordered for comparison purposes
3383 backupinteractive = 3 # do backup if interactively modified
3383 backupinteractive = 3 # do backup if interactively modified
3384 backup = 2 # unconditionally do backup
3384 backup = 2 # unconditionally do backup
3385 check = 1 # check if the existing file differs from target
3385 check = 1 # check if the existing file differs from target
3386 discard = 0 # never do backup
3386 discard = 0 # never do backup
3387 if opts.get(b'no_backup'):
3387 if opts.get(b'no_backup'):
3388 backupinteractive = backup = check = discard
3388 backupinteractive = backup = check = discard
3389 if interactive:
3389 if interactive:
3390 dsmodifiedbackup = backupinteractive
3390 dsmodifiedbackup = backupinteractive
3391 else:
3391 else:
3392 dsmodifiedbackup = backup
3392 dsmodifiedbackup = backup
3393 tobackup = set()
3393 tobackup = set()
3394
3394
3395 backupanddel = actions[b'remove']
3395 backupanddel = actions[b'remove']
3396 if not opts.get(b'no_backup'):
3396 if not opts.get(b'no_backup'):
3397 backupanddel = actions[b'drop']
3397 backupanddel = actions[b'drop']
3398
3398
3399 disptable = (
3399 disptable = (
3400 # dispatch table:
3400 # dispatch table:
3401 # file state
3401 # file state
3402 # action
3402 # action
3403 # make backup
3403 # make backup
3404 ## Sets that results that will change file on disk
3404 ## Sets that results that will change file on disk
3405 # Modified compared to target, no local change
3405 # Modified compared to target, no local change
3406 (modified, actions[b'revert'], discard),
3406 (modified, actions[b'revert'], discard),
3407 # Modified compared to target, but local file is deleted
3407 # Modified compared to target, but local file is deleted
3408 (deleted, actions[b'revert'], discard),
3408 (deleted, actions[b'revert'], discard),
3409 # Modified compared to target, local change
3409 # Modified compared to target, local change
3410 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3410 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3411 # Added since target
3411 # Added since target
3412 (added, actions[b'remove'], discard),
3412 (added, actions[b'remove'], discard),
3413 # Added in working directory
3413 # Added in working directory
3414 (dsadded, actions[b'forget'], discard),
3414 (dsadded, actions[b'forget'], discard),
3415 # Added since target, have local modification
3415 # Added since target, have local modification
3416 (modadded, backupanddel, backup),
3416 (modadded, backupanddel, backup),
3417 # Added since target but file is missing in working directory
3417 # Added since target but file is missing in working directory
3418 (deladded, actions[b'drop'], discard),
3418 (deladded, actions[b'drop'], discard),
3419 # Removed since target, before working copy parent
3419 # Removed since target, before working copy parent
3420 (removed, actions[b'add'], discard),
3420 (removed, actions[b'add'], discard),
3421 # Same as `removed` but an unknown file exists at the same path
3421 # Same as `removed` but an unknown file exists at the same path
3422 (removunk, actions[b'add'], check),
3422 (removunk, actions[b'add'], check),
3423 # Removed since targe, marked as such in working copy parent
3423 # Removed since targe, marked as such in working copy parent
3424 (dsremoved, actions[b'undelete'], discard),
3424 (dsremoved, actions[b'undelete'], discard),
3425 # Same as `dsremoved` but an unknown file exists at the same path
3425 # Same as `dsremoved` but an unknown file exists at the same path
3426 (dsremovunk, actions[b'undelete'], check),
3426 (dsremovunk, actions[b'undelete'], check),
3427 ## the following sets does not result in any file changes
3427 ## the following sets does not result in any file changes
3428 # File with no modification
3428 # File with no modification
3429 (clean, actions[b'noop'], discard),
3429 (clean, actions[b'noop'], discard),
3430 # Existing file, not tracked anywhere
3430 # Existing file, not tracked anywhere
3431 (unknown, actions[b'unknown'], discard),
3431 (unknown, actions[b'unknown'], discard),
3432 )
3432 )
3433
3433
3434 for abs, exact in sorted(names.items()):
3434 for abs, exact in sorted(names.items()):
3435 # target file to be touch on disk (relative to cwd)
3435 # target file to be touch on disk (relative to cwd)
3436 target = repo.wjoin(abs)
3436 target = repo.wjoin(abs)
3437 # search the entry in the dispatch table.
3437 # search the entry in the dispatch table.
3438 # if the file is in any of these sets, it was touched in the working
3438 # if the file is in any of these sets, it was touched in the working
3439 # directory parent and we are sure it needs to be reverted.
3439 # directory parent and we are sure it needs to be reverted.
3440 for table, (xlist, msg), dobackup in disptable:
3440 for table, (xlist, msg), dobackup in disptable:
3441 if abs not in table:
3441 if abs not in table:
3442 continue
3442 continue
3443 if xlist is not None:
3443 if xlist is not None:
3444 xlist.append(abs)
3444 xlist.append(abs)
3445 if dobackup:
3445 if dobackup:
3446 # If in interactive mode, don't automatically create
3446 # If in interactive mode, don't automatically create
3447 # .orig files (issue4793)
3447 # .orig files (issue4793)
3448 if dobackup == backupinteractive:
3448 if dobackup == backupinteractive:
3449 tobackup.add(abs)
3449 tobackup.add(abs)
3450 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3450 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3451 absbakname = scmutil.backuppath(ui, repo, abs)
3451 absbakname = scmutil.backuppath(ui, repo, abs)
3452 bakname = os.path.relpath(
3452 bakname = os.path.relpath(
3453 absbakname, start=repo.root
3453 absbakname, start=repo.root
3454 )
3454 )
3455 ui.note(
3455 ui.note(
3456 _(b'saving current version of %s as %s\n')
3456 _(b'saving current version of %s as %s\n')
3457 % (uipathfn(abs), uipathfn(bakname))
3457 % (uipathfn(abs), uipathfn(bakname))
3458 )
3458 )
3459 if not opts.get(b'dry_run'):
3459 if not opts.get(b'dry_run'):
3460 if interactive:
3460 if interactive:
3461 util.copyfile(target, absbakname)
3461 util.copyfile(target, absbakname)
3462 else:
3462 else:
3463 util.rename(target, absbakname)
3463 util.rename(target, absbakname)
3464 if opts.get(b'dry_run'):
3464 if opts.get(b'dry_run'):
3465 if ui.verbose or not exact:
3465 if ui.verbose or not exact:
3466 ui.status(msg % uipathfn(abs))
3466 ui.status(msg % uipathfn(abs))
3467 elif exact:
3467 elif exact:
3468 ui.warn(msg % uipathfn(abs))
3468 ui.warn(msg % uipathfn(abs))
3469 break
3469 break
3470
3470
3471 if not opts.get(b'dry_run'):
3471 if not opts.get(b'dry_run'):
3472 needdata = (b'revert', b'add', b'undelete')
3472 needdata = (b'revert', b'add', b'undelete')
3473 oplist = [actions[name][0] for name in needdata]
3473 oplist = [actions[name][0] for name in needdata]
3474 prefetch = scmutil.prefetchfiles
3474 prefetch = scmutil.prefetchfiles
3475 matchfiles = scmutil.matchfiles(
3475 matchfiles = scmutil.matchfiles(
3476 repo, [f for sublist in oplist for f in sublist]
3476 repo, [f for sublist in oplist for f in sublist]
3477 )
3477 )
3478 prefetch(
3478 prefetch(
3479 repo, [(ctx.rev(), matchfiles)],
3479 repo, [(ctx.rev(), matchfiles)],
3480 )
3480 )
3481 match = scmutil.match(repo[None], pats)
3481 match = scmutil.match(repo[None], pats)
3482 _performrevert(
3482 _performrevert(
3483 repo,
3483 repo,
3484 ctx,
3484 ctx,
3485 names,
3485 names,
3486 uipathfn,
3486 uipathfn,
3487 actions,
3487 actions,
3488 match,
3488 match,
3489 interactive,
3489 interactive,
3490 tobackup,
3490 tobackup,
3491 )
3491 )
3492
3492
3493 if targetsubs:
3493 if targetsubs:
3494 # Revert the subrepos on the revert list
3494 # Revert the subrepos on the revert list
3495 for sub in targetsubs:
3495 for sub in targetsubs:
3496 try:
3496 try:
3497 wctx.sub(sub).revert(
3497 wctx.sub(sub).revert(
3498 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3498 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3499 )
3499 )
3500 except KeyError:
3500 except KeyError:
3501 raise error.Abort(
3501 raise error.Abort(
3502 b"subrepository '%s' does not exist in %s!"
3502 b"subrepository '%s' does not exist in %s!"
3503 % (sub, short(ctx.node()))
3503 % (sub, short(ctx.node()))
3504 )
3504 )
3505
3505
3506
3506
3507 def _performrevert(
3507 def _performrevert(
3508 repo,
3508 repo,
3509 ctx,
3509 ctx,
3510 names,
3510 names,
3511 uipathfn,
3511 uipathfn,
3512 actions,
3512 actions,
3513 match,
3513 match,
3514 interactive=False,
3514 interactive=False,
3515 tobackup=None,
3515 tobackup=None,
3516 ):
3516 ):
3517 """function that actually perform all the actions computed for revert
3517 """function that actually perform all the actions computed for revert
3518
3518
3519 This is an independent function to let extension to plug in and react to
3519 This is an independent function to let extension to plug in and react to
3520 the imminent revert.
3520 the imminent revert.
3521
3521
3522 Make sure you have the working directory locked when calling this function.
3522 Make sure you have the working directory locked when calling this function.
3523 """
3523 """
3524 parent, p2 = repo.dirstate.parents()
3524 parent, p2 = repo.dirstate.parents()
3525 node = ctx.node()
3525 node = ctx.node()
3526 excluded_files = []
3526 excluded_files = []
3527
3527
3528 def checkout(f):
3528 def checkout(f):
3529 fc = ctx[f]
3529 fc = ctx[f]
3530 repo.wwrite(f, fc.data(), fc.flags())
3530 repo.wwrite(f, fc.data(), fc.flags())
3531
3531
3532 def doremove(f):
3532 def doremove(f):
3533 try:
3533 try:
3534 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3534 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3535 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3535 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3536 except OSError:
3536 except OSError:
3537 pass
3537 pass
3538 repo.dirstate.remove(f)
3538 repo.dirstate.remove(f)
3539
3539
3540 def prntstatusmsg(action, f):
3540 def prntstatusmsg(action, f):
3541 exact = names[f]
3541 exact = names[f]
3542 if repo.ui.verbose or not exact:
3542 if repo.ui.verbose or not exact:
3543 repo.ui.status(actions[action][1] % uipathfn(f))
3543 repo.ui.status(actions[action][1] % uipathfn(f))
3544
3544
3545 audit_path = pathutil.pathauditor(repo.root, cached=True)
3545 audit_path = pathutil.pathauditor(repo.root, cached=True)
3546 for f in actions[b'forget'][0]:
3546 for f in actions[b'forget'][0]:
3547 if interactive:
3547 if interactive:
3548 choice = repo.ui.promptchoice(
3548 choice = repo.ui.promptchoice(
3549 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3549 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3550 )
3550 )
3551 if choice == 0:
3551 if choice == 0:
3552 prntstatusmsg(b'forget', f)
3552 prntstatusmsg(b'forget', f)
3553 repo.dirstate.drop(f)
3553 repo.dirstate.drop(f)
3554 else:
3554 else:
3555 excluded_files.append(f)
3555 excluded_files.append(f)
3556 else:
3556 else:
3557 prntstatusmsg(b'forget', f)
3557 prntstatusmsg(b'forget', f)
3558 repo.dirstate.drop(f)
3558 repo.dirstate.drop(f)
3559 for f in actions[b'remove'][0]:
3559 for f in actions[b'remove'][0]:
3560 audit_path(f)
3560 audit_path(f)
3561 if interactive:
3561 if interactive:
3562 choice = repo.ui.promptchoice(
3562 choice = repo.ui.promptchoice(
3563 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3563 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3564 )
3564 )
3565 if choice == 0:
3565 if choice == 0:
3566 prntstatusmsg(b'remove', f)
3566 prntstatusmsg(b'remove', f)
3567 doremove(f)
3567 doremove(f)
3568 else:
3568 else:
3569 excluded_files.append(f)
3569 excluded_files.append(f)
3570 else:
3570 else:
3571 prntstatusmsg(b'remove', f)
3571 prntstatusmsg(b'remove', f)
3572 doremove(f)
3572 doremove(f)
3573 for f in actions[b'drop'][0]:
3573 for f in actions[b'drop'][0]:
3574 audit_path(f)
3574 audit_path(f)
3575 prntstatusmsg(b'drop', f)
3575 prntstatusmsg(b'drop', f)
3576 repo.dirstate.remove(f)
3576 repo.dirstate.remove(f)
3577
3577
3578 normal = None
3578 normal = None
3579 if node == parent:
3579 if node == parent:
3580 # We're reverting to our parent. If possible, we'd like status
3580 # We're reverting to our parent. If possible, we'd like status
3581 # to report the file as clean. We have to use normallookup for
3581 # to report the file as clean. We have to use normallookup for
3582 # merges to avoid losing information about merged/dirty files.
3582 # merges to avoid losing information about merged/dirty files.
3583 if p2 != nullid:
3583 if p2 != nullid:
3584 normal = repo.dirstate.normallookup
3584 normal = repo.dirstate.normallookup
3585 else:
3585 else:
3586 normal = repo.dirstate.normal
3586 normal = repo.dirstate.normal
3587
3587
3588 newlyaddedandmodifiedfiles = set()
3588 newlyaddedandmodifiedfiles = set()
3589 if interactive:
3589 if interactive:
3590 # Prompt the user for changes to revert
3590 # Prompt the user for changes to revert
3591 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3591 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3592 m = scmutil.matchfiles(repo, torevert)
3592 m = scmutil.matchfiles(repo, torevert)
3593 diffopts = patch.difffeatureopts(
3593 diffopts = patch.difffeatureopts(
3594 repo.ui,
3594 repo.ui,
3595 whitespace=True,
3595 whitespace=True,
3596 section=b'commands',
3596 section=b'commands',
3597 configprefix=b'revert.interactive.',
3597 configprefix=b'revert.interactive.',
3598 )
3598 )
3599 diffopts.nodates = True
3599 diffopts.nodates = True
3600 diffopts.git = True
3600 diffopts.git = True
3601 operation = b'apply'
3601 operation = b'apply'
3602 if node == parent:
3602 if node == parent:
3603 if repo.ui.configbool(
3603 if repo.ui.configbool(
3604 b'experimental', b'revert.interactive.select-to-keep'
3604 b'experimental', b'revert.interactive.select-to-keep'
3605 ):
3605 ):
3606 operation = b'keep'
3606 operation = b'keep'
3607 else:
3607 else:
3608 operation = b'discard'
3608 operation = b'discard'
3609
3609
3610 if operation == b'apply':
3610 if operation == b'apply':
3611 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3611 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3612 else:
3612 else:
3613 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3613 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3614 originalchunks = patch.parsepatch(diff)
3614 originalchunks = patch.parsepatch(diff)
3615
3615
3616 try:
3616 try:
3617
3617
3618 chunks, opts = recordfilter(
3618 chunks, opts = recordfilter(
3619 repo.ui, originalchunks, match, operation=operation
3619 repo.ui, originalchunks, match, operation=operation
3620 )
3620 )
3621 if operation == b'discard':
3621 if operation == b'discard':
3622 chunks = patch.reversehunks(chunks)
3622 chunks = patch.reversehunks(chunks)
3623
3623
3624 except error.PatchError as err:
3624 except error.PatchError as err:
3625 raise error.Abort(_(b'error parsing patch: %s') % err)
3625 raise error.Abort(_(b'error parsing patch: %s') % err)
3626
3626
3627 # FIXME: when doing an interactive revert of a copy, there's no way of
3627 # FIXME: when doing an interactive revert of a copy, there's no way of
3628 # performing a partial revert of the added file, the only option is
3628 # performing a partial revert of the added file, the only option is
3629 # "remove added file <name> (Yn)?", so we don't need to worry about the
3629 # "remove added file <name> (Yn)?", so we don't need to worry about the
3630 # alsorestore value. Ideally we'd be able to partially revert
3630 # alsorestore value. Ideally we'd be able to partially revert
3631 # copied/renamed files.
3631 # copied/renamed files.
3632 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3632 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3633 chunks, originalchunks
3633 chunks, originalchunks
3634 )
3634 )
3635 if tobackup is None:
3635 if tobackup is None:
3636 tobackup = set()
3636 tobackup = set()
3637 # Apply changes
3637 # Apply changes
3638 fp = stringio()
3638 fp = stringio()
3639 # chunks are serialized per file, but files aren't sorted
3639 # chunks are serialized per file, but files aren't sorted
3640 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3640 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3641 prntstatusmsg(b'revert', f)
3641 prntstatusmsg(b'revert', f)
3642 files = set()
3642 files = set()
3643 for c in chunks:
3643 for c in chunks:
3644 if ishunk(c):
3644 if ishunk(c):
3645 abs = c.header.filename()
3645 abs = c.header.filename()
3646 # Create a backup file only if this hunk should be backed up
3646 # Create a backup file only if this hunk should be backed up
3647 if c.header.filename() in tobackup:
3647 if c.header.filename() in tobackup:
3648 target = repo.wjoin(abs)
3648 target = repo.wjoin(abs)
3649 bakname = scmutil.backuppath(repo.ui, repo, abs)
3649 bakname = scmutil.backuppath(repo.ui, repo, abs)
3650 util.copyfile(target, bakname)
3650 util.copyfile(target, bakname)
3651 tobackup.remove(abs)
3651 tobackup.remove(abs)
3652 if abs not in files:
3652 if abs not in files:
3653 files.add(abs)
3653 files.add(abs)
3654 if operation == b'keep':
3654 if operation == b'keep':
3655 checkout(abs)
3655 checkout(abs)
3656 c.write(fp)
3656 c.write(fp)
3657 dopatch = fp.tell()
3657 dopatch = fp.tell()
3658 fp.seek(0)
3658 fp.seek(0)
3659 if dopatch:
3659 if dopatch:
3660 try:
3660 try:
3661 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3661 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3662 except error.PatchError as err:
3662 except error.PatchError as err:
3663 raise error.Abort(pycompat.bytestr(err))
3663 raise error.Abort(pycompat.bytestr(err))
3664 del fp
3664 del fp
3665 else:
3665 else:
3666 for f in actions[b'revert'][0]:
3666 for f in actions[b'revert'][0]:
3667 prntstatusmsg(b'revert', f)
3667 prntstatusmsg(b'revert', f)
3668 checkout(f)
3668 checkout(f)
3669 if normal:
3669 if normal:
3670 normal(f)
3670 normal(f)
3671
3671
3672 for f in actions[b'add'][0]:
3672 for f in actions[b'add'][0]:
3673 # Don't checkout modified files, they are already created by the diff
3673 # Don't checkout modified files, they are already created by the diff
3674 if f not in newlyaddedandmodifiedfiles:
3674 if f not in newlyaddedandmodifiedfiles:
3675 prntstatusmsg(b'add', f)
3675 prntstatusmsg(b'add', f)
3676 checkout(f)
3676 checkout(f)
3677 repo.dirstate.add(f)
3677 repo.dirstate.add(f)
3678
3678
3679 normal = repo.dirstate.normallookup
3679 normal = repo.dirstate.normallookup
3680 if node == parent and p2 == nullid:
3680 if node == parent and p2 == nullid:
3681 normal = repo.dirstate.normal
3681 normal = repo.dirstate.normal
3682 for f in actions[b'undelete'][0]:
3682 for f in actions[b'undelete'][0]:
3683 if interactive:
3683 if interactive:
3684 choice = repo.ui.promptchoice(
3684 choice = repo.ui.promptchoice(
3685 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3685 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3686 )
3686 )
3687 if choice == 0:
3687 if choice == 0:
3688 prntstatusmsg(b'undelete', f)
3688 prntstatusmsg(b'undelete', f)
3689 checkout(f)
3689 checkout(f)
3690 normal(f)
3690 normal(f)
3691 else:
3691 else:
3692 excluded_files.append(f)
3692 excluded_files.append(f)
3693 else:
3693 else:
3694 prntstatusmsg(b'undelete', f)
3694 prntstatusmsg(b'undelete', f)
3695 checkout(f)
3695 checkout(f)
3696 normal(f)
3696 normal(f)
3697
3697
3698 copied = copies.pathcopies(repo[parent], ctx)
3698 copied = copies.pathcopies(repo[parent], ctx)
3699
3699
3700 for f in (
3700 for f in (
3701 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3701 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3702 ):
3702 ):
3703 if f in copied:
3703 if f in copied:
3704 repo.dirstate.copy(copied[f], f)
3704 repo.dirstate.copy(copied[f], f)
3705
3705
3706
3706
3707 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3707 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3708 # commands.outgoing. "missing" is "missing" of the result of
3708 # commands.outgoing. "missing" is "missing" of the result of
3709 # "findcommonoutgoing()"
3709 # "findcommonoutgoing()"
3710 outgoinghooks = util.hooks()
3710 outgoinghooks = util.hooks()
3711
3711
3712 # a list of (ui, repo) functions called by commands.summary
3712 # a list of (ui, repo) functions called by commands.summary
3713 summaryhooks = util.hooks()
3713 summaryhooks = util.hooks()
3714
3714
3715 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3715 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3716 #
3716 #
3717 # functions should return tuple of booleans below, if 'changes' is None:
3717 # functions should return tuple of booleans below, if 'changes' is None:
3718 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3718 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3719 #
3719 #
3720 # otherwise, 'changes' is a tuple of tuples below:
3720 # otherwise, 'changes' is a tuple of tuples below:
3721 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3721 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3722 # - (desturl, destbranch, destpeer, outgoing)
3722 # - (desturl, destbranch, destpeer, outgoing)
3723 summaryremotehooks = util.hooks()
3723 summaryremotehooks = util.hooks()
3724
3724
3725
3725
3726 def checkunfinished(repo, commit=False, skipmerge=False):
3726 def checkunfinished(repo, commit=False, skipmerge=False):
3727 '''Look for an unfinished multistep operation, like graft, and abort
3727 '''Look for an unfinished multistep operation, like graft, and abort
3728 if found. It's probably good to check this right before
3728 if found. It's probably good to check this right before
3729 bailifchanged().
3729 bailifchanged().
3730 '''
3730 '''
3731 # Check for non-clearable states first, so things like rebase will take
3731 # Check for non-clearable states first, so things like rebase will take
3732 # precedence over update.
3732 # precedence over update.
3733 for state in statemod._unfinishedstates:
3733 for state in statemod._unfinishedstates:
3734 if (
3734 if (
3735 state._clearable
3735 state._clearable
3736 or (commit and state._allowcommit)
3736 or (commit and state._allowcommit)
3737 or state._reportonly
3737 or state._reportonly
3738 ):
3738 ):
3739 continue
3739 continue
3740 if state.isunfinished(repo):
3740 if state.isunfinished(repo):
3741 raise error.StateError(state.msg(), hint=state.hint())
3741 raise error.StateError(state.msg(), hint=state.hint())
3742
3742
3743 for s in statemod._unfinishedstates:
3743 for s in statemod._unfinishedstates:
3744 if (
3744 if (
3745 not s._clearable
3745 not s._clearable
3746 or (commit and s._allowcommit)
3746 or (commit and s._allowcommit)
3747 or (s._opname == b'merge' and skipmerge)
3747 or (s._opname == b'merge' and skipmerge)
3748 or s._reportonly
3748 or s._reportonly
3749 ):
3749 ):
3750 continue
3750 continue
3751 if s.isunfinished(repo):
3751 if s.isunfinished(repo):
3752 raise error.StateError(s.msg(), hint=s.hint())
3752 raise error.StateError(s.msg(), hint=s.hint())
3753
3753
3754
3754
3755 def clearunfinished(repo):
3755 def clearunfinished(repo):
3756 '''Check for unfinished operations (as above), and clear the ones
3756 '''Check for unfinished operations (as above), and clear the ones
3757 that are clearable.
3757 that are clearable.
3758 '''
3758 '''
3759 for state in statemod._unfinishedstates:
3759 for state in statemod._unfinishedstates:
3760 if state._reportonly:
3760 if state._reportonly:
3761 continue
3761 continue
3762 if not state._clearable and state.isunfinished(repo):
3762 if not state._clearable and state.isunfinished(repo):
3763 raise error.StateError(state.msg(), hint=state.hint())
3763 raise error.StateError(state.msg(), hint=state.hint())
3764
3764
3765 for s in statemod._unfinishedstates:
3765 for s in statemod._unfinishedstates:
3766 if s._opname == b'merge' or state._reportonly:
3766 if s._opname == b'merge' or state._reportonly:
3767 continue
3767 continue
3768 if s._clearable and s.isunfinished(repo):
3768 if s._clearable and s.isunfinished(repo):
3769 util.unlink(repo.vfs.join(s._fname))
3769 util.unlink(repo.vfs.join(s._fname))
3770
3770
3771
3771
3772 def getunfinishedstate(repo):
3772 def getunfinishedstate(repo):
3773 ''' Checks for unfinished operations and returns statecheck object
3773 ''' Checks for unfinished operations and returns statecheck object
3774 for it'''
3774 for it'''
3775 for state in statemod._unfinishedstates:
3775 for state in statemod._unfinishedstates:
3776 if state.isunfinished(repo):
3776 if state.isunfinished(repo):
3777 return state
3777 return state
3778 return None
3778 return None
3779
3779
3780
3780
3781 def howtocontinue(repo):
3781 def howtocontinue(repo):
3782 '''Check for an unfinished operation and return the command to finish
3782 '''Check for an unfinished operation and return the command to finish
3783 it.
3783 it.
3784
3784
3785 statemod._unfinishedstates list is checked for an unfinished operation
3785 statemod._unfinishedstates list is checked for an unfinished operation
3786 and the corresponding message to finish it is generated if a method to
3786 and the corresponding message to finish it is generated if a method to
3787 continue is supported by the operation.
3787 continue is supported by the operation.
3788
3788
3789 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3789 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3790 a boolean.
3790 a boolean.
3791 '''
3791 '''
3792 contmsg = _(b"continue: %s")
3792 contmsg = _(b"continue: %s")
3793 for state in statemod._unfinishedstates:
3793 for state in statemod._unfinishedstates:
3794 if not state._continueflag:
3794 if not state._continueflag:
3795 continue
3795 continue
3796 if state.isunfinished(repo):
3796 if state.isunfinished(repo):
3797 return contmsg % state.continuemsg(), True
3797 return contmsg % state.continuemsg(), True
3798 if repo[None].dirty(missing=True, merge=False, branch=False):
3798 if repo[None].dirty(missing=True, merge=False, branch=False):
3799 return contmsg % _(b"hg commit"), False
3799 return contmsg % _(b"hg commit"), False
3800 return None, None
3800 return None, None
3801
3801
3802
3802
3803 def checkafterresolved(repo):
3803 def checkafterresolved(repo):
3804 '''Inform the user about the next action after completing hg resolve
3804 '''Inform the user about the next action after completing hg resolve
3805
3805
3806 If there's a an unfinished operation that supports continue flag,
3806 If there's a an unfinished operation that supports continue flag,
3807 howtocontinue will yield repo.ui.warn as the reporter.
3807 howtocontinue will yield repo.ui.warn as the reporter.
3808
3808
3809 Otherwise, it will yield repo.ui.note.
3809 Otherwise, it will yield repo.ui.note.
3810 '''
3810 '''
3811 msg, warning = howtocontinue(repo)
3811 msg, warning = howtocontinue(repo)
3812 if msg is not None:
3812 if msg is not None:
3813 if warning:
3813 if warning:
3814 repo.ui.warn(b"%s\n" % msg)
3814 repo.ui.warn(b"%s\n" % msg)
3815 else:
3815 else:
3816 repo.ui.note(b"%s\n" % msg)
3816 repo.ui.note(b"%s\n" % msg)
3817
3817
3818
3818
3819 def wrongtooltocontinue(repo, task):
3819 def wrongtooltocontinue(repo, task):
3820 '''Raise an abort suggesting how to properly continue if there is an
3820 '''Raise an abort suggesting how to properly continue if there is an
3821 active task.
3821 active task.
3822
3822
3823 Uses howtocontinue() to find the active task.
3823 Uses howtocontinue() to find the active task.
3824
3824
3825 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3825 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3826 a hint.
3826 a hint.
3827 '''
3827 '''
3828 after = howtocontinue(repo)
3828 after = howtocontinue(repo)
3829 hint = None
3829 hint = None
3830 if after[1]:
3830 if after[1]:
3831 hint = after[0]
3831 hint = after[0]
3832 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
3832 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
3833
3833
3834
3834
3835 def abortgraft(ui, repo, graftstate):
3835 def abortgraft(ui, repo, graftstate):
3836 """abort the interrupted graft and rollbacks to the state before interrupted
3836 """abort the interrupted graft and rollbacks to the state before interrupted
3837 graft"""
3837 graft"""
3838 if not graftstate.exists():
3838 if not graftstate.exists():
3839 raise error.StateError(_(b"no interrupted graft to abort"))
3839 raise error.StateError(_(b"no interrupted graft to abort"))
3840 statedata = readgraftstate(repo, graftstate)
3840 statedata = readgraftstate(repo, graftstate)
3841 newnodes = statedata.get(b'newnodes')
3841 newnodes = statedata.get(b'newnodes')
3842 if newnodes is None:
3842 if newnodes is None:
3843 # and old graft state which does not have all the data required to abort
3843 # and old graft state which does not have all the data required to abort
3844 # the graft
3844 # the graft
3845 raise error.Abort(_(b"cannot abort using an old graftstate"))
3845 raise error.Abort(_(b"cannot abort using an old graftstate"))
3846
3846
3847 # changeset from which graft operation was started
3847 # changeset from which graft operation was started
3848 if len(newnodes) > 0:
3848 if len(newnodes) > 0:
3849 startctx = repo[newnodes[0]].p1()
3849 startctx = repo[newnodes[0]].p1()
3850 else:
3850 else:
3851 startctx = repo[b'.']
3851 startctx = repo[b'.']
3852 # whether to strip or not
3852 # whether to strip or not
3853 cleanup = False
3853 cleanup = False
3854
3854
3855 if newnodes:
3855 if newnodes:
3856 newnodes = [repo[r].rev() for r in newnodes]
3856 newnodes = [repo[r].rev() for r in newnodes]
3857 cleanup = True
3857 cleanup = True
3858 # checking that none of the newnodes turned public or is public
3858 # checking that none of the newnodes turned public or is public
3859 immutable = [c for c in newnodes if not repo[c].mutable()]
3859 immutable = [c for c in newnodes if not repo[c].mutable()]
3860 if immutable:
3860 if immutable:
3861 repo.ui.warn(
3861 repo.ui.warn(
3862 _(b"cannot clean up public changesets %s\n")
3862 _(b"cannot clean up public changesets %s\n")
3863 % b', '.join(bytes(repo[r]) for r in immutable),
3863 % b', '.join(bytes(repo[r]) for r in immutable),
3864 hint=_(b"see 'hg help phases' for details"),
3864 hint=_(b"see 'hg help phases' for details"),
3865 )
3865 )
3866 cleanup = False
3866 cleanup = False
3867
3867
3868 # checking that no new nodes are created on top of grafted revs
3868 # checking that no new nodes are created on top of grafted revs
3869 desc = set(repo.changelog.descendants(newnodes))
3869 desc = set(repo.changelog.descendants(newnodes))
3870 if desc - set(newnodes):
3870 if desc - set(newnodes):
3871 repo.ui.warn(
3871 repo.ui.warn(
3872 _(
3872 _(
3873 b"new changesets detected on destination "
3873 b"new changesets detected on destination "
3874 b"branch, can't strip\n"
3874 b"branch, can't strip\n"
3875 )
3875 )
3876 )
3876 )
3877 cleanup = False
3877 cleanup = False
3878
3878
3879 if cleanup:
3879 if cleanup:
3880 with repo.wlock(), repo.lock():
3880 with repo.wlock(), repo.lock():
3881 mergemod.clean_update(startctx)
3881 mergemod.clean_update(startctx)
3882 # stripping the new nodes created
3882 # stripping the new nodes created
3883 strippoints = [
3883 strippoints = [
3884 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3884 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3885 ]
3885 ]
3886 repair.strip(repo.ui, repo, strippoints, backup=False)
3886 repair.strip(repo.ui, repo, strippoints, backup=False)
3887
3887
3888 if not cleanup:
3888 if not cleanup:
3889 # we don't update to the startnode if we can't strip
3889 # we don't update to the startnode if we can't strip
3890 startctx = repo[b'.']
3890 startctx = repo[b'.']
3891 mergemod.clean_update(startctx)
3891 mergemod.clean_update(startctx)
3892
3892
3893 ui.status(_(b"graft aborted\n"))
3893 ui.status(_(b"graft aborted\n"))
3894 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3894 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3895 graftstate.delete()
3895 graftstate.delete()
3896 return 0
3896 return 0
3897
3897
3898
3898
3899 def readgraftstate(repo, graftstate):
3899 def readgraftstate(repo, graftstate):
3900 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3900 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3901 """read the graft state file and return a dict of the data stored in it"""
3901 """read the graft state file and return a dict of the data stored in it"""
3902 try:
3902 try:
3903 return graftstate.read()
3903 return graftstate.read()
3904 except error.CorruptedState:
3904 except error.CorruptedState:
3905 nodes = repo.vfs.read(b'graftstate').splitlines()
3905 nodes = repo.vfs.read(b'graftstate').splitlines()
3906 return {b'nodes': nodes}
3906 return {b'nodes': nodes}
3907
3907
3908
3908
3909 def hgabortgraft(ui, repo):
3909 def hgabortgraft(ui, repo):
3910 """ abort logic for aborting graft using 'hg abort'"""
3910 """ abort logic for aborting graft using 'hg abort'"""
3911 with repo.wlock():
3911 with repo.wlock():
3912 graftstate = statemod.cmdstate(repo, b'graftstate')
3912 graftstate = statemod.cmdstate(repo, b'graftstate')
3913 return abortgraft(ui, repo, graftstate)
3913 return abortgraft(ui, repo, graftstate)
@@ -1,3563 +1,3563 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 functools
11 import functools
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 bundlecaches,
34 bundlecaches,
35 changegroup,
35 changegroup,
36 color,
36 color,
37 commit,
37 commit,
38 context,
38 context,
39 dirstate,
39 dirstate,
40 dirstateguard,
40 dirstateguard,
41 discovery,
41 discovery,
42 encoding,
42 encoding,
43 error,
43 error,
44 exchange,
44 exchange,
45 extensions,
45 extensions,
46 filelog,
46 filelog,
47 hook,
47 hook,
48 lock as lockmod,
48 lock as lockmod,
49 match as matchmod,
49 match as matchmod,
50 mergestate as mergestatemod,
50 mergestate as mergestatemod,
51 mergeutil,
51 mergeutil,
52 namespaces,
52 namespaces,
53 narrowspec,
53 narrowspec,
54 obsolete,
54 obsolete,
55 pathutil,
55 pathutil,
56 phases,
56 phases,
57 pushkey,
57 pushkey,
58 pycompat,
58 pycompat,
59 rcutil,
59 rcutil,
60 repoview,
60 repoview,
61 requirements as requirementsmod,
61 requirements as requirementsmod,
62 revset,
62 revset,
63 revsetlang,
63 revsetlang,
64 scmutil,
64 scmutil,
65 sparse,
65 sparse,
66 store as storemod,
66 store as storemod,
67 subrepoutil,
67 subrepoutil,
68 tags as tagsmod,
68 tags as tagsmod,
69 transaction,
69 transaction,
70 txnutil,
70 txnutil,
71 util,
71 util,
72 vfs as vfsmod,
72 vfs as vfsmod,
73 )
73 )
74
74
75 from .interfaces import (
75 from .interfaces import (
76 repository,
76 repository,
77 util as interfaceutil,
77 util as interfaceutil,
78 )
78 )
79
79
80 from .utils import (
80 from .utils import (
81 hashutil,
81 hashutil,
82 procutil,
82 procutil,
83 stringutil,
83 stringutil,
84 )
84 )
85
85
86 from .revlogutils import constants as revlogconst
86 from .revlogutils import constants as revlogconst
87
87
88 release = lockmod.release
88 release = lockmod.release
89 urlerr = util.urlerr
89 urlerr = util.urlerr
90 urlreq = util.urlreq
90 urlreq = util.urlreq
91
91
92 # set of (path, vfs-location) tuples. vfs-location is:
92 # set of (path, vfs-location) tuples. vfs-location is:
93 # - 'plain for vfs relative paths
93 # - 'plain for vfs relative paths
94 # - '' for svfs relative paths
94 # - '' for svfs relative paths
95 _cachedfiles = set()
95 _cachedfiles = set()
96
96
97
97
98 class _basefilecache(scmutil.filecache):
98 class _basefilecache(scmutil.filecache):
99 """All filecache usage on repo are done for logic that should be unfiltered
99 """All filecache usage on repo are done for logic that should be unfiltered
100 """
100 """
101
101
102 def __get__(self, repo, type=None):
102 def __get__(self, repo, type=None):
103 if repo is None:
103 if repo is None:
104 return self
104 return self
105 # proxy to unfiltered __dict__ since filtered repo has no entry
105 # proxy to unfiltered __dict__ since filtered repo has no entry
106 unfi = repo.unfiltered()
106 unfi = repo.unfiltered()
107 try:
107 try:
108 return unfi.__dict__[self.sname]
108 return unfi.__dict__[self.sname]
109 except KeyError:
109 except KeyError:
110 pass
110 pass
111 return super(_basefilecache, self).__get__(unfi, type)
111 return super(_basefilecache, self).__get__(unfi, type)
112
112
113 def set(self, repo, value):
113 def set(self, repo, value):
114 return super(_basefilecache, self).set(repo.unfiltered(), value)
114 return super(_basefilecache, self).set(repo.unfiltered(), value)
115
115
116
116
117 class repofilecache(_basefilecache):
117 class repofilecache(_basefilecache):
118 """filecache for files in .hg but outside of .hg/store"""
118 """filecache for files in .hg but outside of .hg/store"""
119
119
120 def __init__(self, *paths):
120 def __init__(self, *paths):
121 super(repofilecache, self).__init__(*paths)
121 super(repofilecache, self).__init__(*paths)
122 for path in paths:
122 for path in paths:
123 _cachedfiles.add((path, b'plain'))
123 _cachedfiles.add((path, b'plain'))
124
124
125 def join(self, obj, fname):
125 def join(self, obj, fname):
126 return obj.vfs.join(fname)
126 return obj.vfs.join(fname)
127
127
128
128
129 class storecache(_basefilecache):
129 class storecache(_basefilecache):
130 """filecache for files in the store"""
130 """filecache for files in the store"""
131
131
132 def __init__(self, *paths):
132 def __init__(self, *paths):
133 super(storecache, self).__init__(*paths)
133 super(storecache, self).__init__(*paths)
134 for path in paths:
134 for path in paths:
135 _cachedfiles.add((path, b''))
135 _cachedfiles.add((path, b''))
136
136
137 def join(self, obj, fname):
137 def join(self, obj, fname):
138 return obj.sjoin(fname)
138 return obj.sjoin(fname)
139
139
140
140
141 class mixedrepostorecache(_basefilecache):
141 class mixedrepostorecache(_basefilecache):
142 """filecache for a mix files in .hg/store and outside"""
142 """filecache for a mix files in .hg/store and outside"""
143
143
144 def __init__(self, *pathsandlocations):
144 def __init__(self, *pathsandlocations):
145 # scmutil.filecache only uses the path for passing back into our
145 # scmutil.filecache only uses the path for passing back into our
146 # join(), so we can safely pass a list of paths and locations
146 # join(), so we can safely pass a list of paths and locations
147 super(mixedrepostorecache, self).__init__(*pathsandlocations)
147 super(mixedrepostorecache, self).__init__(*pathsandlocations)
148 _cachedfiles.update(pathsandlocations)
148 _cachedfiles.update(pathsandlocations)
149
149
150 def join(self, obj, fnameandlocation):
150 def join(self, obj, fnameandlocation):
151 fname, location = fnameandlocation
151 fname, location = fnameandlocation
152 if location == b'plain':
152 if location == b'plain':
153 return obj.vfs.join(fname)
153 return obj.vfs.join(fname)
154 else:
154 else:
155 if location != b'':
155 if location != b'':
156 raise error.ProgrammingError(
156 raise error.ProgrammingError(
157 b'unexpected location: %s' % location
157 b'unexpected location: %s' % location
158 )
158 )
159 return obj.sjoin(fname)
159 return obj.sjoin(fname)
160
160
161
161
162 def isfilecached(repo, name):
162 def isfilecached(repo, name):
163 """check if a repo has already cached "name" filecache-ed property
163 """check if a repo has already cached "name" filecache-ed property
164
164
165 This returns (cachedobj-or-None, iscached) tuple.
165 This returns (cachedobj-or-None, iscached) tuple.
166 """
166 """
167 cacheentry = repo.unfiltered()._filecache.get(name, None)
167 cacheentry = repo.unfiltered()._filecache.get(name, None)
168 if not cacheentry:
168 if not cacheentry:
169 return None, False
169 return None, False
170 return cacheentry.obj, True
170 return cacheentry.obj, True
171
171
172
172
173 class unfilteredpropertycache(util.propertycache):
173 class unfilteredpropertycache(util.propertycache):
174 """propertycache that apply to unfiltered repo only"""
174 """propertycache that apply to unfiltered repo only"""
175
175
176 def __get__(self, repo, type=None):
176 def __get__(self, repo, type=None):
177 unfi = repo.unfiltered()
177 unfi = repo.unfiltered()
178 if unfi is repo:
178 if unfi is repo:
179 return super(unfilteredpropertycache, self).__get__(unfi)
179 return super(unfilteredpropertycache, self).__get__(unfi)
180 return getattr(unfi, self.name)
180 return getattr(unfi, self.name)
181
181
182
182
183 class filteredpropertycache(util.propertycache):
183 class filteredpropertycache(util.propertycache):
184 """propertycache that must take filtering in account"""
184 """propertycache that must take filtering in account"""
185
185
186 def cachevalue(self, obj, value):
186 def cachevalue(self, obj, value):
187 object.__setattr__(obj, self.name, value)
187 object.__setattr__(obj, self.name, value)
188
188
189
189
190 def hasunfilteredcache(repo, name):
190 def hasunfilteredcache(repo, name):
191 """check if a repo has an unfilteredpropertycache value for <name>"""
191 """check if a repo has an unfilteredpropertycache value for <name>"""
192 return name in vars(repo.unfiltered())
192 return name in vars(repo.unfiltered())
193
193
194
194
195 def unfilteredmethod(orig):
195 def unfilteredmethod(orig):
196 """decorate method that always need to be run on unfiltered version"""
196 """decorate method that always need to be run on unfiltered version"""
197
197
198 @functools.wraps(orig)
198 @functools.wraps(orig)
199 def wrapper(repo, *args, **kwargs):
199 def wrapper(repo, *args, **kwargs):
200 return orig(repo.unfiltered(), *args, **kwargs)
200 return orig(repo.unfiltered(), *args, **kwargs)
201
201
202 return wrapper
202 return wrapper
203
203
204
204
205 moderncaps = {
205 moderncaps = {
206 b'lookup',
206 b'lookup',
207 b'branchmap',
207 b'branchmap',
208 b'pushkey',
208 b'pushkey',
209 b'known',
209 b'known',
210 b'getbundle',
210 b'getbundle',
211 b'unbundle',
211 b'unbundle',
212 }
212 }
213 legacycaps = moderncaps.union({b'changegroupsubset'})
213 legacycaps = moderncaps.union({b'changegroupsubset'})
214
214
215
215
216 @interfaceutil.implementer(repository.ipeercommandexecutor)
216 @interfaceutil.implementer(repository.ipeercommandexecutor)
217 class localcommandexecutor(object):
217 class localcommandexecutor(object):
218 def __init__(self, peer):
218 def __init__(self, peer):
219 self._peer = peer
219 self._peer = peer
220 self._sent = False
220 self._sent = False
221 self._closed = False
221 self._closed = False
222
222
223 def __enter__(self):
223 def __enter__(self):
224 return self
224 return self
225
225
226 def __exit__(self, exctype, excvalue, exctb):
226 def __exit__(self, exctype, excvalue, exctb):
227 self.close()
227 self.close()
228
228
229 def callcommand(self, command, args):
229 def callcommand(self, command, args):
230 if self._sent:
230 if self._sent:
231 raise error.ProgrammingError(
231 raise error.ProgrammingError(
232 b'callcommand() cannot be used after sendcommands()'
232 b'callcommand() cannot be used after sendcommands()'
233 )
233 )
234
234
235 if self._closed:
235 if self._closed:
236 raise error.ProgrammingError(
236 raise error.ProgrammingError(
237 b'callcommand() cannot be used after close()'
237 b'callcommand() cannot be used after close()'
238 )
238 )
239
239
240 # We don't need to support anything fancy. Just call the named
240 # We don't need to support anything fancy. Just call the named
241 # method on the peer and return a resolved future.
241 # method on the peer and return a resolved future.
242 fn = getattr(self._peer, pycompat.sysstr(command))
242 fn = getattr(self._peer, pycompat.sysstr(command))
243
243
244 f = pycompat.futures.Future()
244 f = pycompat.futures.Future()
245
245
246 try:
246 try:
247 result = fn(**pycompat.strkwargs(args))
247 result = fn(**pycompat.strkwargs(args))
248 except Exception:
248 except Exception:
249 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
249 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
250 else:
250 else:
251 f.set_result(result)
251 f.set_result(result)
252
252
253 return f
253 return f
254
254
255 def sendcommands(self):
255 def sendcommands(self):
256 self._sent = True
256 self._sent = True
257
257
258 def close(self):
258 def close(self):
259 self._closed = True
259 self._closed = True
260
260
261
261
262 @interfaceutil.implementer(repository.ipeercommands)
262 @interfaceutil.implementer(repository.ipeercommands)
263 class localpeer(repository.peer):
263 class localpeer(repository.peer):
264 '''peer for a local repo; reflects only the most recent API'''
264 '''peer for a local repo; reflects only the most recent API'''
265
265
266 def __init__(self, repo, caps=None):
266 def __init__(self, repo, caps=None):
267 super(localpeer, self).__init__()
267 super(localpeer, self).__init__()
268
268
269 if caps is None:
269 if caps is None:
270 caps = moderncaps.copy()
270 caps = moderncaps.copy()
271 self._repo = repo.filtered(b'served')
271 self._repo = repo.filtered(b'served')
272 self.ui = repo.ui
272 self.ui = repo.ui
273 self._caps = repo._restrictcapabilities(caps)
273 self._caps = repo._restrictcapabilities(caps)
274
274
275 # Begin of _basepeer interface.
275 # Begin of _basepeer interface.
276
276
277 def url(self):
277 def url(self):
278 return self._repo.url()
278 return self._repo.url()
279
279
280 def local(self):
280 def local(self):
281 return self._repo
281 return self._repo
282
282
283 def peer(self):
283 def peer(self):
284 return self
284 return self
285
285
286 def canpush(self):
286 def canpush(self):
287 return True
287 return True
288
288
289 def close(self):
289 def close(self):
290 self._repo.close()
290 self._repo.close()
291
291
292 # End of _basepeer interface.
292 # End of _basepeer interface.
293
293
294 # Begin of _basewirecommands interface.
294 # Begin of _basewirecommands interface.
295
295
296 def branchmap(self):
296 def branchmap(self):
297 return self._repo.branchmap()
297 return self._repo.branchmap()
298
298
299 def capabilities(self):
299 def capabilities(self):
300 return self._caps
300 return self._caps
301
301
302 def clonebundles(self):
302 def clonebundles(self):
303 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
303 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
304
304
305 def debugwireargs(self, one, two, three=None, four=None, five=None):
305 def debugwireargs(self, one, two, three=None, four=None, five=None):
306 """Used to test argument passing over the wire"""
306 """Used to test argument passing over the wire"""
307 return b"%s %s %s %s %s" % (
307 return b"%s %s %s %s %s" % (
308 one,
308 one,
309 two,
309 two,
310 pycompat.bytestr(three),
310 pycompat.bytestr(three),
311 pycompat.bytestr(four),
311 pycompat.bytestr(four),
312 pycompat.bytestr(five),
312 pycompat.bytestr(five),
313 )
313 )
314
314
315 def getbundle(
315 def getbundle(
316 self, source, heads=None, common=None, bundlecaps=None, **kwargs
316 self, source, heads=None, common=None, bundlecaps=None, **kwargs
317 ):
317 ):
318 chunks = exchange.getbundlechunks(
318 chunks = exchange.getbundlechunks(
319 self._repo,
319 self._repo,
320 source,
320 source,
321 heads=heads,
321 heads=heads,
322 common=common,
322 common=common,
323 bundlecaps=bundlecaps,
323 bundlecaps=bundlecaps,
324 **kwargs
324 **kwargs
325 )[1]
325 )[1]
326 cb = util.chunkbuffer(chunks)
326 cb = util.chunkbuffer(chunks)
327
327
328 if exchange.bundle2requested(bundlecaps):
328 if exchange.bundle2requested(bundlecaps):
329 # When requesting a bundle2, getbundle returns a stream to make the
329 # When requesting a bundle2, getbundle returns a stream to make the
330 # wire level function happier. We need to build a proper object
330 # wire level function happier. We need to build a proper object
331 # from it in local peer.
331 # from it in local peer.
332 return bundle2.getunbundler(self.ui, cb)
332 return bundle2.getunbundler(self.ui, cb)
333 else:
333 else:
334 return changegroup.getunbundler(b'01', cb, None)
334 return changegroup.getunbundler(b'01', cb, None)
335
335
336 def heads(self):
336 def heads(self):
337 return self._repo.heads()
337 return self._repo.heads()
338
338
339 def known(self, nodes):
339 def known(self, nodes):
340 return self._repo.known(nodes)
340 return self._repo.known(nodes)
341
341
342 def listkeys(self, namespace):
342 def listkeys(self, namespace):
343 return self._repo.listkeys(namespace)
343 return self._repo.listkeys(namespace)
344
344
345 def lookup(self, key):
345 def lookup(self, key):
346 return self._repo.lookup(key)
346 return self._repo.lookup(key)
347
347
348 def pushkey(self, namespace, key, old, new):
348 def pushkey(self, namespace, key, old, new):
349 return self._repo.pushkey(namespace, key, old, new)
349 return self._repo.pushkey(namespace, key, old, new)
350
350
351 def stream_out(self):
351 def stream_out(self):
352 raise error.Abort(_(b'cannot perform stream clone against local peer'))
352 raise error.Abort(_(b'cannot perform stream clone against local peer'))
353
353
354 def unbundle(self, bundle, heads, url):
354 def unbundle(self, bundle, heads, url):
355 """apply a bundle on a repo
355 """apply a bundle on a repo
356
356
357 This function handles the repo locking itself."""
357 This function handles the repo locking itself."""
358 try:
358 try:
359 try:
359 try:
360 bundle = exchange.readbundle(self.ui, bundle, None)
360 bundle = exchange.readbundle(self.ui, bundle, None)
361 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
361 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
362 if util.safehasattr(ret, b'getchunks'):
362 if util.safehasattr(ret, b'getchunks'):
363 # This is a bundle20 object, turn it into an unbundler.
363 # This is a bundle20 object, turn it into an unbundler.
364 # This little dance should be dropped eventually when the
364 # This little dance should be dropped eventually when the
365 # API is finally improved.
365 # API is finally improved.
366 stream = util.chunkbuffer(ret.getchunks())
366 stream = util.chunkbuffer(ret.getchunks())
367 ret = bundle2.getunbundler(self.ui, stream)
367 ret = bundle2.getunbundler(self.ui, stream)
368 return ret
368 return ret
369 except Exception as exc:
369 except Exception as exc:
370 # If the exception contains output salvaged from a bundle2
370 # If the exception contains output salvaged from a bundle2
371 # reply, we need to make sure it is printed before continuing
371 # reply, we need to make sure it is printed before continuing
372 # to fail. So we build a bundle2 with such output and consume
372 # to fail. So we build a bundle2 with such output and consume
373 # it directly.
373 # it directly.
374 #
374 #
375 # This is not very elegant but allows a "simple" solution for
375 # This is not very elegant but allows a "simple" solution for
376 # issue4594
376 # issue4594
377 output = getattr(exc, '_bundle2salvagedoutput', ())
377 output = getattr(exc, '_bundle2salvagedoutput', ())
378 if output:
378 if output:
379 bundler = bundle2.bundle20(self._repo.ui)
379 bundler = bundle2.bundle20(self._repo.ui)
380 for out in output:
380 for out in output:
381 bundler.addpart(out)
381 bundler.addpart(out)
382 stream = util.chunkbuffer(bundler.getchunks())
382 stream = util.chunkbuffer(bundler.getchunks())
383 b = bundle2.getunbundler(self.ui, stream)
383 b = bundle2.getunbundler(self.ui, stream)
384 bundle2.processbundle(self._repo, b)
384 bundle2.processbundle(self._repo, b)
385 raise
385 raise
386 except error.PushRaced as exc:
386 except error.PushRaced as exc:
387 raise error.ResponseError(
387 raise error.ResponseError(
388 _(b'push failed:'), stringutil.forcebytestr(exc)
388 _(b'push failed:'), stringutil.forcebytestr(exc)
389 )
389 )
390
390
391 # End of _basewirecommands interface.
391 # End of _basewirecommands interface.
392
392
393 # Begin of peer interface.
393 # Begin of peer interface.
394
394
395 def commandexecutor(self):
395 def commandexecutor(self):
396 return localcommandexecutor(self)
396 return localcommandexecutor(self)
397
397
398 # End of peer interface.
398 # End of peer interface.
399
399
400
400
401 @interfaceutil.implementer(repository.ipeerlegacycommands)
401 @interfaceutil.implementer(repository.ipeerlegacycommands)
402 class locallegacypeer(localpeer):
402 class locallegacypeer(localpeer):
403 '''peer extension which implements legacy methods too; used for tests with
403 '''peer extension which implements legacy methods too; used for tests with
404 restricted capabilities'''
404 restricted capabilities'''
405
405
406 def __init__(self, repo):
406 def __init__(self, repo):
407 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
407 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
408
408
409 # Begin of baselegacywirecommands interface.
409 # Begin of baselegacywirecommands interface.
410
410
411 def between(self, pairs):
411 def between(self, pairs):
412 return self._repo.between(pairs)
412 return self._repo.between(pairs)
413
413
414 def branches(self, nodes):
414 def branches(self, nodes):
415 return self._repo.branches(nodes)
415 return self._repo.branches(nodes)
416
416
417 def changegroup(self, nodes, source):
417 def changegroup(self, nodes, source):
418 outgoing = discovery.outgoing(
418 outgoing = discovery.outgoing(
419 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
419 self._repo, missingroots=nodes, ancestorsof=self._repo.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 def changegroupsubset(self, bases, heads, source):
423 def changegroupsubset(self, bases, heads, source):
424 outgoing = discovery.outgoing(
424 outgoing = discovery.outgoing(
425 self._repo, missingroots=bases, ancestorsof=heads
425 self._repo, missingroots=bases, ancestorsof=heads
426 )
426 )
427 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
427 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
428
428
429 # End of baselegacywirecommands interface.
429 # End of baselegacywirecommands interface.
430
430
431
431
432 # Functions receiving (ui, features) that extensions can register to impact
432 # Functions receiving (ui, features) that extensions can register to impact
433 # the ability to load repositories with custom requirements. Only
433 # the ability to load repositories with custom requirements. Only
434 # functions defined in loaded extensions are called.
434 # functions defined in loaded extensions are called.
435 #
435 #
436 # The function receives a set of requirement strings that the repository
436 # The function receives a set of requirement strings that the repository
437 # is capable of opening. Functions will typically add elements to the
437 # is capable of opening. Functions will typically add elements to the
438 # set to reflect that the extension knows how to handle that requirements.
438 # set to reflect that the extension knows how to handle that requirements.
439 featuresetupfuncs = set()
439 featuresetupfuncs = set()
440
440
441
441
442 def _getsharedvfs(hgvfs, requirements):
442 def _getsharedvfs(hgvfs, requirements):
443 """ returns the vfs object pointing to root of shared source
443 """ returns the vfs object pointing to root of shared source
444 repo for a shared repository
444 repo for a shared repository
445
445
446 hgvfs is vfs pointing at .hg/ of current repo (shared one)
446 hgvfs is vfs pointing at .hg/ of current repo (shared one)
447 requirements is a set of requirements of current repo (shared one)
447 requirements is a set of requirements of current repo (shared one)
448 """
448 """
449 # The ``shared`` or ``relshared`` requirements indicate the
449 # The ``shared`` or ``relshared`` requirements indicate the
450 # store lives in the path contained in the ``.hg/sharedpath`` file.
450 # store lives in the path contained in the ``.hg/sharedpath`` file.
451 # This is an absolute path for ``shared`` and relative to
451 # This is an absolute path for ``shared`` and relative to
452 # ``.hg/`` for ``relshared``.
452 # ``.hg/`` for ``relshared``.
453 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
453 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
454 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
454 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
455 sharedpath = hgvfs.join(sharedpath)
455 sharedpath = hgvfs.join(sharedpath)
456
456
457 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
457 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
458
458
459 if not sharedvfs.exists():
459 if not sharedvfs.exists():
460 raise error.RepoError(
460 raise error.RepoError(
461 _(b'.hg/sharedpath points to nonexistent directory %s')
461 _(b'.hg/sharedpath points to nonexistent directory %s')
462 % sharedvfs.base
462 % sharedvfs.base
463 )
463 )
464 return sharedvfs
464 return sharedvfs
465
465
466
466
467 def _readrequires(vfs, allowmissing):
467 def _readrequires(vfs, allowmissing):
468 """ reads the require file present at root of this vfs
468 """ reads the require file present at root of this vfs
469 and return a set of requirements
469 and return a set of requirements
470
470
471 If allowmissing is True, we suppress ENOENT if raised"""
471 If allowmissing is True, we suppress ENOENT if raised"""
472 # requires file contains a newline-delimited list of
472 # requires file contains a newline-delimited list of
473 # features/capabilities the opener (us) must have in order to use
473 # features/capabilities the opener (us) must have in order to use
474 # the repository. This file was introduced in Mercurial 0.9.2,
474 # the repository. This file was introduced in Mercurial 0.9.2,
475 # which means very old repositories may not have one. We assume
475 # which means very old repositories may not have one. We assume
476 # a missing file translates to no requirements.
476 # a missing file translates to no requirements.
477 try:
477 try:
478 requirements = set(vfs.read(b'requires').splitlines())
478 requirements = set(vfs.read(b'requires').splitlines())
479 except IOError as e:
479 except IOError as e:
480 if not (allowmissing and e.errno == errno.ENOENT):
480 if not (allowmissing and e.errno == errno.ENOENT):
481 raise
481 raise
482 requirements = set()
482 requirements = set()
483 return requirements
483 return requirements
484
484
485
485
486 def makelocalrepository(baseui, path, intents=None):
486 def makelocalrepository(baseui, path, intents=None):
487 """Create a local repository object.
487 """Create a local repository object.
488
488
489 Given arguments needed to construct a local repository, this function
489 Given arguments needed to construct a local repository, this function
490 performs various early repository loading functionality (such as
490 performs various early repository loading functionality (such as
491 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
491 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
492 the repository can be opened, derives a type suitable for representing
492 the repository can be opened, derives a type suitable for representing
493 that repository, and returns an instance of it.
493 that repository, and returns an instance of it.
494
494
495 The returned object conforms to the ``repository.completelocalrepository``
495 The returned object conforms to the ``repository.completelocalrepository``
496 interface.
496 interface.
497
497
498 The repository type is derived by calling a series of factory functions
498 The repository type is derived by calling a series of factory functions
499 for each aspect/interface of the final repository. These are defined by
499 for each aspect/interface of the final repository. These are defined by
500 ``REPO_INTERFACES``.
500 ``REPO_INTERFACES``.
501
501
502 Each factory function is called to produce a type implementing a specific
502 Each factory function is called to produce a type implementing a specific
503 interface. The cumulative list of returned types will be combined into a
503 interface. The cumulative list of returned types will be combined into a
504 new type and that type will be instantiated to represent the local
504 new type and that type will be instantiated to represent the local
505 repository.
505 repository.
506
506
507 The factory functions each receive various state that may be consulted
507 The factory functions each receive various state that may be consulted
508 as part of deriving a type.
508 as part of deriving a type.
509
509
510 Extensions should wrap these factory functions to customize repository type
510 Extensions should wrap these factory functions to customize repository type
511 creation. Note that an extension's wrapped function may be called even if
511 creation. Note that an extension's wrapped function may be called even if
512 that extension is not loaded for the repo being constructed. Extensions
512 that extension is not loaded for the repo being constructed. Extensions
513 should check if their ``__name__`` appears in the
513 should check if their ``__name__`` appears in the
514 ``extensionmodulenames`` set passed to the factory function and no-op if
514 ``extensionmodulenames`` set passed to the factory function and no-op if
515 not.
515 not.
516 """
516 """
517 ui = baseui.copy()
517 ui = baseui.copy()
518 # Prevent copying repo configuration.
518 # Prevent copying repo configuration.
519 ui.copy = baseui.copy
519 ui.copy = baseui.copy
520
520
521 # Working directory VFS rooted at repository root.
521 # Working directory VFS rooted at repository root.
522 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
522 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
523
523
524 # Main VFS for .hg/ directory.
524 # Main VFS for .hg/ directory.
525 hgpath = wdirvfs.join(b'.hg')
525 hgpath = wdirvfs.join(b'.hg')
526 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
526 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
527 # Whether this repository is shared one or not
527 # Whether this repository is shared one or not
528 shared = False
528 shared = False
529 # If this repository is shared, vfs pointing to shared repo
529 # If this repository is shared, vfs pointing to shared repo
530 sharedvfs = None
530 sharedvfs = None
531
531
532 # The .hg/ path should exist and should be a directory. All other
532 # The .hg/ path should exist and should be a directory. All other
533 # cases are errors.
533 # cases are errors.
534 if not hgvfs.isdir():
534 if not hgvfs.isdir():
535 try:
535 try:
536 hgvfs.stat()
536 hgvfs.stat()
537 except OSError as e:
537 except OSError as e:
538 if e.errno != errno.ENOENT:
538 if e.errno != errno.ENOENT:
539 raise
539 raise
540 except ValueError as e:
540 except ValueError as e:
541 # Can be raised on Python 3.8 when path is invalid.
541 # Can be raised on Python 3.8 when path is invalid.
542 raise error.Abort(
542 raise error.Abort(
543 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
543 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
544 )
544 )
545
545
546 raise error.RepoError(_(b'repository %s not found') % path)
546 raise error.RepoError(_(b'repository %s not found') % path)
547
547
548 requirements = _readrequires(hgvfs, True)
548 requirements = _readrequires(hgvfs, True)
549 shared = (
549 shared = (
550 requirementsmod.SHARED_REQUIREMENT in requirements
550 requirementsmod.SHARED_REQUIREMENT in requirements
551 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
551 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
552 )
552 )
553 if shared:
553 if shared:
554 sharedvfs = _getsharedvfs(hgvfs, requirements)
554 sharedvfs = _getsharedvfs(hgvfs, requirements)
555
555
556 # if .hg/requires contains the sharesafe requirement, it means
556 # if .hg/requires contains the sharesafe requirement, it means
557 # there exists a `.hg/store/requires` too and we should read it
557 # there exists a `.hg/store/requires` too and we should read it
558 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
558 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
559 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
559 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
560 # is not present, refer checkrequirementscompat() for that
560 # is not present, refer checkrequirementscompat() for that
561 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
561 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
562 if shared:
562 if shared:
563 # This is a shared repo
563 # This is a shared repo
564 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
564 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
565 else:
565 else:
566 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
566 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
567
567
568 requirements |= _readrequires(storevfs, False)
568 requirements |= _readrequires(storevfs, False)
569
569
570 # The .hg/hgrc file may load extensions or contain config options
570 # The .hg/hgrc file may load extensions or contain config options
571 # that influence repository construction. Attempt to load it and
571 # that influence repository construction. Attempt to load it and
572 # process any new extensions that it may have pulled in.
572 # process any new extensions that it may have pulled in.
573 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
573 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
574 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
574 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
575 extensions.loadall(ui)
575 extensions.loadall(ui)
576 extensions.populateui(ui)
576 extensions.populateui(ui)
577
577
578 # Set of module names of extensions loaded for this repository.
578 # Set of module names of extensions loaded for this repository.
579 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
579 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
580
580
581 supportedrequirements = gathersupportedrequirements(ui)
581 supportedrequirements = gathersupportedrequirements(ui)
582
582
583 # We first validate the requirements are known.
583 # We first validate the requirements are known.
584 ensurerequirementsrecognized(requirements, supportedrequirements)
584 ensurerequirementsrecognized(requirements, supportedrequirements)
585
585
586 # Then we validate that the known set is reasonable to use together.
586 # Then we validate that the known set is reasonable to use together.
587 ensurerequirementscompatible(ui, requirements)
587 ensurerequirementscompatible(ui, requirements)
588
588
589 # TODO there are unhandled edge cases related to opening repositories with
589 # TODO there are unhandled edge cases related to opening repositories with
590 # shared storage. If storage is shared, we should also test for requirements
590 # shared storage. If storage is shared, we should also test for requirements
591 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
591 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
592 # that repo, as that repo may load extensions needed to open it. This is a
592 # that repo, as that repo may load extensions needed to open it. This is a
593 # bit complicated because we don't want the other hgrc to overwrite settings
593 # bit complicated because we don't want the other hgrc to overwrite settings
594 # in this hgrc.
594 # in this hgrc.
595 #
595 #
596 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
596 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
597 # file when sharing repos. But if a requirement is added after the share is
597 # file when sharing repos. But if a requirement is added after the share is
598 # performed, thereby introducing a new requirement for the opener, we may
598 # performed, thereby introducing a new requirement for the opener, we may
599 # will not see that and could encounter a run-time error interacting with
599 # will not see that and could encounter a run-time error interacting with
600 # that shared store since it has an unknown-to-us requirement.
600 # that shared store since it has an unknown-to-us requirement.
601
601
602 # At this point, we know we should be capable of opening the repository.
602 # At this point, we know we should be capable of opening the repository.
603 # Now get on with doing that.
603 # Now get on with doing that.
604
604
605 features = set()
605 features = set()
606
606
607 # The "store" part of the repository holds versioned data. How it is
607 # The "store" part of the repository holds versioned data. How it is
608 # accessed is determined by various requirements. If `shared` or
608 # accessed is determined by various requirements. If `shared` or
609 # `relshared` requirements are present, this indicates current repository
609 # `relshared` requirements are present, this indicates current repository
610 # is a share and store exists in path mentioned in `.hg/sharedpath`
610 # is a share and store exists in path mentioned in `.hg/sharedpath`
611 if shared:
611 if shared:
612 storebasepath = sharedvfs.base
612 storebasepath = sharedvfs.base
613 cachepath = sharedvfs.join(b'cache')
613 cachepath = sharedvfs.join(b'cache')
614 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
614 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
615 else:
615 else:
616 storebasepath = hgvfs.base
616 storebasepath = hgvfs.base
617 cachepath = hgvfs.join(b'cache')
617 cachepath = hgvfs.join(b'cache')
618 wcachepath = hgvfs.join(b'wcache')
618 wcachepath = hgvfs.join(b'wcache')
619
619
620 # The store has changed over time and the exact layout is dictated by
620 # The store has changed over time and the exact layout is dictated by
621 # requirements. The store interface abstracts differences across all
621 # requirements. The store interface abstracts differences across all
622 # of them.
622 # of them.
623 store = makestore(
623 store = makestore(
624 requirements,
624 requirements,
625 storebasepath,
625 storebasepath,
626 lambda base: vfsmod.vfs(base, cacheaudited=True),
626 lambda base: vfsmod.vfs(base, cacheaudited=True),
627 )
627 )
628 hgvfs.createmode = store.createmode
628 hgvfs.createmode = store.createmode
629
629
630 storevfs = store.vfs
630 storevfs = store.vfs
631 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
631 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
632
632
633 # The cache vfs is used to manage cache files.
633 # The cache vfs is used to manage cache files.
634 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
634 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
635 cachevfs.createmode = store.createmode
635 cachevfs.createmode = store.createmode
636 # The cache vfs is used to manage cache files related to the working copy
636 # The cache vfs is used to manage cache files related to the working copy
637 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
637 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
638 wcachevfs.createmode = store.createmode
638 wcachevfs.createmode = store.createmode
639
639
640 # Now resolve the type for the repository object. We do this by repeatedly
640 # Now resolve the type for the repository object. We do this by repeatedly
641 # calling a factory function to produces types for specific aspects of the
641 # calling a factory function to produces types for specific aspects of the
642 # repo's operation. The aggregate returned types are used as base classes
642 # repo's operation. The aggregate returned types are used as base classes
643 # for a dynamically-derived type, which will represent our new repository.
643 # for a dynamically-derived type, which will represent our new repository.
644
644
645 bases = []
645 bases = []
646 extrastate = {}
646 extrastate = {}
647
647
648 for iface, fn in REPO_INTERFACES:
648 for iface, fn in REPO_INTERFACES:
649 # We pass all potentially useful state to give extensions tons of
649 # We pass all potentially useful state to give extensions tons of
650 # flexibility.
650 # flexibility.
651 typ = fn()(
651 typ = fn()(
652 ui=ui,
652 ui=ui,
653 intents=intents,
653 intents=intents,
654 requirements=requirements,
654 requirements=requirements,
655 features=features,
655 features=features,
656 wdirvfs=wdirvfs,
656 wdirvfs=wdirvfs,
657 hgvfs=hgvfs,
657 hgvfs=hgvfs,
658 store=store,
658 store=store,
659 storevfs=storevfs,
659 storevfs=storevfs,
660 storeoptions=storevfs.options,
660 storeoptions=storevfs.options,
661 cachevfs=cachevfs,
661 cachevfs=cachevfs,
662 wcachevfs=wcachevfs,
662 wcachevfs=wcachevfs,
663 extensionmodulenames=extensionmodulenames,
663 extensionmodulenames=extensionmodulenames,
664 extrastate=extrastate,
664 extrastate=extrastate,
665 baseclasses=bases,
665 baseclasses=bases,
666 )
666 )
667
667
668 if not isinstance(typ, type):
668 if not isinstance(typ, type):
669 raise error.ProgrammingError(
669 raise error.ProgrammingError(
670 b'unable to construct type for %s' % iface
670 b'unable to construct type for %s' % iface
671 )
671 )
672
672
673 bases.append(typ)
673 bases.append(typ)
674
674
675 # type() allows you to use characters in type names that wouldn't be
675 # type() allows you to use characters in type names that wouldn't be
676 # recognized as Python symbols in source code. We abuse that to add
676 # recognized as Python symbols in source code. We abuse that to add
677 # rich information about our constructed repo.
677 # rich information about our constructed repo.
678 name = pycompat.sysstr(
678 name = pycompat.sysstr(
679 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
679 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
680 )
680 )
681
681
682 cls = type(name, tuple(bases), {})
682 cls = type(name, tuple(bases), {})
683
683
684 return cls(
684 return cls(
685 baseui=baseui,
685 baseui=baseui,
686 ui=ui,
686 ui=ui,
687 origroot=path,
687 origroot=path,
688 wdirvfs=wdirvfs,
688 wdirvfs=wdirvfs,
689 hgvfs=hgvfs,
689 hgvfs=hgvfs,
690 requirements=requirements,
690 requirements=requirements,
691 supportedrequirements=supportedrequirements,
691 supportedrequirements=supportedrequirements,
692 sharedpath=storebasepath,
692 sharedpath=storebasepath,
693 store=store,
693 store=store,
694 cachevfs=cachevfs,
694 cachevfs=cachevfs,
695 wcachevfs=wcachevfs,
695 wcachevfs=wcachevfs,
696 features=features,
696 features=features,
697 intents=intents,
697 intents=intents,
698 )
698 )
699
699
700
700
701 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
701 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
702 """Load hgrc files/content into a ui instance.
702 """Load hgrc files/content into a ui instance.
703
703
704 This is called during repository opening to load any additional
704 This is called during repository opening to load any additional
705 config files or settings relevant to the current repository.
705 config files or settings relevant to the current repository.
706
706
707 Returns a bool indicating whether any additional configs were loaded.
707 Returns a bool indicating whether any additional configs were loaded.
708
708
709 Extensions should monkeypatch this function to modify how per-repo
709 Extensions should monkeypatch this function to modify how per-repo
710 configs are loaded. For example, an extension may wish to pull in
710 configs are loaded. For example, an extension may wish to pull in
711 configs from alternate files or sources.
711 configs from alternate files or sources.
712
712
713 sharedvfs is vfs object pointing to source repo if the current one is a
713 sharedvfs is vfs object pointing to source repo if the current one is a
714 shared one
714 shared one
715 """
715 """
716 if not rcutil.use_repo_hgrc():
716 if not rcutil.use_repo_hgrc():
717 return False
717 return False
718
718
719 ret = False
719 ret = False
720 # first load config from shared source if we has to
720 # first load config from shared source if we has to
721 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
721 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
722 try:
722 try:
723 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
723 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
724 ret = True
724 ret = True
725 except IOError:
725 except IOError:
726 pass
726 pass
727
727
728 try:
728 try:
729 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
729 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
730 ret = True
730 ret = True
731 except IOError:
731 except IOError:
732 pass
732 pass
733
733
734 try:
734 try:
735 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
735 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
736 ret = True
736 ret = True
737 except IOError:
737 except IOError:
738 pass
738 pass
739
739
740 return ret
740 return ret
741
741
742
742
743 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
743 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
744 """Perform additional actions after .hg/hgrc is loaded.
744 """Perform additional actions after .hg/hgrc is loaded.
745
745
746 This function is called during repository loading immediately after
746 This function is called during repository loading immediately after
747 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
747 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
748
748
749 The function can be used to validate configs, automatically add
749 The function can be used to validate configs, automatically add
750 options (including extensions) based on requirements, etc.
750 options (including extensions) based on requirements, etc.
751 """
751 """
752
752
753 # Map of requirements to list of extensions to load automatically when
753 # Map of requirements to list of extensions to load automatically when
754 # requirement is present.
754 # requirement is present.
755 autoextensions = {
755 autoextensions = {
756 b'git': [b'git'],
756 b'git': [b'git'],
757 b'largefiles': [b'largefiles'],
757 b'largefiles': [b'largefiles'],
758 b'lfs': [b'lfs'],
758 b'lfs': [b'lfs'],
759 }
759 }
760
760
761 for requirement, names in sorted(autoextensions.items()):
761 for requirement, names in sorted(autoextensions.items()):
762 if requirement not in requirements:
762 if requirement not in requirements:
763 continue
763 continue
764
764
765 for name in names:
765 for name in names:
766 if not ui.hasconfig(b'extensions', name):
766 if not ui.hasconfig(b'extensions', name):
767 ui.setconfig(b'extensions', name, b'', source=b'autoload')
767 ui.setconfig(b'extensions', name, b'', source=b'autoload')
768
768
769
769
770 def gathersupportedrequirements(ui):
770 def gathersupportedrequirements(ui):
771 """Determine the complete set of recognized requirements."""
771 """Determine the complete set of recognized requirements."""
772 # Start with all requirements supported by this file.
772 # Start with all requirements supported by this file.
773 supported = set(localrepository._basesupported)
773 supported = set(localrepository._basesupported)
774
774
775 # Execute ``featuresetupfuncs`` entries if they belong to an extension
775 # Execute ``featuresetupfuncs`` entries if they belong to an extension
776 # relevant to this ui instance.
776 # relevant to this ui instance.
777 modules = {m.__name__ for n, m in extensions.extensions(ui)}
777 modules = {m.__name__ for n, m in extensions.extensions(ui)}
778
778
779 for fn in featuresetupfuncs:
779 for fn in featuresetupfuncs:
780 if fn.__module__ in modules:
780 if fn.__module__ in modules:
781 fn(ui, supported)
781 fn(ui, supported)
782
782
783 # Add derived requirements from registered compression engines.
783 # Add derived requirements from registered compression engines.
784 for name in util.compengines:
784 for name in util.compengines:
785 engine = util.compengines[name]
785 engine = util.compengines[name]
786 if engine.available() and engine.revlogheader():
786 if engine.available() and engine.revlogheader():
787 supported.add(b'exp-compression-%s' % name)
787 supported.add(b'exp-compression-%s' % name)
788 if engine.name() == b'zstd':
788 if engine.name() == b'zstd':
789 supported.add(b'revlog-compression-zstd')
789 supported.add(b'revlog-compression-zstd')
790
790
791 return supported
791 return supported
792
792
793
793
794 def ensurerequirementsrecognized(requirements, supported):
794 def ensurerequirementsrecognized(requirements, supported):
795 """Validate that a set of local requirements is recognized.
795 """Validate that a set of local requirements is recognized.
796
796
797 Receives a set of requirements. Raises an ``error.RepoError`` if there
797 Receives a set of requirements. Raises an ``error.RepoError`` if there
798 exists any requirement in that set that currently loaded code doesn't
798 exists any requirement in that set that currently loaded code doesn't
799 recognize.
799 recognize.
800
800
801 Returns a set of supported requirements.
801 Returns a set of supported requirements.
802 """
802 """
803 missing = set()
803 missing = set()
804
804
805 for requirement in requirements:
805 for requirement in requirements:
806 if requirement in supported:
806 if requirement in supported:
807 continue
807 continue
808
808
809 if not requirement or not requirement[0:1].isalnum():
809 if not requirement or not requirement[0:1].isalnum():
810 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
810 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
811
811
812 missing.add(requirement)
812 missing.add(requirement)
813
813
814 if missing:
814 if missing:
815 raise error.RequirementError(
815 raise error.RequirementError(
816 _(b'repository requires features unknown to this Mercurial: %s')
816 _(b'repository requires features unknown to this Mercurial: %s')
817 % b' '.join(sorted(missing)),
817 % b' '.join(sorted(missing)),
818 hint=_(
818 hint=_(
819 b'see https://mercurial-scm.org/wiki/MissingRequirement '
819 b'see https://mercurial-scm.org/wiki/MissingRequirement '
820 b'for more information'
820 b'for more information'
821 ),
821 ),
822 )
822 )
823
823
824
824
825 def ensurerequirementscompatible(ui, requirements):
825 def ensurerequirementscompatible(ui, requirements):
826 """Validates that a set of recognized requirements is mutually compatible.
826 """Validates that a set of recognized requirements is mutually compatible.
827
827
828 Some requirements may not be compatible with others or require
828 Some requirements may not be compatible with others or require
829 config options that aren't enabled. This function is called during
829 config options that aren't enabled. This function is called during
830 repository opening to ensure that the set of requirements needed
830 repository opening to ensure that the set of requirements needed
831 to open a repository is sane and compatible with config options.
831 to open a repository is sane and compatible with config options.
832
832
833 Extensions can monkeypatch this function to perform additional
833 Extensions can monkeypatch this function to perform additional
834 checking.
834 checking.
835
835
836 ``error.RepoError`` should be raised on failure.
836 ``error.RepoError`` should be raised on failure.
837 """
837 """
838 if (
838 if (
839 requirementsmod.SPARSE_REQUIREMENT in requirements
839 requirementsmod.SPARSE_REQUIREMENT in requirements
840 and not sparse.enabled
840 and not sparse.enabled
841 ):
841 ):
842 raise error.RepoError(
842 raise error.RepoError(
843 _(
843 _(
844 b'repository is using sparse feature but '
844 b'repository is using sparse feature but '
845 b'sparse is not enabled; enable the '
845 b'sparse is not enabled; enable the '
846 b'"sparse" extensions to access'
846 b'"sparse" extensions to access'
847 )
847 )
848 )
848 )
849
849
850
850
851 def makestore(requirements, path, vfstype):
851 def makestore(requirements, path, vfstype):
852 """Construct a storage object for a repository."""
852 """Construct a storage object for a repository."""
853 if b'store' in requirements:
853 if b'store' in requirements:
854 if b'fncache' in requirements:
854 if b'fncache' in requirements:
855 return storemod.fncachestore(
855 return storemod.fncachestore(
856 path, vfstype, b'dotencode' in requirements
856 path, vfstype, b'dotencode' in requirements
857 )
857 )
858
858
859 return storemod.encodedstore(path, vfstype)
859 return storemod.encodedstore(path, vfstype)
860
860
861 return storemod.basicstore(path, vfstype)
861 return storemod.basicstore(path, vfstype)
862
862
863
863
864 def resolvestorevfsoptions(ui, requirements, features):
864 def resolvestorevfsoptions(ui, requirements, features):
865 """Resolve the options to pass to the store vfs opener.
865 """Resolve the options to pass to the store vfs opener.
866
866
867 The returned dict is used to influence behavior of the storage layer.
867 The returned dict is used to influence behavior of the storage layer.
868 """
868 """
869 options = {}
869 options = {}
870
870
871 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
871 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
872 options[b'treemanifest'] = True
872 options[b'treemanifest'] = True
873
873
874 # experimental config: format.manifestcachesize
874 # experimental config: format.manifestcachesize
875 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
875 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
876 if manifestcachesize is not None:
876 if manifestcachesize is not None:
877 options[b'manifestcachesize'] = manifestcachesize
877 options[b'manifestcachesize'] = manifestcachesize
878
878
879 # In the absence of another requirement superseding a revlog-related
879 # In the absence of another requirement superseding a revlog-related
880 # requirement, we have to assume the repo is using revlog version 0.
880 # requirement, we have to assume the repo is using revlog version 0.
881 # This revlog format is super old and we don't bother trying to parse
881 # This revlog format is super old and we don't bother trying to parse
882 # opener options for it because those options wouldn't do anything
882 # opener options for it because those options wouldn't do anything
883 # meaningful on such old repos.
883 # meaningful on such old repos.
884 if (
884 if (
885 b'revlogv1' in requirements
885 b'revlogv1' in requirements
886 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
886 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
887 ):
887 ):
888 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
888 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
889 else: # explicitly mark repo as using revlogv0
889 else: # explicitly mark repo as using revlogv0
890 options[b'revlogv0'] = True
890 options[b'revlogv0'] = True
891
891
892 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
892 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
893 options[b'copies-storage'] = b'changeset-sidedata'
893 options[b'copies-storage'] = b'changeset-sidedata'
894 else:
894 else:
895 writecopiesto = ui.config(b'experimental', b'copies.write-to')
895 writecopiesto = ui.config(b'experimental', b'copies.write-to')
896 copiesextramode = (b'changeset-only', b'compatibility')
896 copiesextramode = (b'changeset-only', b'compatibility')
897 if writecopiesto in copiesextramode:
897 if writecopiesto in copiesextramode:
898 options[b'copies-storage'] = b'extra'
898 options[b'copies-storage'] = b'extra'
899
899
900 return options
900 return options
901
901
902
902
903 def resolverevlogstorevfsoptions(ui, requirements, features):
903 def resolverevlogstorevfsoptions(ui, requirements, features):
904 """Resolve opener options specific to revlogs."""
904 """Resolve opener options specific to revlogs."""
905
905
906 options = {}
906 options = {}
907 options[b'flagprocessors'] = {}
907 options[b'flagprocessors'] = {}
908
908
909 if b'revlogv1' in requirements:
909 if b'revlogv1' in requirements:
910 options[b'revlogv1'] = True
910 options[b'revlogv1'] = True
911 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
911 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
912 options[b'revlogv2'] = True
912 options[b'revlogv2'] = True
913
913
914 if b'generaldelta' in requirements:
914 if b'generaldelta' in requirements:
915 options[b'generaldelta'] = True
915 options[b'generaldelta'] = True
916
916
917 # experimental config: format.chunkcachesize
917 # experimental config: format.chunkcachesize
918 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
918 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
919 if chunkcachesize is not None:
919 if chunkcachesize is not None:
920 options[b'chunkcachesize'] = chunkcachesize
920 options[b'chunkcachesize'] = chunkcachesize
921
921
922 deltabothparents = ui.configbool(
922 deltabothparents = ui.configbool(
923 b'storage', b'revlog.optimize-delta-parent-choice'
923 b'storage', b'revlog.optimize-delta-parent-choice'
924 )
924 )
925 options[b'deltabothparents'] = deltabothparents
925 options[b'deltabothparents'] = deltabothparents
926
926
927 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
927 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
928 lazydeltabase = False
928 lazydeltabase = False
929 if lazydelta:
929 if lazydelta:
930 lazydeltabase = ui.configbool(
930 lazydeltabase = ui.configbool(
931 b'storage', b'revlog.reuse-external-delta-parent'
931 b'storage', b'revlog.reuse-external-delta-parent'
932 )
932 )
933 if lazydeltabase is None:
933 if lazydeltabase is None:
934 lazydeltabase = not scmutil.gddeltaconfig(ui)
934 lazydeltabase = not scmutil.gddeltaconfig(ui)
935 options[b'lazydelta'] = lazydelta
935 options[b'lazydelta'] = lazydelta
936 options[b'lazydeltabase'] = lazydeltabase
936 options[b'lazydeltabase'] = lazydeltabase
937
937
938 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
938 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
939 if 0 <= chainspan:
939 if 0 <= chainspan:
940 options[b'maxdeltachainspan'] = chainspan
940 options[b'maxdeltachainspan'] = chainspan
941
941
942 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
942 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
943 if mmapindexthreshold is not None:
943 if mmapindexthreshold is not None:
944 options[b'mmapindexthreshold'] = mmapindexthreshold
944 options[b'mmapindexthreshold'] = mmapindexthreshold
945
945
946 withsparseread = ui.configbool(b'experimental', b'sparse-read')
946 withsparseread = ui.configbool(b'experimental', b'sparse-read')
947 srdensitythres = float(
947 srdensitythres = float(
948 ui.config(b'experimental', b'sparse-read.density-threshold')
948 ui.config(b'experimental', b'sparse-read.density-threshold')
949 )
949 )
950 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
950 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
951 options[b'with-sparse-read'] = withsparseread
951 options[b'with-sparse-read'] = withsparseread
952 options[b'sparse-read-density-threshold'] = srdensitythres
952 options[b'sparse-read-density-threshold'] = srdensitythres
953 options[b'sparse-read-min-gap-size'] = srmingapsize
953 options[b'sparse-read-min-gap-size'] = srmingapsize
954
954
955 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
955 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
956 options[b'sparse-revlog'] = sparserevlog
956 options[b'sparse-revlog'] = sparserevlog
957 if sparserevlog:
957 if sparserevlog:
958 options[b'generaldelta'] = True
958 options[b'generaldelta'] = True
959
959
960 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
960 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
961 options[b'side-data'] = sidedata
961 options[b'side-data'] = sidedata
962
962
963 maxchainlen = None
963 maxchainlen = None
964 if sparserevlog:
964 if sparserevlog:
965 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
965 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
966 # experimental config: format.maxchainlen
966 # experimental config: format.maxchainlen
967 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
967 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
968 if maxchainlen is not None:
968 if maxchainlen is not None:
969 options[b'maxchainlen'] = maxchainlen
969 options[b'maxchainlen'] = maxchainlen
970
970
971 for r in requirements:
971 for r in requirements:
972 # we allow multiple compression engine requirement to co-exist because
972 # we allow multiple compression engine requirement to co-exist because
973 # strickly speaking, revlog seems to support mixed compression style.
973 # strickly speaking, revlog seems to support mixed compression style.
974 #
974 #
975 # The compression used for new entries will be "the last one"
975 # The compression used for new entries will be "the last one"
976 prefix = r.startswith
976 prefix = r.startswith
977 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
977 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
978 options[b'compengine'] = r.split(b'-', 2)[2]
978 options[b'compengine'] = r.split(b'-', 2)[2]
979
979
980 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
980 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
981 if options[b'zlib.level'] is not None:
981 if options[b'zlib.level'] is not None:
982 if not (0 <= options[b'zlib.level'] <= 9):
982 if not (0 <= options[b'zlib.level'] <= 9):
983 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
983 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
984 raise error.Abort(msg % options[b'zlib.level'])
984 raise error.Abort(msg % options[b'zlib.level'])
985 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
985 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
986 if options[b'zstd.level'] is not None:
986 if options[b'zstd.level'] is not None:
987 if not (0 <= options[b'zstd.level'] <= 22):
987 if not (0 <= options[b'zstd.level'] <= 22):
988 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
988 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
989 raise error.Abort(msg % options[b'zstd.level'])
989 raise error.Abort(msg % options[b'zstd.level'])
990
990
991 if requirementsmod.NARROW_REQUIREMENT in requirements:
991 if requirementsmod.NARROW_REQUIREMENT in requirements:
992 options[b'enableellipsis'] = True
992 options[b'enableellipsis'] = True
993
993
994 if ui.configbool(b'experimental', b'rust.index'):
994 if ui.configbool(b'experimental', b'rust.index'):
995 options[b'rust.index'] = True
995 options[b'rust.index'] = True
996 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
996 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
997 options[b'persistent-nodemap'] = True
997 options[b'persistent-nodemap'] = True
998 if ui.configbool(b'storage', b'revlog.nodemap.mmap'):
998 if ui.configbool(b'storage', b'revlog.nodemap.mmap'):
999 options[b'persistent-nodemap.mmap'] = True
999 options[b'persistent-nodemap.mmap'] = True
1000 epnm = ui.config(b'storage', b'revlog.nodemap.mode')
1000 epnm = ui.config(b'storage', b'revlog.nodemap.mode')
1001 options[b'persistent-nodemap.mode'] = epnm
1001 options[b'persistent-nodemap.mode'] = epnm
1002 if ui.configbool(b'devel', b'persistent-nodemap'):
1002 if ui.configbool(b'devel', b'persistent-nodemap'):
1003 options[b'devel-force-nodemap'] = True
1003 options[b'devel-force-nodemap'] = True
1004
1004
1005 return options
1005 return options
1006
1006
1007
1007
1008 def makemain(**kwargs):
1008 def makemain(**kwargs):
1009 """Produce a type conforming to ``ilocalrepositorymain``."""
1009 """Produce a type conforming to ``ilocalrepositorymain``."""
1010 return localrepository
1010 return localrepository
1011
1011
1012
1012
1013 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1013 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1014 class revlogfilestorage(object):
1014 class revlogfilestorage(object):
1015 """File storage when using revlogs."""
1015 """File storage when using revlogs."""
1016
1016
1017 def file(self, path):
1017 def file(self, path):
1018 if path[0] == b'/':
1018 if path[0] == b'/':
1019 path = path[1:]
1019 path = path[1:]
1020
1020
1021 return filelog.filelog(self.svfs, path)
1021 return filelog.filelog(self.svfs, path)
1022
1022
1023
1023
1024 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1024 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1025 class revlognarrowfilestorage(object):
1025 class revlognarrowfilestorage(object):
1026 """File storage when using revlogs and narrow files."""
1026 """File storage when using revlogs and narrow files."""
1027
1027
1028 def file(self, path):
1028 def file(self, path):
1029 if path[0] == b'/':
1029 if path[0] == b'/':
1030 path = path[1:]
1030 path = path[1:]
1031
1031
1032 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1032 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1033
1033
1034
1034
1035 def makefilestorage(requirements, features, **kwargs):
1035 def makefilestorage(requirements, features, **kwargs):
1036 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1036 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1037 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1037 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1038 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1038 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1039
1039
1040 if requirementsmod.NARROW_REQUIREMENT in requirements:
1040 if requirementsmod.NARROW_REQUIREMENT in requirements:
1041 return revlognarrowfilestorage
1041 return revlognarrowfilestorage
1042 else:
1042 else:
1043 return revlogfilestorage
1043 return revlogfilestorage
1044
1044
1045
1045
1046 # List of repository interfaces and factory functions for them. Each
1046 # List of repository interfaces and factory functions for them. Each
1047 # will be called in order during ``makelocalrepository()`` to iteratively
1047 # will be called in order during ``makelocalrepository()`` to iteratively
1048 # derive the final type for a local repository instance. We capture the
1048 # derive the final type for a local repository instance. We capture the
1049 # function as a lambda so we don't hold a reference and the module-level
1049 # function as a lambda so we don't hold a reference and the module-level
1050 # functions can be wrapped.
1050 # functions can be wrapped.
1051 REPO_INTERFACES = [
1051 REPO_INTERFACES = [
1052 (repository.ilocalrepositorymain, lambda: makemain),
1052 (repository.ilocalrepositorymain, lambda: makemain),
1053 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1053 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1054 ]
1054 ]
1055
1055
1056
1056
1057 @interfaceutil.implementer(repository.ilocalrepositorymain)
1057 @interfaceutil.implementer(repository.ilocalrepositorymain)
1058 class localrepository(object):
1058 class localrepository(object):
1059 """Main class for representing local repositories.
1059 """Main class for representing local repositories.
1060
1060
1061 All local repositories are instances of this class.
1061 All local repositories are instances of this class.
1062
1062
1063 Constructed on its own, instances of this class are not usable as
1063 Constructed on its own, instances of this class are not usable as
1064 repository objects. To obtain a usable repository object, call
1064 repository objects. To obtain a usable repository object, call
1065 ``hg.repository()``, ``localrepo.instance()``, or
1065 ``hg.repository()``, ``localrepo.instance()``, or
1066 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1066 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1067 ``instance()`` adds support for creating new repositories.
1067 ``instance()`` adds support for creating new repositories.
1068 ``hg.repository()`` adds more extension integration, including calling
1068 ``hg.repository()`` adds more extension integration, including calling
1069 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1069 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1070 used.
1070 used.
1071 """
1071 """
1072
1072
1073 # obsolete experimental requirements:
1073 # obsolete experimental requirements:
1074 # - manifestv2: An experimental new manifest format that allowed
1074 # - manifestv2: An experimental new manifest format that allowed
1075 # for stem compression of long paths. Experiment ended up not
1075 # for stem compression of long paths. Experiment ended up not
1076 # being successful (repository sizes went up due to worse delta
1076 # being successful (repository sizes went up due to worse delta
1077 # chains), and the code was deleted in 4.6.
1077 # chains), and the code was deleted in 4.6.
1078 supportedformats = {
1078 supportedformats = {
1079 b'revlogv1',
1079 b'revlogv1',
1080 b'generaldelta',
1080 b'generaldelta',
1081 requirementsmod.TREEMANIFEST_REQUIREMENT,
1081 requirementsmod.TREEMANIFEST_REQUIREMENT,
1082 requirementsmod.COPIESSDC_REQUIREMENT,
1082 requirementsmod.COPIESSDC_REQUIREMENT,
1083 requirementsmod.REVLOGV2_REQUIREMENT,
1083 requirementsmod.REVLOGV2_REQUIREMENT,
1084 requirementsmod.SIDEDATA_REQUIREMENT,
1084 requirementsmod.SIDEDATA_REQUIREMENT,
1085 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1085 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1086 requirementsmod.NODEMAP_REQUIREMENT,
1086 requirementsmod.NODEMAP_REQUIREMENT,
1087 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1087 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1088 requirementsmod.SHARESAFE_REQUIREMENT,
1088 requirementsmod.SHARESAFE_REQUIREMENT,
1089 }
1089 }
1090 _basesupported = supportedformats | {
1090 _basesupported = supportedformats | {
1091 b'store',
1091 b'store',
1092 b'fncache',
1092 b'fncache',
1093 requirementsmod.SHARED_REQUIREMENT,
1093 requirementsmod.SHARED_REQUIREMENT,
1094 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1094 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1095 b'dotencode',
1095 b'dotencode',
1096 requirementsmod.SPARSE_REQUIREMENT,
1096 requirementsmod.SPARSE_REQUIREMENT,
1097 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1097 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1098 }
1098 }
1099
1099
1100 # list of prefix for file which can be written without 'wlock'
1100 # list of prefix for file which can be written without 'wlock'
1101 # Extensions should extend this list when needed
1101 # Extensions should extend this list when needed
1102 _wlockfreeprefix = {
1102 _wlockfreeprefix = {
1103 # We migh consider requiring 'wlock' for the next
1103 # We migh consider requiring 'wlock' for the next
1104 # two, but pretty much all the existing code assume
1104 # two, but pretty much all the existing code assume
1105 # wlock is not needed so we keep them excluded for
1105 # wlock is not needed so we keep them excluded for
1106 # now.
1106 # now.
1107 b'hgrc',
1107 b'hgrc',
1108 b'requires',
1108 b'requires',
1109 # XXX cache is a complicatged business someone
1109 # XXX cache is a complicatged business someone
1110 # should investigate this in depth at some point
1110 # should investigate this in depth at some point
1111 b'cache/',
1111 b'cache/',
1112 # XXX shouldn't be dirstate covered by the wlock?
1112 # XXX shouldn't be dirstate covered by the wlock?
1113 b'dirstate',
1113 b'dirstate',
1114 # XXX bisect was still a bit too messy at the time
1114 # XXX bisect was still a bit too messy at the time
1115 # this changeset was introduced. Someone should fix
1115 # this changeset was introduced. Someone should fix
1116 # the remainig bit and drop this line
1116 # the remainig bit and drop this line
1117 b'bisect.state',
1117 b'bisect.state',
1118 }
1118 }
1119
1119
1120 def __init__(
1120 def __init__(
1121 self,
1121 self,
1122 baseui,
1122 baseui,
1123 ui,
1123 ui,
1124 origroot,
1124 origroot,
1125 wdirvfs,
1125 wdirvfs,
1126 hgvfs,
1126 hgvfs,
1127 requirements,
1127 requirements,
1128 supportedrequirements,
1128 supportedrequirements,
1129 sharedpath,
1129 sharedpath,
1130 store,
1130 store,
1131 cachevfs,
1131 cachevfs,
1132 wcachevfs,
1132 wcachevfs,
1133 features,
1133 features,
1134 intents=None,
1134 intents=None,
1135 ):
1135 ):
1136 """Create a new local repository instance.
1136 """Create a new local repository instance.
1137
1137
1138 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1138 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1139 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1139 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1140 object.
1140 object.
1141
1141
1142 Arguments:
1142 Arguments:
1143
1143
1144 baseui
1144 baseui
1145 ``ui.ui`` instance that ``ui`` argument was based off of.
1145 ``ui.ui`` instance that ``ui`` argument was based off of.
1146
1146
1147 ui
1147 ui
1148 ``ui.ui`` instance for use by the repository.
1148 ``ui.ui`` instance for use by the repository.
1149
1149
1150 origroot
1150 origroot
1151 ``bytes`` path to working directory root of this repository.
1151 ``bytes`` path to working directory root of this repository.
1152
1152
1153 wdirvfs
1153 wdirvfs
1154 ``vfs.vfs`` rooted at the working directory.
1154 ``vfs.vfs`` rooted at the working directory.
1155
1155
1156 hgvfs
1156 hgvfs
1157 ``vfs.vfs`` rooted at .hg/
1157 ``vfs.vfs`` rooted at .hg/
1158
1158
1159 requirements
1159 requirements
1160 ``set`` of bytestrings representing repository opening requirements.
1160 ``set`` of bytestrings representing repository opening requirements.
1161
1161
1162 supportedrequirements
1162 supportedrequirements
1163 ``set`` of bytestrings representing repository requirements that we
1163 ``set`` of bytestrings representing repository requirements that we
1164 know how to open. May be a supetset of ``requirements``.
1164 know how to open. May be a supetset of ``requirements``.
1165
1165
1166 sharedpath
1166 sharedpath
1167 ``bytes`` Defining path to storage base directory. Points to a
1167 ``bytes`` Defining path to storage base directory. Points to a
1168 ``.hg/`` directory somewhere.
1168 ``.hg/`` directory somewhere.
1169
1169
1170 store
1170 store
1171 ``store.basicstore`` (or derived) instance providing access to
1171 ``store.basicstore`` (or derived) instance providing access to
1172 versioned storage.
1172 versioned storage.
1173
1173
1174 cachevfs
1174 cachevfs
1175 ``vfs.vfs`` used for cache files.
1175 ``vfs.vfs`` used for cache files.
1176
1176
1177 wcachevfs
1177 wcachevfs
1178 ``vfs.vfs`` used for cache files related to the working copy.
1178 ``vfs.vfs`` used for cache files related to the working copy.
1179
1179
1180 features
1180 features
1181 ``set`` of bytestrings defining features/capabilities of this
1181 ``set`` of bytestrings defining features/capabilities of this
1182 instance.
1182 instance.
1183
1183
1184 intents
1184 intents
1185 ``set`` of system strings indicating what this repo will be used
1185 ``set`` of system strings indicating what this repo will be used
1186 for.
1186 for.
1187 """
1187 """
1188 self.baseui = baseui
1188 self.baseui = baseui
1189 self.ui = ui
1189 self.ui = ui
1190 self.origroot = origroot
1190 self.origroot = origroot
1191 # vfs rooted at working directory.
1191 # vfs rooted at working directory.
1192 self.wvfs = wdirvfs
1192 self.wvfs = wdirvfs
1193 self.root = wdirvfs.base
1193 self.root = wdirvfs.base
1194 # vfs rooted at .hg/. Used to access most non-store paths.
1194 # vfs rooted at .hg/. Used to access most non-store paths.
1195 self.vfs = hgvfs
1195 self.vfs = hgvfs
1196 self.path = hgvfs.base
1196 self.path = hgvfs.base
1197 self.requirements = requirements
1197 self.requirements = requirements
1198 self.supported = supportedrequirements
1198 self.supported = supportedrequirements
1199 self.sharedpath = sharedpath
1199 self.sharedpath = sharedpath
1200 self.store = store
1200 self.store = store
1201 self.cachevfs = cachevfs
1201 self.cachevfs = cachevfs
1202 self.wcachevfs = wcachevfs
1202 self.wcachevfs = wcachevfs
1203 self.features = features
1203 self.features = features
1204
1204
1205 self.filtername = None
1205 self.filtername = None
1206
1206
1207 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1207 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1208 b'devel', b'check-locks'
1208 b'devel', b'check-locks'
1209 ):
1209 ):
1210 self.vfs.audit = self._getvfsward(self.vfs.audit)
1210 self.vfs.audit = self._getvfsward(self.vfs.audit)
1211 # A list of callback to shape the phase if no data were found.
1211 # A list of callback to shape the phase if no data were found.
1212 # Callback are in the form: func(repo, roots) --> processed root.
1212 # Callback are in the form: func(repo, roots) --> processed root.
1213 # This list it to be filled by extension during repo setup
1213 # This list it to be filled by extension during repo setup
1214 self._phasedefaults = []
1214 self._phasedefaults = []
1215
1215
1216 color.setup(self.ui)
1216 color.setup(self.ui)
1217
1217
1218 self.spath = self.store.path
1218 self.spath = self.store.path
1219 self.svfs = self.store.vfs
1219 self.svfs = self.store.vfs
1220 self.sjoin = self.store.join
1220 self.sjoin = self.store.join
1221 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1221 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1222 b'devel', b'check-locks'
1222 b'devel', b'check-locks'
1223 ):
1223 ):
1224 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1224 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1225 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1225 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1226 else: # standard vfs
1226 else: # standard vfs
1227 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1227 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1228
1228
1229 self._dirstatevalidatewarned = False
1229 self._dirstatevalidatewarned = False
1230
1230
1231 self._branchcaches = branchmap.BranchMapCache()
1231 self._branchcaches = branchmap.BranchMapCache()
1232 self._revbranchcache = None
1232 self._revbranchcache = None
1233 self._filterpats = {}
1233 self._filterpats = {}
1234 self._datafilters = {}
1234 self._datafilters = {}
1235 self._transref = self._lockref = self._wlockref = None
1235 self._transref = self._lockref = self._wlockref = None
1236
1236
1237 # A cache for various files under .hg/ that tracks file changes,
1237 # A cache for various files under .hg/ that tracks file changes,
1238 # (used by the filecache decorator)
1238 # (used by the filecache decorator)
1239 #
1239 #
1240 # Maps a property name to its util.filecacheentry
1240 # Maps a property name to its util.filecacheentry
1241 self._filecache = {}
1241 self._filecache = {}
1242
1242
1243 # hold sets of revision to be filtered
1243 # hold sets of revision to be filtered
1244 # should be cleared when something might have changed the filter value:
1244 # should be cleared when something might have changed the filter value:
1245 # - new changesets,
1245 # - new changesets,
1246 # - phase change,
1246 # - phase change,
1247 # - new obsolescence marker,
1247 # - new obsolescence marker,
1248 # - working directory parent change,
1248 # - working directory parent change,
1249 # - bookmark changes
1249 # - bookmark changes
1250 self.filteredrevcache = {}
1250 self.filteredrevcache = {}
1251
1251
1252 # post-dirstate-status hooks
1252 # post-dirstate-status hooks
1253 self._postdsstatus = []
1253 self._postdsstatus = []
1254
1254
1255 # generic mapping between names and nodes
1255 # generic mapping between names and nodes
1256 self.names = namespaces.namespaces()
1256 self.names = namespaces.namespaces()
1257
1257
1258 # Key to signature value.
1258 # Key to signature value.
1259 self._sparsesignaturecache = {}
1259 self._sparsesignaturecache = {}
1260 # Signature to cached matcher instance.
1260 # Signature to cached matcher instance.
1261 self._sparsematchercache = {}
1261 self._sparsematchercache = {}
1262
1262
1263 self._extrafilterid = repoview.extrafilter(ui)
1263 self._extrafilterid = repoview.extrafilter(ui)
1264
1264
1265 self.filecopiesmode = None
1265 self.filecopiesmode = None
1266 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1266 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1267 self.filecopiesmode = b'changeset-sidedata'
1267 self.filecopiesmode = b'changeset-sidedata'
1268
1268
1269 def _getvfsward(self, origfunc):
1269 def _getvfsward(self, origfunc):
1270 """build a ward for self.vfs"""
1270 """build a ward for self.vfs"""
1271 rref = weakref.ref(self)
1271 rref = weakref.ref(self)
1272
1272
1273 def checkvfs(path, mode=None):
1273 def checkvfs(path, mode=None):
1274 ret = origfunc(path, mode=mode)
1274 ret = origfunc(path, mode=mode)
1275 repo = rref()
1275 repo = rref()
1276 if (
1276 if (
1277 repo is None
1277 repo is None
1278 or not util.safehasattr(repo, b'_wlockref')
1278 or not util.safehasattr(repo, b'_wlockref')
1279 or not util.safehasattr(repo, b'_lockref')
1279 or not util.safehasattr(repo, b'_lockref')
1280 ):
1280 ):
1281 return
1281 return
1282 if mode in (None, b'r', b'rb'):
1282 if mode in (None, b'r', b'rb'):
1283 return
1283 return
1284 if path.startswith(repo.path):
1284 if path.startswith(repo.path):
1285 # truncate name relative to the repository (.hg)
1285 # truncate name relative to the repository (.hg)
1286 path = path[len(repo.path) + 1 :]
1286 path = path[len(repo.path) + 1 :]
1287 if path.startswith(b'cache/'):
1287 if path.startswith(b'cache/'):
1288 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1288 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1289 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1289 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1290 # path prefixes covered by 'lock'
1290 # path prefixes covered by 'lock'
1291 vfs_path_prefixes = (
1291 vfs_path_prefixes = (
1292 b'journal.',
1292 b'journal.',
1293 b'undo.',
1293 b'undo.',
1294 b'strip-backup/',
1294 b'strip-backup/',
1295 b'cache/',
1295 b'cache/',
1296 )
1296 )
1297 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1297 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1298 if repo._currentlock(repo._lockref) is None:
1298 if repo._currentlock(repo._lockref) is None:
1299 repo.ui.develwarn(
1299 repo.ui.develwarn(
1300 b'write with no lock: "%s"' % path,
1300 b'write with no lock: "%s"' % path,
1301 stacklevel=3,
1301 stacklevel=3,
1302 config=b'check-locks',
1302 config=b'check-locks',
1303 )
1303 )
1304 elif repo._currentlock(repo._wlockref) is None:
1304 elif repo._currentlock(repo._wlockref) is None:
1305 # rest of vfs files are covered by 'wlock'
1305 # rest of vfs files are covered by 'wlock'
1306 #
1306 #
1307 # exclude special files
1307 # exclude special files
1308 for prefix in self._wlockfreeprefix:
1308 for prefix in self._wlockfreeprefix:
1309 if path.startswith(prefix):
1309 if path.startswith(prefix):
1310 return
1310 return
1311 repo.ui.develwarn(
1311 repo.ui.develwarn(
1312 b'write with no wlock: "%s"' % path,
1312 b'write with no wlock: "%s"' % path,
1313 stacklevel=3,
1313 stacklevel=3,
1314 config=b'check-locks',
1314 config=b'check-locks',
1315 )
1315 )
1316 return ret
1316 return ret
1317
1317
1318 return checkvfs
1318 return checkvfs
1319
1319
1320 def _getsvfsward(self, origfunc):
1320 def _getsvfsward(self, origfunc):
1321 """build a ward for self.svfs"""
1321 """build a ward for self.svfs"""
1322 rref = weakref.ref(self)
1322 rref = weakref.ref(self)
1323
1323
1324 def checksvfs(path, mode=None):
1324 def checksvfs(path, mode=None):
1325 ret = origfunc(path, mode=mode)
1325 ret = origfunc(path, mode=mode)
1326 repo = rref()
1326 repo = rref()
1327 if repo is None or not util.safehasattr(repo, b'_lockref'):
1327 if repo is None or not util.safehasattr(repo, b'_lockref'):
1328 return
1328 return
1329 if mode in (None, b'r', b'rb'):
1329 if mode in (None, b'r', b'rb'):
1330 return
1330 return
1331 if path.startswith(repo.sharedpath):
1331 if path.startswith(repo.sharedpath):
1332 # truncate name relative to the repository (.hg)
1332 # truncate name relative to the repository (.hg)
1333 path = path[len(repo.sharedpath) + 1 :]
1333 path = path[len(repo.sharedpath) + 1 :]
1334 if repo._currentlock(repo._lockref) is None:
1334 if repo._currentlock(repo._lockref) is None:
1335 repo.ui.develwarn(
1335 repo.ui.develwarn(
1336 b'write with no lock: "%s"' % path, stacklevel=4
1336 b'write with no lock: "%s"' % path, stacklevel=4
1337 )
1337 )
1338 return ret
1338 return ret
1339
1339
1340 return checksvfs
1340 return checksvfs
1341
1341
1342 def close(self):
1342 def close(self):
1343 self._writecaches()
1343 self._writecaches()
1344
1344
1345 def _writecaches(self):
1345 def _writecaches(self):
1346 if self._revbranchcache:
1346 if self._revbranchcache:
1347 self._revbranchcache.write()
1347 self._revbranchcache.write()
1348
1348
1349 def _restrictcapabilities(self, caps):
1349 def _restrictcapabilities(self, caps):
1350 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1350 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1351 caps = set(caps)
1351 caps = set(caps)
1352 capsblob = bundle2.encodecaps(
1352 capsblob = bundle2.encodecaps(
1353 bundle2.getrepocaps(self, role=b'client')
1353 bundle2.getrepocaps(self, role=b'client')
1354 )
1354 )
1355 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1355 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1356 return caps
1356 return caps
1357
1357
1358 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1358 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1359 # self -> auditor -> self._checknested -> self
1359 # self -> auditor -> self._checknested -> self
1360
1360
1361 @property
1361 @property
1362 def auditor(self):
1362 def auditor(self):
1363 # This is only used by context.workingctx.match in order to
1363 # This is only used by context.workingctx.match in order to
1364 # detect files in subrepos.
1364 # detect files in subrepos.
1365 return pathutil.pathauditor(self.root, callback=self._checknested)
1365 return pathutil.pathauditor(self.root, callback=self._checknested)
1366
1366
1367 @property
1367 @property
1368 def nofsauditor(self):
1368 def nofsauditor(self):
1369 # This is only used by context.basectx.match in order to detect
1369 # This is only used by context.basectx.match in order to detect
1370 # files in subrepos.
1370 # files in subrepos.
1371 return pathutil.pathauditor(
1371 return pathutil.pathauditor(
1372 self.root, callback=self._checknested, realfs=False, cached=True
1372 self.root, callback=self._checknested, realfs=False, cached=True
1373 )
1373 )
1374
1374
1375 def _checknested(self, path):
1375 def _checknested(self, path):
1376 """Determine if path is a legal nested repository."""
1376 """Determine if path is a legal nested repository."""
1377 if not path.startswith(self.root):
1377 if not path.startswith(self.root):
1378 return False
1378 return False
1379 subpath = path[len(self.root) + 1 :]
1379 subpath = path[len(self.root) + 1 :]
1380 normsubpath = util.pconvert(subpath)
1380 normsubpath = util.pconvert(subpath)
1381
1381
1382 # XXX: Checking against the current working copy is wrong in
1382 # XXX: Checking against the current working copy is wrong in
1383 # the sense that it can reject things like
1383 # the sense that it can reject things like
1384 #
1384 #
1385 # $ hg cat -r 10 sub/x.txt
1385 # $ hg cat -r 10 sub/x.txt
1386 #
1386 #
1387 # if sub/ is no longer a subrepository in the working copy
1387 # if sub/ is no longer a subrepository in the working copy
1388 # parent revision.
1388 # parent revision.
1389 #
1389 #
1390 # However, it can of course also allow things that would have
1390 # However, it can of course also allow things that would have
1391 # been rejected before, such as the above cat command if sub/
1391 # been rejected before, such as the above cat command if sub/
1392 # is a subrepository now, but was a normal directory before.
1392 # is a subrepository now, but was a normal directory before.
1393 # The old path auditor would have rejected by mistake since it
1393 # The old path auditor would have rejected by mistake since it
1394 # panics when it sees sub/.hg/.
1394 # panics when it sees sub/.hg/.
1395 #
1395 #
1396 # All in all, checking against the working copy seems sensible
1396 # All in all, checking against the working copy seems sensible
1397 # since we want to prevent access to nested repositories on
1397 # since we want to prevent access to nested repositories on
1398 # the filesystem *now*.
1398 # the filesystem *now*.
1399 ctx = self[None]
1399 ctx = self[None]
1400 parts = util.splitpath(subpath)
1400 parts = util.splitpath(subpath)
1401 while parts:
1401 while parts:
1402 prefix = b'/'.join(parts)
1402 prefix = b'/'.join(parts)
1403 if prefix in ctx.substate:
1403 if prefix in ctx.substate:
1404 if prefix == normsubpath:
1404 if prefix == normsubpath:
1405 return True
1405 return True
1406 else:
1406 else:
1407 sub = ctx.sub(prefix)
1407 sub = ctx.sub(prefix)
1408 return sub.checknested(subpath[len(prefix) + 1 :])
1408 return sub.checknested(subpath[len(prefix) + 1 :])
1409 else:
1409 else:
1410 parts.pop()
1410 parts.pop()
1411 return False
1411 return False
1412
1412
1413 def peer(self):
1413 def peer(self):
1414 return localpeer(self) # not cached to avoid reference cycle
1414 return localpeer(self) # not cached to avoid reference cycle
1415
1415
1416 def unfiltered(self):
1416 def unfiltered(self):
1417 """Return unfiltered version of the repository
1417 """Return unfiltered version of the repository
1418
1418
1419 Intended to be overwritten by filtered repo."""
1419 Intended to be overwritten by filtered repo."""
1420 return self
1420 return self
1421
1421
1422 def filtered(self, name, visibilityexceptions=None):
1422 def filtered(self, name, visibilityexceptions=None):
1423 """Return a filtered version of a repository
1423 """Return a filtered version of a repository
1424
1424
1425 The `name` parameter is the identifier of the requested view. This
1425 The `name` parameter is the identifier of the requested view. This
1426 will return a repoview object set "exactly" to the specified view.
1426 will return a repoview object set "exactly" to the specified view.
1427
1427
1428 This function does not apply recursive filtering to a repository. For
1428 This function does not apply recursive filtering to a repository. For
1429 example calling `repo.filtered("served")` will return a repoview using
1429 example calling `repo.filtered("served")` will return a repoview using
1430 the "served" view, regardless of the initial view used by `repo`.
1430 the "served" view, regardless of the initial view used by `repo`.
1431
1431
1432 In other word, there is always only one level of `repoview` "filtering".
1432 In other word, there is always only one level of `repoview` "filtering".
1433 """
1433 """
1434 if self._extrafilterid is not None and b'%' not in name:
1434 if self._extrafilterid is not None and b'%' not in name:
1435 name = name + b'%' + self._extrafilterid
1435 name = name + b'%' + self._extrafilterid
1436
1436
1437 cls = repoview.newtype(self.unfiltered().__class__)
1437 cls = repoview.newtype(self.unfiltered().__class__)
1438 return cls(self, name, visibilityexceptions)
1438 return cls(self, name, visibilityexceptions)
1439
1439
1440 @mixedrepostorecache(
1440 @mixedrepostorecache(
1441 (b'bookmarks', b'plain'),
1441 (b'bookmarks', b'plain'),
1442 (b'bookmarks.current', b'plain'),
1442 (b'bookmarks.current', b'plain'),
1443 (b'bookmarks', b''),
1443 (b'bookmarks', b''),
1444 (b'00changelog.i', b''),
1444 (b'00changelog.i', b''),
1445 )
1445 )
1446 def _bookmarks(self):
1446 def _bookmarks(self):
1447 # Since the multiple files involved in the transaction cannot be
1447 # Since the multiple files involved in the transaction cannot be
1448 # written atomically (with current repository format), there is a race
1448 # written atomically (with current repository format), there is a race
1449 # condition here.
1449 # condition here.
1450 #
1450 #
1451 # 1) changelog content A is read
1451 # 1) changelog content A is read
1452 # 2) outside transaction update changelog to content B
1452 # 2) outside transaction update changelog to content B
1453 # 3) outside transaction update bookmark file referring to content B
1453 # 3) outside transaction update bookmark file referring to content B
1454 # 4) bookmarks file content is read and filtered against changelog-A
1454 # 4) bookmarks file content is read and filtered against changelog-A
1455 #
1455 #
1456 # When this happens, bookmarks against nodes missing from A are dropped.
1456 # When this happens, bookmarks against nodes missing from A are dropped.
1457 #
1457 #
1458 # Having this happening during read is not great, but it become worse
1458 # Having this happening during read is not great, but it become worse
1459 # when this happen during write because the bookmarks to the "unknown"
1459 # when this happen during write because the bookmarks to the "unknown"
1460 # nodes will be dropped for good. However, writes happen within locks.
1460 # nodes will be dropped for good. However, writes happen within locks.
1461 # This locking makes it possible to have a race free consistent read.
1461 # This locking makes it possible to have a race free consistent read.
1462 # For this purpose data read from disc before locking are
1462 # For this purpose data read from disc before locking are
1463 # "invalidated" right after the locks are taken. This invalidations are
1463 # "invalidated" right after the locks are taken. This invalidations are
1464 # "light", the `filecache` mechanism keep the data in memory and will
1464 # "light", the `filecache` mechanism keep the data in memory and will
1465 # reuse them if the underlying files did not changed. Not parsing the
1465 # reuse them if the underlying files did not changed. Not parsing the
1466 # same data multiple times helps performances.
1466 # same data multiple times helps performances.
1467 #
1467 #
1468 # Unfortunately in the case describe above, the files tracked by the
1468 # Unfortunately in the case describe above, the files tracked by the
1469 # bookmarks file cache might not have changed, but the in-memory
1469 # bookmarks file cache might not have changed, but the in-memory
1470 # content is still "wrong" because we used an older changelog content
1470 # content is still "wrong" because we used an older changelog content
1471 # to process the on-disk data. So after locking, the changelog would be
1471 # to process the on-disk data. So after locking, the changelog would be
1472 # refreshed but `_bookmarks` would be preserved.
1472 # refreshed but `_bookmarks` would be preserved.
1473 # Adding `00changelog.i` to the list of tracked file is not
1473 # Adding `00changelog.i` to the list of tracked file is not
1474 # enough, because at the time we build the content for `_bookmarks` in
1474 # enough, because at the time we build the content for `_bookmarks` in
1475 # (4), the changelog file has already diverged from the content used
1475 # (4), the changelog file has already diverged from the content used
1476 # for loading `changelog` in (1)
1476 # for loading `changelog` in (1)
1477 #
1477 #
1478 # To prevent the issue, we force the changelog to be explicitly
1478 # To prevent the issue, we force the changelog to be explicitly
1479 # reloaded while computing `_bookmarks`. The data race can still happen
1479 # reloaded while computing `_bookmarks`. The data race can still happen
1480 # without the lock (with a narrower window), but it would no longer go
1480 # without the lock (with a narrower window), but it would no longer go
1481 # undetected during the lock time refresh.
1481 # undetected during the lock time refresh.
1482 #
1482 #
1483 # The new schedule is as follow
1483 # The new schedule is as follow
1484 #
1484 #
1485 # 1) filecache logic detect that `_bookmarks` needs to be computed
1485 # 1) filecache logic detect that `_bookmarks` needs to be computed
1486 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1486 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1487 # 3) We force `changelog` filecache to be tested
1487 # 3) We force `changelog` filecache to be tested
1488 # 4) cachestat for `changelog` are captured (for changelog)
1488 # 4) cachestat for `changelog` are captured (for changelog)
1489 # 5) `_bookmarks` is computed and cached
1489 # 5) `_bookmarks` is computed and cached
1490 #
1490 #
1491 # The step in (3) ensure we have a changelog at least as recent as the
1491 # The step in (3) ensure we have a changelog at least as recent as the
1492 # cache stat computed in (1). As a result at locking time:
1492 # cache stat computed in (1). As a result at locking time:
1493 # * if the changelog did not changed since (1) -> we can reuse the data
1493 # * if the changelog did not changed since (1) -> we can reuse the data
1494 # * otherwise -> the bookmarks get refreshed.
1494 # * otherwise -> the bookmarks get refreshed.
1495 self._refreshchangelog()
1495 self._refreshchangelog()
1496 return bookmarks.bmstore(self)
1496 return bookmarks.bmstore(self)
1497
1497
1498 def _refreshchangelog(self):
1498 def _refreshchangelog(self):
1499 """make sure the in memory changelog match the on-disk one"""
1499 """make sure the in memory changelog match the on-disk one"""
1500 if 'changelog' in vars(self) and self.currenttransaction() is None:
1500 if 'changelog' in vars(self) and self.currenttransaction() is None:
1501 del self.changelog
1501 del self.changelog
1502
1502
1503 @property
1503 @property
1504 def _activebookmark(self):
1504 def _activebookmark(self):
1505 return self._bookmarks.active
1505 return self._bookmarks.active
1506
1506
1507 # _phasesets depend on changelog. what we need is to call
1507 # _phasesets depend on changelog. what we need is to call
1508 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1508 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1509 # can't be easily expressed in filecache mechanism.
1509 # can't be easily expressed in filecache mechanism.
1510 @storecache(b'phaseroots', b'00changelog.i')
1510 @storecache(b'phaseroots', b'00changelog.i')
1511 def _phasecache(self):
1511 def _phasecache(self):
1512 return phases.phasecache(self, self._phasedefaults)
1512 return phases.phasecache(self, self._phasedefaults)
1513
1513
1514 @storecache(b'obsstore')
1514 @storecache(b'obsstore')
1515 def obsstore(self):
1515 def obsstore(self):
1516 return obsolete.makestore(self.ui, self)
1516 return obsolete.makestore(self.ui, self)
1517
1517
1518 @storecache(b'00changelog.i')
1518 @storecache(b'00changelog.i')
1519 def changelog(self):
1519 def changelog(self):
1520 # load dirstate before changelog to avoid race see issue6303
1520 # load dirstate before changelog to avoid race see issue6303
1521 self.dirstate.prefetch_parents()
1521 self.dirstate.prefetch_parents()
1522 return self.store.changelog(txnutil.mayhavepending(self.root))
1522 return self.store.changelog(txnutil.mayhavepending(self.root))
1523
1523
1524 @storecache(b'00manifest.i')
1524 @storecache(b'00manifest.i')
1525 def manifestlog(self):
1525 def manifestlog(self):
1526 return self.store.manifestlog(self, self._storenarrowmatch)
1526 return self.store.manifestlog(self, self._storenarrowmatch)
1527
1527
1528 @repofilecache(b'dirstate')
1528 @repofilecache(b'dirstate')
1529 def dirstate(self):
1529 def dirstate(self):
1530 return self._makedirstate()
1530 return self._makedirstate()
1531
1531
1532 def _makedirstate(self):
1532 def _makedirstate(self):
1533 """Extension point for wrapping the dirstate per-repo."""
1533 """Extension point for wrapping the dirstate per-repo."""
1534 sparsematchfn = lambda: sparse.matcher(self)
1534 sparsematchfn = lambda: sparse.matcher(self)
1535
1535
1536 return dirstate.dirstate(
1536 return dirstate.dirstate(
1537 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1537 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1538 )
1538 )
1539
1539
1540 def _dirstatevalidate(self, node):
1540 def _dirstatevalidate(self, node):
1541 try:
1541 try:
1542 self.changelog.rev(node)
1542 self.changelog.rev(node)
1543 return node
1543 return node
1544 except error.LookupError:
1544 except error.LookupError:
1545 if not self._dirstatevalidatewarned:
1545 if not self._dirstatevalidatewarned:
1546 self._dirstatevalidatewarned = True
1546 self._dirstatevalidatewarned = True
1547 self.ui.warn(
1547 self.ui.warn(
1548 _(b"warning: ignoring unknown working parent %s!\n")
1548 _(b"warning: ignoring unknown working parent %s!\n")
1549 % short(node)
1549 % short(node)
1550 )
1550 )
1551 return nullid
1551 return nullid
1552
1552
1553 @storecache(narrowspec.FILENAME)
1553 @storecache(narrowspec.FILENAME)
1554 def narrowpats(self):
1554 def narrowpats(self):
1555 """matcher patterns for this repository's narrowspec
1555 """matcher patterns for this repository's narrowspec
1556
1556
1557 A tuple of (includes, excludes).
1557 A tuple of (includes, excludes).
1558 """
1558 """
1559 return narrowspec.load(self)
1559 return narrowspec.load(self)
1560
1560
1561 @storecache(narrowspec.FILENAME)
1561 @storecache(narrowspec.FILENAME)
1562 def _storenarrowmatch(self):
1562 def _storenarrowmatch(self):
1563 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1563 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1564 return matchmod.always()
1564 return matchmod.always()
1565 include, exclude = self.narrowpats
1565 include, exclude = self.narrowpats
1566 return narrowspec.match(self.root, include=include, exclude=exclude)
1566 return narrowspec.match(self.root, include=include, exclude=exclude)
1567
1567
1568 @storecache(narrowspec.FILENAME)
1568 @storecache(narrowspec.FILENAME)
1569 def _narrowmatch(self):
1569 def _narrowmatch(self):
1570 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1570 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1571 return matchmod.always()
1571 return matchmod.always()
1572 narrowspec.checkworkingcopynarrowspec(self)
1572 narrowspec.checkworkingcopynarrowspec(self)
1573 include, exclude = self.narrowpats
1573 include, exclude = self.narrowpats
1574 return narrowspec.match(self.root, include=include, exclude=exclude)
1574 return narrowspec.match(self.root, include=include, exclude=exclude)
1575
1575
1576 def narrowmatch(self, match=None, includeexact=False):
1576 def narrowmatch(self, match=None, includeexact=False):
1577 """matcher corresponding the the repo's narrowspec
1577 """matcher corresponding the the repo's narrowspec
1578
1578
1579 If `match` is given, then that will be intersected with the narrow
1579 If `match` is given, then that will be intersected with the narrow
1580 matcher.
1580 matcher.
1581
1581
1582 If `includeexact` is True, then any exact matches from `match` will
1582 If `includeexact` is True, then any exact matches from `match` will
1583 be included even if they're outside the narrowspec.
1583 be included even if they're outside the narrowspec.
1584 """
1584 """
1585 if match:
1585 if match:
1586 if includeexact and not self._narrowmatch.always():
1586 if includeexact and not self._narrowmatch.always():
1587 # do not exclude explicitly-specified paths so that they can
1587 # do not exclude explicitly-specified paths so that they can
1588 # be warned later on
1588 # be warned later on
1589 em = matchmod.exact(match.files())
1589 em = matchmod.exact(match.files())
1590 nm = matchmod.unionmatcher([self._narrowmatch, em])
1590 nm = matchmod.unionmatcher([self._narrowmatch, em])
1591 return matchmod.intersectmatchers(match, nm)
1591 return matchmod.intersectmatchers(match, nm)
1592 return matchmod.intersectmatchers(match, self._narrowmatch)
1592 return matchmod.intersectmatchers(match, self._narrowmatch)
1593 return self._narrowmatch
1593 return self._narrowmatch
1594
1594
1595 def setnarrowpats(self, newincludes, newexcludes):
1595 def setnarrowpats(self, newincludes, newexcludes):
1596 narrowspec.save(self, newincludes, newexcludes)
1596 narrowspec.save(self, newincludes, newexcludes)
1597 self.invalidate(clearfilecache=True)
1597 self.invalidate(clearfilecache=True)
1598
1598
1599 @unfilteredpropertycache
1599 @unfilteredpropertycache
1600 def _quick_access_changeid_null(self):
1600 def _quick_access_changeid_null(self):
1601 return {
1601 return {
1602 b'null': (nullrev, nullid),
1602 b'null': (nullrev, nullid),
1603 nullrev: (nullrev, nullid),
1603 nullrev: (nullrev, nullid),
1604 nullid: (nullrev, nullid),
1604 nullid: (nullrev, nullid),
1605 }
1605 }
1606
1606
1607 @unfilteredpropertycache
1607 @unfilteredpropertycache
1608 def _quick_access_changeid_wc(self):
1608 def _quick_access_changeid_wc(self):
1609 # also fast path access to the working copy parents
1609 # also fast path access to the working copy parents
1610 # however, only do it for filter that ensure wc is visible.
1610 # however, only do it for filter that ensure wc is visible.
1611 quick = self._quick_access_changeid_null.copy()
1611 quick = self._quick_access_changeid_null.copy()
1612 cl = self.unfiltered().changelog
1612 cl = self.unfiltered().changelog
1613 for node in self.dirstate.parents():
1613 for node in self.dirstate.parents():
1614 if node == nullid:
1614 if node == nullid:
1615 continue
1615 continue
1616 rev = cl.index.get_rev(node)
1616 rev = cl.index.get_rev(node)
1617 if rev is None:
1617 if rev is None:
1618 # unknown working copy parent case:
1618 # unknown working copy parent case:
1619 #
1619 #
1620 # skip the fast path and let higher code deal with it
1620 # skip the fast path and let higher code deal with it
1621 continue
1621 continue
1622 pair = (rev, node)
1622 pair = (rev, node)
1623 quick[rev] = pair
1623 quick[rev] = pair
1624 quick[node] = pair
1624 quick[node] = pair
1625 # also add the parents of the parents
1625 # also add the parents of the parents
1626 for r in cl.parentrevs(rev):
1626 for r in cl.parentrevs(rev):
1627 if r == nullrev:
1627 if r == nullrev:
1628 continue
1628 continue
1629 n = cl.node(r)
1629 n = cl.node(r)
1630 pair = (r, n)
1630 pair = (r, n)
1631 quick[r] = pair
1631 quick[r] = pair
1632 quick[n] = pair
1632 quick[n] = pair
1633 p1node = self.dirstate.p1()
1633 p1node = self.dirstate.p1()
1634 if p1node != nullid:
1634 if p1node != nullid:
1635 quick[b'.'] = quick[p1node]
1635 quick[b'.'] = quick[p1node]
1636 return quick
1636 return quick
1637
1637
1638 @unfilteredmethod
1638 @unfilteredmethod
1639 def _quick_access_changeid_invalidate(self):
1639 def _quick_access_changeid_invalidate(self):
1640 if '_quick_access_changeid_wc' in vars(self):
1640 if '_quick_access_changeid_wc' in vars(self):
1641 del self.__dict__['_quick_access_changeid_wc']
1641 del self.__dict__['_quick_access_changeid_wc']
1642
1642
1643 @property
1643 @property
1644 def _quick_access_changeid(self):
1644 def _quick_access_changeid(self):
1645 """an helper dictionnary for __getitem__ calls
1645 """an helper dictionnary for __getitem__ calls
1646
1646
1647 This contains a list of symbol we can recognise right away without
1647 This contains a list of symbol we can recognise right away without
1648 further processing.
1648 further processing.
1649 """
1649 """
1650 if self.filtername in repoview.filter_has_wc:
1650 if self.filtername in repoview.filter_has_wc:
1651 return self._quick_access_changeid_wc
1651 return self._quick_access_changeid_wc
1652 return self._quick_access_changeid_null
1652 return self._quick_access_changeid_null
1653
1653
1654 def __getitem__(self, changeid):
1654 def __getitem__(self, changeid):
1655 # dealing with special cases
1655 # dealing with special cases
1656 if changeid is None:
1656 if changeid is None:
1657 return context.workingctx(self)
1657 return context.workingctx(self)
1658 if isinstance(changeid, context.basectx):
1658 if isinstance(changeid, context.basectx):
1659 return changeid
1659 return changeid
1660
1660
1661 # dealing with multiple revisions
1661 # dealing with multiple revisions
1662 if isinstance(changeid, slice):
1662 if isinstance(changeid, slice):
1663 # wdirrev isn't contiguous so the slice shouldn't include it
1663 # wdirrev isn't contiguous so the slice shouldn't include it
1664 return [
1664 return [
1665 self[i]
1665 self[i]
1666 for i in pycompat.xrange(*changeid.indices(len(self)))
1666 for i in pycompat.xrange(*changeid.indices(len(self)))
1667 if i not in self.changelog.filteredrevs
1667 if i not in self.changelog.filteredrevs
1668 ]
1668 ]
1669
1669
1670 # dealing with some special values
1670 # dealing with some special values
1671 quick_access = self._quick_access_changeid.get(changeid)
1671 quick_access = self._quick_access_changeid.get(changeid)
1672 if quick_access is not None:
1672 if quick_access is not None:
1673 rev, node = quick_access
1673 rev, node = quick_access
1674 return context.changectx(self, rev, node, maybe_filtered=False)
1674 return context.changectx(self, rev, node, maybe_filtered=False)
1675 if changeid == b'tip':
1675 if changeid == b'tip':
1676 node = self.changelog.tip()
1676 node = self.changelog.tip()
1677 rev = self.changelog.rev(node)
1677 rev = self.changelog.rev(node)
1678 return context.changectx(self, rev, node)
1678 return context.changectx(self, rev, node)
1679
1679
1680 # dealing with arbitrary values
1680 # dealing with arbitrary values
1681 try:
1681 try:
1682 if isinstance(changeid, int):
1682 if isinstance(changeid, int):
1683 node = self.changelog.node(changeid)
1683 node = self.changelog.node(changeid)
1684 rev = changeid
1684 rev = changeid
1685 elif changeid == b'.':
1685 elif changeid == b'.':
1686 # this is a hack to delay/avoid loading obsmarkers
1686 # this is a hack to delay/avoid loading obsmarkers
1687 # when we know that '.' won't be hidden
1687 # when we know that '.' won't be hidden
1688 node = self.dirstate.p1()
1688 node = self.dirstate.p1()
1689 rev = self.unfiltered().changelog.rev(node)
1689 rev = self.unfiltered().changelog.rev(node)
1690 elif len(changeid) == 20:
1690 elif len(changeid) == 20:
1691 try:
1691 try:
1692 node = changeid
1692 node = changeid
1693 rev = self.changelog.rev(changeid)
1693 rev = self.changelog.rev(changeid)
1694 except error.FilteredLookupError:
1694 except error.FilteredLookupError:
1695 changeid = hex(changeid) # for the error message
1695 changeid = hex(changeid) # for the error message
1696 raise
1696 raise
1697 except LookupError:
1697 except LookupError:
1698 # check if it might have come from damaged dirstate
1698 # check if it might have come from damaged dirstate
1699 #
1699 #
1700 # XXX we could avoid the unfiltered if we had a recognizable
1700 # XXX we could avoid the unfiltered if we had a recognizable
1701 # exception for filtered changeset access
1701 # exception for filtered changeset access
1702 if (
1702 if (
1703 self.local()
1703 self.local()
1704 and changeid in self.unfiltered().dirstate.parents()
1704 and changeid in self.unfiltered().dirstate.parents()
1705 ):
1705 ):
1706 msg = _(b"working directory has unknown parent '%s'!")
1706 msg = _(b"working directory has unknown parent '%s'!")
1707 raise error.Abort(msg % short(changeid))
1707 raise error.Abort(msg % short(changeid))
1708 changeid = hex(changeid) # for the error message
1708 changeid = hex(changeid) # for the error message
1709 raise
1709 raise
1710
1710
1711 elif len(changeid) == 40:
1711 elif len(changeid) == 40:
1712 node = bin(changeid)
1712 node = bin(changeid)
1713 rev = self.changelog.rev(node)
1713 rev = self.changelog.rev(node)
1714 else:
1714 else:
1715 raise error.ProgrammingError(
1715 raise error.ProgrammingError(
1716 b"unsupported changeid '%s' of type %s"
1716 b"unsupported changeid '%s' of type %s"
1717 % (changeid, pycompat.bytestr(type(changeid)))
1717 % (changeid, pycompat.bytestr(type(changeid)))
1718 )
1718 )
1719
1719
1720 return context.changectx(self, rev, node)
1720 return context.changectx(self, rev, node)
1721
1721
1722 except (error.FilteredIndexError, error.FilteredLookupError):
1722 except (error.FilteredIndexError, error.FilteredLookupError):
1723 raise error.FilteredRepoLookupError(
1723 raise error.FilteredRepoLookupError(
1724 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1724 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1725 )
1725 )
1726 except (IndexError, LookupError):
1726 except (IndexError, LookupError):
1727 raise error.RepoLookupError(
1727 raise error.RepoLookupError(
1728 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1728 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1729 )
1729 )
1730 except error.WdirUnsupported:
1730 except error.WdirUnsupported:
1731 return context.workingctx(self)
1731 return context.workingctx(self)
1732
1732
1733 def __contains__(self, changeid):
1733 def __contains__(self, changeid):
1734 """True if the given changeid exists
1734 """True if the given changeid exists
1735
1735
1736 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1736 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1737 specified.
1737 specified.
1738 """
1738 """
1739 try:
1739 try:
1740 self[changeid]
1740 self[changeid]
1741 return True
1741 return True
1742 except error.RepoLookupError:
1742 except error.RepoLookupError:
1743 return False
1743 return False
1744
1744
1745 def __nonzero__(self):
1745 def __nonzero__(self):
1746 return True
1746 return True
1747
1747
1748 __bool__ = __nonzero__
1748 __bool__ = __nonzero__
1749
1749
1750 def __len__(self):
1750 def __len__(self):
1751 # no need to pay the cost of repoview.changelog
1751 # no need to pay the cost of repoview.changelog
1752 unfi = self.unfiltered()
1752 unfi = self.unfiltered()
1753 return len(unfi.changelog)
1753 return len(unfi.changelog)
1754
1754
1755 def __iter__(self):
1755 def __iter__(self):
1756 return iter(self.changelog)
1756 return iter(self.changelog)
1757
1757
1758 def revs(self, expr, *args):
1758 def revs(self, expr, *args):
1759 '''Find revisions matching a revset.
1759 '''Find revisions matching a revset.
1760
1760
1761 The revset is specified as a string ``expr`` that may contain
1761 The revset is specified as a string ``expr`` that may contain
1762 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1762 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1763
1763
1764 Revset aliases from the configuration are not expanded. To expand
1764 Revset aliases from the configuration are not expanded. To expand
1765 user aliases, consider calling ``scmutil.revrange()`` or
1765 user aliases, consider calling ``scmutil.revrange()`` or
1766 ``repo.anyrevs([expr], user=True)``.
1766 ``repo.anyrevs([expr], user=True)``.
1767
1767
1768 Returns a smartset.abstractsmartset, which is a list-like interface
1768 Returns a smartset.abstractsmartset, which is a list-like interface
1769 that contains integer revisions.
1769 that contains integer revisions.
1770 '''
1770 '''
1771 tree = revsetlang.spectree(expr, *args)
1771 tree = revsetlang.spectree(expr, *args)
1772 return revset.makematcher(tree)(self)
1772 return revset.makematcher(tree)(self)
1773
1773
1774 def set(self, expr, *args):
1774 def set(self, expr, *args):
1775 '''Find revisions matching a revset and emit changectx instances.
1775 '''Find revisions matching a revset and emit changectx instances.
1776
1776
1777 This is a convenience wrapper around ``revs()`` that iterates the
1777 This is a convenience wrapper around ``revs()`` that iterates the
1778 result and is a generator of changectx instances.
1778 result and is a generator of changectx instances.
1779
1779
1780 Revset aliases from the configuration are not expanded. To expand
1780 Revset aliases from the configuration are not expanded. To expand
1781 user aliases, consider calling ``scmutil.revrange()``.
1781 user aliases, consider calling ``scmutil.revrange()``.
1782 '''
1782 '''
1783 for r in self.revs(expr, *args):
1783 for r in self.revs(expr, *args):
1784 yield self[r]
1784 yield self[r]
1785
1785
1786 def anyrevs(self, specs, user=False, localalias=None):
1786 def anyrevs(self, specs, user=False, localalias=None):
1787 '''Find revisions matching one of the given revsets.
1787 '''Find revisions matching one of the given revsets.
1788
1788
1789 Revset aliases from the configuration are not expanded by default. To
1789 Revset aliases from the configuration are not expanded by default. To
1790 expand user aliases, specify ``user=True``. To provide some local
1790 expand user aliases, specify ``user=True``. To provide some local
1791 definitions overriding user aliases, set ``localalias`` to
1791 definitions overriding user aliases, set ``localalias`` to
1792 ``{name: definitionstring}``.
1792 ``{name: definitionstring}``.
1793 '''
1793 '''
1794 if specs == [b'null']:
1794 if specs == [b'null']:
1795 return revset.baseset([nullrev])
1795 return revset.baseset([nullrev])
1796 if specs == [b'.']:
1796 if specs == [b'.']:
1797 quick_data = self._quick_access_changeid.get(b'.')
1797 quick_data = self._quick_access_changeid.get(b'.')
1798 if quick_data is not None:
1798 if quick_data is not None:
1799 return revset.baseset([quick_data[0]])
1799 return revset.baseset([quick_data[0]])
1800 if user:
1800 if user:
1801 m = revset.matchany(
1801 m = revset.matchany(
1802 self.ui,
1802 self.ui,
1803 specs,
1803 specs,
1804 lookup=revset.lookupfn(self),
1804 lookup=revset.lookupfn(self),
1805 localalias=localalias,
1805 localalias=localalias,
1806 )
1806 )
1807 else:
1807 else:
1808 m = revset.matchany(None, specs, localalias=localalias)
1808 m = revset.matchany(None, specs, localalias=localalias)
1809 return m(self)
1809 return m(self)
1810
1810
1811 def url(self):
1811 def url(self):
1812 return b'file:' + self.root
1812 return b'file:' + self.root
1813
1813
1814 def hook(self, name, throw=False, **args):
1814 def hook(self, name, throw=False, **args):
1815 """Call a hook, passing this repo instance.
1815 """Call a hook, passing this repo instance.
1816
1816
1817 This a convenience method to aid invoking hooks. Extensions likely
1817 This a convenience method to aid invoking hooks. Extensions likely
1818 won't call this unless they have registered a custom hook or are
1818 won't call this unless they have registered a custom hook or are
1819 replacing code that is expected to call a hook.
1819 replacing code that is expected to call a hook.
1820 """
1820 """
1821 return hook.hook(self.ui, self, name, throw, **args)
1821 return hook.hook(self.ui, self, name, throw, **args)
1822
1822
1823 @filteredpropertycache
1823 @filteredpropertycache
1824 def _tagscache(self):
1824 def _tagscache(self):
1825 '''Returns a tagscache object that contains various tags related
1825 '''Returns a tagscache object that contains various tags related
1826 caches.'''
1826 caches.'''
1827
1827
1828 # This simplifies its cache management by having one decorated
1828 # This simplifies its cache management by having one decorated
1829 # function (this one) and the rest simply fetch things from it.
1829 # function (this one) and the rest simply fetch things from it.
1830 class tagscache(object):
1830 class tagscache(object):
1831 def __init__(self):
1831 def __init__(self):
1832 # These two define the set of tags for this repository. tags
1832 # These two define the set of tags for this repository. tags
1833 # maps tag name to node; tagtypes maps tag name to 'global' or
1833 # maps tag name to node; tagtypes maps tag name to 'global' or
1834 # 'local'. (Global tags are defined by .hgtags across all
1834 # 'local'. (Global tags are defined by .hgtags across all
1835 # heads, and local tags are defined in .hg/localtags.)
1835 # heads, and local tags are defined in .hg/localtags.)
1836 # They constitute the in-memory cache of tags.
1836 # They constitute the in-memory cache of tags.
1837 self.tags = self.tagtypes = None
1837 self.tags = self.tagtypes = None
1838
1838
1839 self.nodetagscache = self.tagslist = None
1839 self.nodetagscache = self.tagslist = None
1840
1840
1841 cache = tagscache()
1841 cache = tagscache()
1842 cache.tags, cache.tagtypes = self._findtags()
1842 cache.tags, cache.tagtypes = self._findtags()
1843
1843
1844 return cache
1844 return cache
1845
1845
1846 def tags(self):
1846 def tags(self):
1847 '''return a mapping of tag to node'''
1847 '''return a mapping of tag to node'''
1848 t = {}
1848 t = {}
1849 if self.changelog.filteredrevs:
1849 if self.changelog.filteredrevs:
1850 tags, tt = self._findtags()
1850 tags, tt = self._findtags()
1851 else:
1851 else:
1852 tags = self._tagscache.tags
1852 tags = self._tagscache.tags
1853 rev = self.changelog.rev
1853 rev = self.changelog.rev
1854 for k, v in pycompat.iteritems(tags):
1854 for k, v in pycompat.iteritems(tags):
1855 try:
1855 try:
1856 # ignore tags to unknown nodes
1856 # ignore tags to unknown nodes
1857 rev(v)
1857 rev(v)
1858 t[k] = v
1858 t[k] = v
1859 except (error.LookupError, ValueError):
1859 except (error.LookupError, ValueError):
1860 pass
1860 pass
1861 return t
1861 return t
1862
1862
1863 def _findtags(self):
1863 def _findtags(self):
1864 '''Do the hard work of finding tags. Return a pair of dicts
1864 '''Do the hard work of finding tags. Return a pair of dicts
1865 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1865 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1866 maps tag name to a string like \'global\' or \'local\'.
1866 maps tag name to a string like \'global\' or \'local\'.
1867 Subclasses or extensions are free to add their own tags, but
1867 Subclasses or extensions are free to add their own tags, but
1868 should be aware that the returned dicts will be retained for the
1868 should be aware that the returned dicts will be retained for the
1869 duration of the localrepo object.'''
1869 duration of the localrepo object.'''
1870
1870
1871 # XXX what tagtype should subclasses/extensions use? Currently
1871 # XXX what tagtype should subclasses/extensions use? Currently
1872 # mq and bookmarks add tags, but do not set the tagtype at all.
1872 # mq and bookmarks add tags, but do not set the tagtype at all.
1873 # Should each extension invent its own tag type? Should there
1873 # Should each extension invent its own tag type? Should there
1874 # be one tagtype for all such "virtual" tags? Or is the status
1874 # be one tagtype for all such "virtual" tags? Or is the status
1875 # quo fine?
1875 # quo fine?
1876
1876
1877 # map tag name to (node, hist)
1877 # map tag name to (node, hist)
1878 alltags = tagsmod.findglobaltags(self.ui, self)
1878 alltags = tagsmod.findglobaltags(self.ui, self)
1879 # map tag name to tag type
1879 # map tag name to tag type
1880 tagtypes = {tag: b'global' for tag in alltags}
1880 tagtypes = {tag: b'global' for tag in alltags}
1881
1881
1882 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1882 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1883
1883
1884 # Build the return dicts. Have to re-encode tag names because
1884 # Build the return dicts. Have to re-encode tag names because
1885 # the tags module always uses UTF-8 (in order not to lose info
1885 # the tags module always uses UTF-8 (in order not to lose info
1886 # writing to the cache), but the rest of Mercurial wants them in
1886 # writing to the cache), but the rest of Mercurial wants them in
1887 # local encoding.
1887 # local encoding.
1888 tags = {}
1888 tags = {}
1889 for (name, (node, hist)) in pycompat.iteritems(alltags):
1889 for (name, (node, hist)) in pycompat.iteritems(alltags):
1890 if node != nullid:
1890 if node != nullid:
1891 tags[encoding.tolocal(name)] = node
1891 tags[encoding.tolocal(name)] = node
1892 tags[b'tip'] = self.changelog.tip()
1892 tags[b'tip'] = self.changelog.tip()
1893 tagtypes = {
1893 tagtypes = {
1894 encoding.tolocal(name): value
1894 encoding.tolocal(name): value
1895 for (name, value) in pycompat.iteritems(tagtypes)
1895 for (name, value) in pycompat.iteritems(tagtypes)
1896 }
1896 }
1897 return (tags, tagtypes)
1897 return (tags, tagtypes)
1898
1898
1899 def tagtype(self, tagname):
1899 def tagtype(self, tagname):
1900 '''
1900 '''
1901 return the type of the given tag. result can be:
1901 return the type of the given tag. result can be:
1902
1902
1903 'local' : a local tag
1903 'local' : a local tag
1904 'global' : a global tag
1904 'global' : a global tag
1905 None : tag does not exist
1905 None : tag does not exist
1906 '''
1906 '''
1907
1907
1908 return self._tagscache.tagtypes.get(tagname)
1908 return self._tagscache.tagtypes.get(tagname)
1909
1909
1910 def tagslist(self):
1910 def tagslist(self):
1911 '''return a list of tags ordered by revision'''
1911 '''return a list of tags ordered by revision'''
1912 if not self._tagscache.tagslist:
1912 if not self._tagscache.tagslist:
1913 l = []
1913 l = []
1914 for t, n in pycompat.iteritems(self.tags()):
1914 for t, n in pycompat.iteritems(self.tags()):
1915 l.append((self.changelog.rev(n), t, n))
1915 l.append((self.changelog.rev(n), t, n))
1916 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1916 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1917
1917
1918 return self._tagscache.tagslist
1918 return self._tagscache.tagslist
1919
1919
1920 def nodetags(self, node):
1920 def nodetags(self, node):
1921 '''return the tags associated with a node'''
1921 '''return the tags associated with a node'''
1922 if not self._tagscache.nodetagscache:
1922 if not self._tagscache.nodetagscache:
1923 nodetagscache = {}
1923 nodetagscache = {}
1924 for t, n in pycompat.iteritems(self._tagscache.tags):
1924 for t, n in pycompat.iteritems(self._tagscache.tags):
1925 nodetagscache.setdefault(n, []).append(t)
1925 nodetagscache.setdefault(n, []).append(t)
1926 for tags in pycompat.itervalues(nodetagscache):
1926 for tags in pycompat.itervalues(nodetagscache):
1927 tags.sort()
1927 tags.sort()
1928 self._tagscache.nodetagscache = nodetagscache
1928 self._tagscache.nodetagscache = nodetagscache
1929 return self._tagscache.nodetagscache.get(node, [])
1929 return self._tagscache.nodetagscache.get(node, [])
1930
1930
1931 def nodebookmarks(self, node):
1931 def nodebookmarks(self, node):
1932 """return the list of bookmarks pointing to the specified node"""
1932 """return the list of bookmarks pointing to the specified node"""
1933 return self._bookmarks.names(node)
1933 return self._bookmarks.names(node)
1934
1934
1935 def branchmap(self):
1935 def branchmap(self):
1936 '''returns a dictionary {branch: [branchheads]} with branchheads
1936 '''returns a dictionary {branch: [branchheads]} with branchheads
1937 ordered by increasing revision number'''
1937 ordered by increasing revision number'''
1938 return self._branchcaches[self]
1938 return self._branchcaches[self]
1939
1939
1940 @unfilteredmethod
1940 @unfilteredmethod
1941 def revbranchcache(self):
1941 def revbranchcache(self):
1942 if not self._revbranchcache:
1942 if not self._revbranchcache:
1943 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1943 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1944 return self._revbranchcache
1944 return self._revbranchcache
1945
1945
1946 def branchtip(self, branch, ignoremissing=False):
1946 def branchtip(self, branch, ignoremissing=False):
1947 '''return the tip node for a given branch
1947 '''return the tip node for a given branch
1948
1948
1949 If ignoremissing is True, then this method will not raise an error.
1949 If ignoremissing is True, then this method will not raise an error.
1950 This is helpful for callers that only expect None for a missing branch
1950 This is helpful for callers that only expect None for a missing branch
1951 (e.g. namespace).
1951 (e.g. namespace).
1952
1952
1953 '''
1953 '''
1954 try:
1954 try:
1955 return self.branchmap().branchtip(branch)
1955 return self.branchmap().branchtip(branch)
1956 except KeyError:
1956 except KeyError:
1957 if not ignoremissing:
1957 if not ignoremissing:
1958 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1958 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1959 else:
1959 else:
1960 pass
1960 pass
1961
1961
1962 def lookup(self, key):
1962 def lookup(self, key):
1963 node = scmutil.revsymbol(self, key).node()
1963 node = scmutil.revsymbol(self, key).node()
1964 if node is None:
1964 if node is None:
1965 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1965 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1966 return node
1966 return node
1967
1967
1968 def lookupbranch(self, key):
1968 def lookupbranch(self, key):
1969 if self.branchmap().hasbranch(key):
1969 if self.branchmap().hasbranch(key):
1970 return key
1970 return key
1971
1971
1972 return scmutil.revsymbol(self, key).branch()
1972 return scmutil.revsymbol(self, key).branch()
1973
1973
1974 def known(self, nodes):
1974 def known(self, nodes):
1975 cl = self.changelog
1975 cl = self.changelog
1976 get_rev = cl.index.get_rev
1976 get_rev = cl.index.get_rev
1977 filtered = cl.filteredrevs
1977 filtered = cl.filteredrevs
1978 result = []
1978 result = []
1979 for n in nodes:
1979 for n in nodes:
1980 r = get_rev(n)
1980 r = get_rev(n)
1981 resp = not (r is None or r in filtered)
1981 resp = not (r is None or r in filtered)
1982 result.append(resp)
1982 result.append(resp)
1983 return result
1983 return result
1984
1984
1985 def local(self):
1985 def local(self):
1986 return self
1986 return self
1987
1987
1988 def publishing(self):
1988 def publishing(self):
1989 # it's safe (and desirable) to trust the publish flag unconditionally
1989 # it's safe (and desirable) to trust the publish flag unconditionally
1990 # so that we don't finalize changes shared between users via ssh or nfs
1990 # so that we don't finalize changes shared between users via ssh or nfs
1991 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1991 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1992
1992
1993 def cancopy(self):
1993 def cancopy(self):
1994 # so statichttprepo's override of local() works
1994 # so statichttprepo's override of local() works
1995 if not self.local():
1995 if not self.local():
1996 return False
1996 return False
1997 if not self.publishing():
1997 if not self.publishing():
1998 return True
1998 return True
1999 # if publishing we can't copy if there is filtered content
1999 # if publishing we can't copy if there is filtered content
2000 return not self.filtered(b'visible').changelog.filteredrevs
2000 return not self.filtered(b'visible').changelog.filteredrevs
2001
2001
2002 def shared(self):
2002 def shared(self):
2003 '''the type of shared repository (None if not shared)'''
2003 '''the type of shared repository (None if not shared)'''
2004 if self.sharedpath != self.path:
2004 if self.sharedpath != self.path:
2005 return b'store'
2005 return b'store'
2006 return None
2006 return None
2007
2007
2008 def wjoin(self, f, *insidef):
2008 def wjoin(self, f, *insidef):
2009 return self.vfs.reljoin(self.root, f, *insidef)
2009 return self.vfs.reljoin(self.root, f, *insidef)
2010
2010
2011 def setparents(self, p1, p2=nullid):
2011 def setparents(self, p1, p2=nullid):
2012 self[None].setparents(p1, p2)
2012 self[None].setparents(p1, p2)
2013 self._quick_access_changeid_invalidate()
2013 self._quick_access_changeid_invalidate()
2014
2014
2015 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2015 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2016 """changeid must be a changeset revision, if specified.
2016 """changeid must be a changeset revision, if specified.
2017 fileid can be a file revision or node."""
2017 fileid can be a file revision or node."""
2018 return context.filectx(
2018 return context.filectx(
2019 self, path, changeid, fileid, changectx=changectx
2019 self, path, changeid, fileid, changectx=changectx
2020 )
2020 )
2021
2021
2022 def getcwd(self):
2022 def getcwd(self):
2023 return self.dirstate.getcwd()
2023 return self.dirstate.getcwd()
2024
2024
2025 def pathto(self, f, cwd=None):
2025 def pathto(self, f, cwd=None):
2026 return self.dirstate.pathto(f, cwd)
2026 return self.dirstate.pathto(f, cwd)
2027
2027
2028 def _loadfilter(self, filter):
2028 def _loadfilter(self, filter):
2029 if filter not in self._filterpats:
2029 if filter not in self._filterpats:
2030 l = []
2030 l = []
2031 for pat, cmd in self.ui.configitems(filter):
2031 for pat, cmd in self.ui.configitems(filter):
2032 if cmd == b'!':
2032 if cmd == b'!':
2033 continue
2033 continue
2034 mf = matchmod.match(self.root, b'', [pat])
2034 mf = matchmod.match(self.root, b'', [pat])
2035 fn = None
2035 fn = None
2036 params = cmd
2036 params = cmd
2037 for name, filterfn in pycompat.iteritems(self._datafilters):
2037 for name, filterfn in pycompat.iteritems(self._datafilters):
2038 if cmd.startswith(name):
2038 if cmd.startswith(name):
2039 fn = filterfn
2039 fn = filterfn
2040 params = cmd[len(name) :].lstrip()
2040 params = cmd[len(name) :].lstrip()
2041 break
2041 break
2042 if not fn:
2042 if not fn:
2043 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2043 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2044 fn.__name__ = 'commandfilter'
2044 fn.__name__ = 'commandfilter'
2045 # Wrap old filters not supporting keyword arguments
2045 # Wrap old filters not supporting keyword arguments
2046 if not pycompat.getargspec(fn)[2]:
2046 if not pycompat.getargspec(fn)[2]:
2047 oldfn = fn
2047 oldfn = fn
2048 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2048 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2049 fn.__name__ = 'compat-' + oldfn.__name__
2049 fn.__name__ = 'compat-' + oldfn.__name__
2050 l.append((mf, fn, params))
2050 l.append((mf, fn, params))
2051 self._filterpats[filter] = l
2051 self._filterpats[filter] = l
2052 return self._filterpats[filter]
2052 return self._filterpats[filter]
2053
2053
2054 def _filter(self, filterpats, filename, data):
2054 def _filter(self, filterpats, filename, data):
2055 for mf, fn, cmd in filterpats:
2055 for mf, fn, cmd in filterpats:
2056 if mf(filename):
2056 if mf(filename):
2057 self.ui.debug(
2057 self.ui.debug(
2058 b"filtering %s through %s\n"
2058 b"filtering %s through %s\n"
2059 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2059 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2060 )
2060 )
2061 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2061 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2062 break
2062 break
2063
2063
2064 return data
2064 return data
2065
2065
2066 @unfilteredpropertycache
2066 @unfilteredpropertycache
2067 def _encodefilterpats(self):
2067 def _encodefilterpats(self):
2068 return self._loadfilter(b'encode')
2068 return self._loadfilter(b'encode')
2069
2069
2070 @unfilteredpropertycache
2070 @unfilteredpropertycache
2071 def _decodefilterpats(self):
2071 def _decodefilterpats(self):
2072 return self._loadfilter(b'decode')
2072 return self._loadfilter(b'decode')
2073
2073
2074 def adddatafilter(self, name, filter):
2074 def adddatafilter(self, name, filter):
2075 self._datafilters[name] = filter
2075 self._datafilters[name] = filter
2076
2076
2077 def wread(self, filename):
2077 def wread(self, filename):
2078 if self.wvfs.islink(filename):
2078 if self.wvfs.islink(filename):
2079 data = self.wvfs.readlink(filename)
2079 data = self.wvfs.readlink(filename)
2080 else:
2080 else:
2081 data = self.wvfs.read(filename)
2081 data = self.wvfs.read(filename)
2082 return self._filter(self._encodefilterpats, filename, data)
2082 return self._filter(self._encodefilterpats, filename, data)
2083
2083
2084 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2084 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2085 """write ``data`` into ``filename`` in the working directory
2085 """write ``data`` into ``filename`` in the working directory
2086
2086
2087 This returns length of written (maybe decoded) data.
2087 This returns length of written (maybe decoded) data.
2088 """
2088 """
2089 data = self._filter(self._decodefilterpats, filename, data)
2089 data = self._filter(self._decodefilterpats, filename, data)
2090 if b'l' in flags:
2090 if b'l' in flags:
2091 self.wvfs.symlink(data, filename)
2091 self.wvfs.symlink(data, filename)
2092 else:
2092 else:
2093 self.wvfs.write(
2093 self.wvfs.write(
2094 filename, data, backgroundclose=backgroundclose, **kwargs
2094 filename, data, backgroundclose=backgroundclose, **kwargs
2095 )
2095 )
2096 if b'x' in flags:
2096 if b'x' in flags:
2097 self.wvfs.setflags(filename, False, True)
2097 self.wvfs.setflags(filename, False, True)
2098 else:
2098 else:
2099 self.wvfs.setflags(filename, False, False)
2099 self.wvfs.setflags(filename, False, False)
2100 return len(data)
2100 return len(data)
2101
2101
2102 def wwritedata(self, filename, data):
2102 def wwritedata(self, filename, data):
2103 return self._filter(self._decodefilterpats, filename, data)
2103 return self._filter(self._decodefilterpats, filename, data)
2104
2104
2105 def currenttransaction(self):
2105 def currenttransaction(self):
2106 """return the current transaction or None if non exists"""
2106 """return the current transaction or None if non exists"""
2107 if self._transref:
2107 if self._transref:
2108 tr = self._transref()
2108 tr = self._transref()
2109 else:
2109 else:
2110 tr = None
2110 tr = None
2111
2111
2112 if tr and tr.running():
2112 if tr and tr.running():
2113 return tr
2113 return tr
2114 return None
2114 return None
2115
2115
2116 def transaction(self, desc, report=None):
2116 def transaction(self, desc, report=None):
2117 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2117 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2118 b'devel', b'check-locks'
2118 b'devel', b'check-locks'
2119 ):
2119 ):
2120 if self._currentlock(self._lockref) is None:
2120 if self._currentlock(self._lockref) is None:
2121 raise error.ProgrammingError(b'transaction requires locking')
2121 raise error.ProgrammingError(b'transaction requires locking')
2122 tr = self.currenttransaction()
2122 tr = self.currenttransaction()
2123 if tr is not None:
2123 if tr is not None:
2124 return tr.nest(name=desc)
2124 return tr.nest(name=desc)
2125
2125
2126 # abort here if the journal already exists
2126 # abort here if the journal already exists
2127 if self.svfs.exists(b"journal"):
2127 if self.svfs.exists(b"journal"):
2128 raise error.RepoError(
2128 raise error.RepoError(
2129 _(b"abandoned transaction found"),
2129 _(b"abandoned transaction found"),
2130 hint=_(b"run 'hg recover' to clean up transaction"),
2130 hint=_(b"run 'hg recover' to clean up transaction"),
2131 )
2131 )
2132
2132
2133 idbase = b"%.40f#%f" % (random.random(), time.time())
2133 idbase = b"%.40f#%f" % (random.random(), time.time())
2134 ha = hex(hashutil.sha1(idbase).digest())
2134 ha = hex(hashutil.sha1(idbase).digest())
2135 txnid = b'TXN:' + ha
2135 txnid = b'TXN:' + ha
2136 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2136 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2137
2137
2138 self._writejournal(desc)
2138 self._writejournal(desc)
2139 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2139 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2140 if report:
2140 if report:
2141 rp = report
2141 rp = report
2142 else:
2142 else:
2143 rp = self.ui.warn
2143 rp = self.ui.warn
2144 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2144 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2145 # we must avoid cyclic reference between repo and transaction.
2145 # we must avoid cyclic reference between repo and transaction.
2146 reporef = weakref.ref(self)
2146 reporef = weakref.ref(self)
2147 # Code to track tag movement
2147 # Code to track tag movement
2148 #
2148 #
2149 # Since tags are all handled as file content, it is actually quite hard
2149 # Since tags are all handled as file content, it is actually quite hard
2150 # to track these movement from a code perspective. So we fallback to a
2150 # to track these movement from a code perspective. So we fallback to a
2151 # tracking at the repository level. One could envision to track changes
2151 # tracking at the repository level. One could envision to track changes
2152 # to the '.hgtags' file through changegroup apply but that fails to
2152 # to the '.hgtags' file through changegroup apply but that fails to
2153 # cope with case where transaction expose new heads without changegroup
2153 # cope with case where transaction expose new heads without changegroup
2154 # being involved (eg: phase movement).
2154 # being involved (eg: phase movement).
2155 #
2155 #
2156 # For now, We gate the feature behind a flag since this likely comes
2156 # For now, We gate the feature behind a flag since this likely comes
2157 # with performance impacts. The current code run more often than needed
2157 # with performance impacts. The current code run more often than needed
2158 # and do not use caches as much as it could. The current focus is on
2158 # and do not use caches as much as it could. The current focus is on
2159 # the behavior of the feature so we disable it by default. The flag
2159 # the behavior of the feature so we disable it by default. The flag
2160 # will be removed when we are happy with the performance impact.
2160 # will be removed when we are happy with the performance impact.
2161 #
2161 #
2162 # Once this feature is no longer experimental move the following
2162 # Once this feature is no longer experimental move the following
2163 # documentation to the appropriate help section:
2163 # documentation to the appropriate help section:
2164 #
2164 #
2165 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2165 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2166 # tags (new or changed or deleted tags). In addition the details of
2166 # tags (new or changed or deleted tags). In addition the details of
2167 # these changes are made available in a file at:
2167 # these changes are made available in a file at:
2168 # ``REPOROOT/.hg/changes/tags.changes``.
2168 # ``REPOROOT/.hg/changes/tags.changes``.
2169 # Make sure you check for HG_TAG_MOVED before reading that file as it
2169 # Make sure you check for HG_TAG_MOVED before reading that file as it
2170 # might exist from a previous transaction even if no tag were touched
2170 # might exist from a previous transaction even if no tag were touched
2171 # in this one. Changes are recorded in a line base format::
2171 # in this one. Changes are recorded in a line base format::
2172 #
2172 #
2173 # <action> <hex-node> <tag-name>\n
2173 # <action> <hex-node> <tag-name>\n
2174 #
2174 #
2175 # Actions are defined as follow:
2175 # Actions are defined as follow:
2176 # "-R": tag is removed,
2176 # "-R": tag is removed,
2177 # "+A": tag is added,
2177 # "+A": tag is added,
2178 # "-M": tag is moved (old value),
2178 # "-M": tag is moved (old value),
2179 # "+M": tag is moved (new value),
2179 # "+M": tag is moved (new value),
2180 tracktags = lambda x: None
2180 tracktags = lambda x: None
2181 # experimental config: experimental.hook-track-tags
2181 # experimental config: experimental.hook-track-tags
2182 shouldtracktags = self.ui.configbool(
2182 shouldtracktags = self.ui.configbool(
2183 b'experimental', b'hook-track-tags'
2183 b'experimental', b'hook-track-tags'
2184 )
2184 )
2185 if desc != b'strip' and shouldtracktags:
2185 if desc != b'strip' and shouldtracktags:
2186 oldheads = self.changelog.headrevs()
2186 oldheads = self.changelog.headrevs()
2187
2187
2188 def tracktags(tr2):
2188 def tracktags(tr2):
2189 repo = reporef()
2189 repo = reporef()
2190 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2190 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2191 newheads = repo.changelog.headrevs()
2191 newheads = repo.changelog.headrevs()
2192 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2192 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2193 # notes: we compare lists here.
2193 # notes: we compare lists here.
2194 # As we do it only once buiding set would not be cheaper
2194 # As we do it only once buiding set would not be cheaper
2195 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2195 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2196 if changes:
2196 if changes:
2197 tr2.hookargs[b'tag_moved'] = b'1'
2197 tr2.hookargs[b'tag_moved'] = b'1'
2198 with repo.vfs(
2198 with repo.vfs(
2199 b'changes/tags.changes', b'w', atomictemp=True
2199 b'changes/tags.changes', b'w', atomictemp=True
2200 ) as changesfile:
2200 ) as changesfile:
2201 # note: we do not register the file to the transaction
2201 # note: we do not register the file to the transaction
2202 # because we needs it to still exist on the transaction
2202 # because we needs it to still exist on the transaction
2203 # is close (for txnclose hooks)
2203 # is close (for txnclose hooks)
2204 tagsmod.writediff(changesfile, changes)
2204 tagsmod.writediff(changesfile, changes)
2205
2205
2206 def validate(tr2):
2206 def validate(tr2):
2207 """will run pre-closing hooks"""
2207 """will run pre-closing hooks"""
2208 # XXX the transaction API is a bit lacking here so we take a hacky
2208 # XXX the transaction API is a bit lacking here so we take a hacky
2209 # path for now
2209 # path for now
2210 #
2210 #
2211 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2211 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2212 # dict is copied before these run. In addition we needs the data
2212 # dict is copied before these run. In addition we needs the data
2213 # available to in memory hooks too.
2213 # available to in memory hooks too.
2214 #
2214 #
2215 # Moreover, we also need to make sure this runs before txnclose
2215 # Moreover, we also need to make sure this runs before txnclose
2216 # hooks and there is no "pending" mechanism that would execute
2216 # hooks and there is no "pending" mechanism that would execute
2217 # logic only if hooks are about to run.
2217 # logic only if hooks are about to run.
2218 #
2218 #
2219 # Fixing this limitation of the transaction is also needed to track
2219 # Fixing this limitation of the transaction is also needed to track
2220 # other families of changes (bookmarks, phases, obsolescence).
2220 # other families of changes (bookmarks, phases, obsolescence).
2221 #
2221 #
2222 # This will have to be fixed before we remove the experimental
2222 # This will have to be fixed before we remove the experimental
2223 # gating.
2223 # gating.
2224 tracktags(tr2)
2224 tracktags(tr2)
2225 repo = reporef()
2225 repo = reporef()
2226
2226
2227 singleheadopt = (b'experimental', b'single-head-per-branch')
2227 singleheadopt = (b'experimental', b'single-head-per-branch')
2228 singlehead = repo.ui.configbool(*singleheadopt)
2228 singlehead = repo.ui.configbool(*singleheadopt)
2229 if singlehead:
2229 if singlehead:
2230 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2230 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2231 accountclosed = singleheadsub.get(
2231 accountclosed = singleheadsub.get(
2232 b"account-closed-heads", False
2232 b"account-closed-heads", False
2233 )
2233 )
2234 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2234 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2235 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2235 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2236 for name, (old, new) in sorted(
2236 for name, (old, new) in sorted(
2237 tr.changes[b'bookmarks'].items()
2237 tr.changes[b'bookmarks'].items()
2238 ):
2238 ):
2239 args = tr.hookargs.copy()
2239 args = tr.hookargs.copy()
2240 args.update(bookmarks.preparehookargs(name, old, new))
2240 args.update(bookmarks.preparehookargs(name, old, new))
2241 repo.hook(
2241 repo.hook(
2242 b'pretxnclose-bookmark',
2242 b'pretxnclose-bookmark',
2243 throw=True,
2243 throw=True,
2244 **pycompat.strkwargs(args)
2244 **pycompat.strkwargs(args)
2245 )
2245 )
2246 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2246 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2247 cl = repo.unfiltered().changelog
2247 cl = repo.unfiltered().changelog
2248 for revs, (old, new) in tr.changes[b'phases']:
2248 for revs, (old, new) in tr.changes[b'phases']:
2249 for rev in revs:
2249 for rev in revs:
2250 args = tr.hookargs.copy()
2250 args = tr.hookargs.copy()
2251 node = hex(cl.node(rev))
2251 node = hex(cl.node(rev))
2252 args.update(phases.preparehookargs(node, old, new))
2252 args.update(phases.preparehookargs(node, old, new))
2253 repo.hook(
2253 repo.hook(
2254 b'pretxnclose-phase',
2254 b'pretxnclose-phase',
2255 throw=True,
2255 throw=True,
2256 **pycompat.strkwargs(args)
2256 **pycompat.strkwargs(args)
2257 )
2257 )
2258
2258
2259 repo.hook(
2259 repo.hook(
2260 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2260 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2261 )
2261 )
2262
2262
2263 def releasefn(tr, success):
2263 def releasefn(tr, success):
2264 repo = reporef()
2264 repo = reporef()
2265 if repo is None:
2265 if repo is None:
2266 # If the repo has been GC'd (and this release function is being
2266 # If the repo has been GC'd (and this release function is being
2267 # called from transaction.__del__), there's not much we can do,
2267 # called from transaction.__del__), there's not much we can do,
2268 # so just leave the unfinished transaction there and let the
2268 # so just leave the unfinished transaction there and let the
2269 # user run `hg recover`.
2269 # user run `hg recover`.
2270 return
2270 return
2271 if success:
2271 if success:
2272 # this should be explicitly invoked here, because
2272 # this should be explicitly invoked here, because
2273 # in-memory changes aren't written out at closing
2273 # in-memory changes aren't written out at closing
2274 # transaction, if tr.addfilegenerator (via
2274 # transaction, if tr.addfilegenerator (via
2275 # dirstate.write or so) isn't invoked while
2275 # dirstate.write or so) isn't invoked while
2276 # transaction running
2276 # transaction running
2277 repo.dirstate.write(None)
2277 repo.dirstate.write(None)
2278 else:
2278 else:
2279 # discard all changes (including ones already written
2279 # discard all changes (including ones already written
2280 # out) in this transaction
2280 # out) in this transaction
2281 narrowspec.restorebackup(self, b'journal.narrowspec')
2281 narrowspec.restorebackup(self, b'journal.narrowspec')
2282 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2282 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2283 repo.dirstate.restorebackup(None, b'journal.dirstate')
2283 repo.dirstate.restorebackup(None, b'journal.dirstate')
2284
2284
2285 repo.invalidate(clearfilecache=True)
2285 repo.invalidate(clearfilecache=True)
2286
2286
2287 tr = transaction.transaction(
2287 tr = transaction.transaction(
2288 rp,
2288 rp,
2289 self.svfs,
2289 self.svfs,
2290 vfsmap,
2290 vfsmap,
2291 b"journal",
2291 b"journal",
2292 b"undo",
2292 b"undo",
2293 aftertrans(renames),
2293 aftertrans(renames),
2294 self.store.createmode,
2294 self.store.createmode,
2295 validator=validate,
2295 validator=validate,
2296 releasefn=releasefn,
2296 releasefn=releasefn,
2297 checkambigfiles=_cachedfiles,
2297 checkambigfiles=_cachedfiles,
2298 name=desc,
2298 name=desc,
2299 )
2299 )
2300 tr.changes[b'origrepolen'] = len(self)
2300 tr.changes[b'origrepolen'] = len(self)
2301 tr.changes[b'obsmarkers'] = set()
2301 tr.changes[b'obsmarkers'] = set()
2302 tr.changes[b'phases'] = []
2302 tr.changes[b'phases'] = []
2303 tr.changes[b'bookmarks'] = {}
2303 tr.changes[b'bookmarks'] = {}
2304
2304
2305 tr.hookargs[b'txnid'] = txnid
2305 tr.hookargs[b'txnid'] = txnid
2306 tr.hookargs[b'txnname'] = desc
2306 tr.hookargs[b'txnname'] = desc
2307 tr.hookargs[b'changes'] = tr.changes
2307 tr.hookargs[b'changes'] = tr.changes
2308 # note: writing the fncache only during finalize mean that the file is
2308 # note: writing the fncache only during finalize mean that the file is
2309 # outdated when running hooks. As fncache is used for streaming clone,
2309 # outdated when running hooks. As fncache is used for streaming clone,
2310 # this is not expected to break anything that happen during the hooks.
2310 # this is not expected to break anything that happen during the hooks.
2311 tr.addfinalize(b'flush-fncache', self.store.write)
2311 tr.addfinalize(b'flush-fncache', self.store.write)
2312
2312
2313 def txnclosehook(tr2):
2313 def txnclosehook(tr2):
2314 """To be run if transaction is successful, will schedule a hook run
2314 """To be run if transaction is successful, will schedule a hook run
2315 """
2315 """
2316 # Don't reference tr2 in hook() so we don't hold a reference.
2316 # Don't reference tr2 in hook() so we don't hold a reference.
2317 # This reduces memory consumption when there are multiple
2317 # This reduces memory consumption when there are multiple
2318 # transactions per lock. This can likely go away if issue5045
2318 # transactions per lock. This can likely go away if issue5045
2319 # fixes the function accumulation.
2319 # fixes the function accumulation.
2320 hookargs = tr2.hookargs
2320 hookargs = tr2.hookargs
2321
2321
2322 def hookfunc(unused_success):
2322 def hookfunc(unused_success):
2323 repo = reporef()
2323 repo = reporef()
2324 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2324 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2325 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2325 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2326 for name, (old, new) in bmchanges:
2326 for name, (old, new) in bmchanges:
2327 args = tr.hookargs.copy()
2327 args = tr.hookargs.copy()
2328 args.update(bookmarks.preparehookargs(name, old, new))
2328 args.update(bookmarks.preparehookargs(name, old, new))
2329 repo.hook(
2329 repo.hook(
2330 b'txnclose-bookmark',
2330 b'txnclose-bookmark',
2331 throw=False,
2331 throw=False,
2332 **pycompat.strkwargs(args)
2332 **pycompat.strkwargs(args)
2333 )
2333 )
2334
2334
2335 if hook.hashook(repo.ui, b'txnclose-phase'):
2335 if hook.hashook(repo.ui, b'txnclose-phase'):
2336 cl = repo.unfiltered().changelog
2336 cl = repo.unfiltered().changelog
2337 phasemv = sorted(
2337 phasemv = sorted(
2338 tr.changes[b'phases'], key=lambda r: r[0][0]
2338 tr.changes[b'phases'], key=lambda r: r[0][0]
2339 )
2339 )
2340 for revs, (old, new) in phasemv:
2340 for revs, (old, new) in phasemv:
2341 for rev in revs:
2341 for rev in revs:
2342 args = tr.hookargs.copy()
2342 args = tr.hookargs.copy()
2343 node = hex(cl.node(rev))
2343 node = hex(cl.node(rev))
2344 args.update(phases.preparehookargs(node, old, new))
2344 args.update(phases.preparehookargs(node, old, new))
2345 repo.hook(
2345 repo.hook(
2346 b'txnclose-phase',
2346 b'txnclose-phase',
2347 throw=False,
2347 throw=False,
2348 **pycompat.strkwargs(args)
2348 **pycompat.strkwargs(args)
2349 )
2349 )
2350
2350
2351 repo.hook(
2351 repo.hook(
2352 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2352 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2353 )
2353 )
2354
2354
2355 reporef()._afterlock(hookfunc)
2355 reporef()._afterlock(hookfunc)
2356
2356
2357 tr.addfinalize(b'txnclose-hook', txnclosehook)
2357 tr.addfinalize(b'txnclose-hook', txnclosehook)
2358 # Include a leading "-" to make it happen before the transaction summary
2358 # Include a leading "-" to make it happen before the transaction summary
2359 # reports registered via scmutil.registersummarycallback() whose names
2359 # reports registered via scmutil.registersummarycallback() whose names
2360 # are 00-txnreport etc. That way, the caches will be warm when the
2360 # are 00-txnreport etc. That way, the caches will be warm when the
2361 # callbacks run.
2361 # callbacks run.
2362 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2362 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2363
2363
2364 def txnaborthook(tr2):
2364 def txnaborthook(tr2):
2365 """To be run if transaction is aborted
2365 """To be run if transaction is aborted
2366 """
2366 """
2367 reporef().hook(
2367 reporef().hook(
2368 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2368 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2369 )
2369 )
2370
2370
2371 tr.addabort(b'txnabort-hook', txnaborthook)
2371 tr.addabort(b'txnabort-hook', txnaborthook)
2372 # avoid eager cache invalidation. in-memory data should be identical
2372 # avoid eager cache invalidation. in-memory data should be identical
2373 # to stored data if transaction has no error.
2373 # to stored data if transaction has no error.
2374 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2374 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2375 self._transref = weakref.ref(tr)
2375 self._transref = weakref.ref(tr)
2376 scmutil.registersummarycallback(self, tr, desc)
2376 scmutil.registersummarycallback(self, tr, desc)
2377 return tr
2377 return tr
2378
2378
2379 def _journalfiles(self):
2379 def _journalfiles(self):
2380 return (
2380 return (
2381 (self.svfs, b'journal'),
2381 (self.svfs, b'journal'),
2382 (self.svfs, b'journal.narrowspec'),
2382 (self.svfs, b'journal.narrowspec'),
2383 (self.vfs, b'journal.narrowspec.dirstate'),
2383 (self.vfs, b'journal.narrowspec.dirstate'),
2384 (self.vfs, b'journal.dirstate'),
2384 (self.vfs, b'journal.dirstate'),
2385 (self.vfs, b'journal.branch'),
2385 (self.vfs, b'journal.branch'),
2386 (self.vfs, b'journal.desc'),
2386 (self.vfs, b'journal.desc'),
2387 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2387 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2388 (self.svfs, b'journal.phaseroots'),
2388 (self.svfs, b'journal.phaseroots'),
2389 )
2389 )
2390
2390
2391 def undofiles(self):
2391 def undofiles(self):
2392 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2392 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2393
2393
2394 @unfilteredmethod
2394 @unfilteredmethod
2395 def _writejournal(self, desc):
2395 def _writejournal(self, desc):
2396 self.dirstate.savebackup(None, b'journal.dirstate')
2396 self.dirstate.savebackup(None, b'journal.dirstate')
2397 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2397 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2398 narrowspec.savebackup(self, b'journal.narrowspec')
2398 narrowspec.savebackup(self, b'journal.narrowspec')
2399 self.vfs.write(
2399 self.vfs.write(
2400 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2400 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2401 )
2401 )
2402 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2402 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2403 bookmarksvfs = bookmarks.bookmarksvfs(self)
2403 bookmarksvfs = bookmarks.bookmarksvfs(self)
2404 bookmarksvfs.write(
2404 bookmarksvfs.write(
2405 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2405 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2406 )
2406 )
2407 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2407 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2408
2408
2409 def recover(self):
2409 def recover(self):
2410 with self.lock():
2410 with self.lock():
2411 if self.svfs.exists(b"journal"):
2411 if self.svfs.exists(b"journal"):
2412 self.ui.status(_(b"rolling back interrupted transaction\n"))
2412 self.ui.status(_(b"rolling back interrupted transaction\n"))
2413 vfsmap = {
2413 vfsmap = {
2414 b'': self.svfs,
2414 b'': self.svfs,
2415 b'plain': self.vfs,
2415 b'plain': self.vfs,
2416 }
2416 }
2417 transaction.rollback(
2417 transaction.rollback(
2418 self.svfs,
2418 self.svfs,
2419 vfsmap,
2419 vfsmap,
2420 b"journal",
2420 b"journal",
2421 self.ui.warn,
2421 self.ui.warn,
2422 checkambigfiles=_cachedfiles,
2422 checkambigfiles=_cachedfiles,
2423 )
2423 )
2424 self.invalidate()
2424 self.invalidate()
2425 return True
2425 return True
2426 else:
2426 else:
2427 self.ui.warn(_(b"no interrupted transaction available\n"))
2427 self.ui.warn(_(b"no interrupted transaction available\n"))
2428 return False
2428 return False
2429
2429
2430 def rollback(self, dryrun=False, force=False):
2430 def rollback(self, dryrun=False, force=False):
2431 wlock = lock = dsguard = None
2431 wlock = lock = dsguard = None
2432 try:
2432 try:
2433 wlock = self.wlock()
2433 wlock = self.wlock()
2434 lock = self.lock()
2434 lock = self.lock()
2435 if self.svfs.exists(b"undo"):
2435 if self.svfs.exists(b"undo"):
2436 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2436 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2437
2437
2438 return self._rollback(dryrun, force, dsguard)
2438 return self._rollback(dryrun, force, dsguard)
2439 else:
2439 else:
2440 self.ui.warn(_(b"no rollback information available\n"))
2440 self.ui.warn(_(b"no rollback information available\n"))
2441 return 1
2441 return 1
2442 finally:
2442 finally:
2443 release(dsguard, lock, wlock)
2443 release(dsguard, lock, wlock)
2444
2444
2445 @unfilteredmethod # Until we get smarter cache management
2445 @unfilteredmethod # Until we get smarter cache management
2446 def _rollback(self, dryrun, force, dsguard):
2446 def _rollback(self, dryrun, force, dsguard):
2447 ui = self.ui
2447 ui = self.ui
2448 try:
2448 try:
2449 args = self.vfs.read(b'undo.desc').splitlines()
2449 args = self.vfs.read(b'undo.desc').splitlines()
2450 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2450 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2451 if len(args) >= 3:
2451 if len(args) >= 3:
2452 detail = args[2]
2452 detail = args[2]
2453 oldtip = oldlen - 1
2453 oldtip = oldlen - 1
2454
2454
2455 if detail and ui.verbose:
2455 if detail and ui.verbose:
2456 msg = _(
2456 msg = _(
2457 b'repository tip rolled back to revision %d'
2457 b'repository tip rolled back to revision %d'
2458 b' (undo %s: %s)\n'
2458 b' (undo %s: %s)\n'
2459 ) % (oldtip, desc, detail)
2459 ) % (oldtip, desc, detail)
2460 else:
2460 else:
2461 msg = _(
2461 msg = _(
2462 b'repository tip rolled back to revision %d (undo %s)\n'
2462 b'repository tip rolled back to revision %d (undo %s)\n'
2463 ) % (oldtip, desc)
2463 ) % (oldtip, desc)
2464 except IOError:
2464 except IOError:
2465 msg = _(b'rolling back unknown transaction\n')
2465 msg = _(b'rolling back unknown transaction\n')
2466 desc = None
2466 desc = None
2467
2467
2468 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2468 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2469 raise error.Abort(
2469 raise error.Abort(
2470 _(
2470 _(
2471 b'rollback of last commit while not checked out '
2471 b'rollback of last commit while not checked out '
2472 b'may lose data'
2472 b'may lose data'
2473 ),
2473 ),
2474 hint=_(b'use -f to force'),
2474 hint=_(b'use -f to force'),
2475 )
2475 )
2476
2476
2477 ui.status(msg)
2477 ui.status(msg)
2478 if dryrun:
2478 if dryrun:
2479 return 0
2479 return 0
2480
2480
2481 parents = self.dirstate.parents()
2481 parents = self.dirstate.parents()
2482 self.destroying()
2482 self.destroying()
2483 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2483 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2484 transaction.rollback(
2484 transaction.rollback(
2485 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2485 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2486 )
2486 )
2487 bookmarksvfs = bookmarks.bookmarksvfs(self)
2487 bookmarksvfs = bookmarks.bookmarksvfs(self)
2488 if bookmarksvfs.exists(b'undo.bookmarks'):
2488 if bookmarksvfs.exists(b'undo.bookmarks'):
2489 bookmarksvfs.rename(
2489 bookmarksvfs.rename(
2490 b'undo.bookmarks', b'bookmarks', checkambig=True
2490 b'undo.bookmarks', b'bookmarks', checkambig=True
2491 )
2491 )
2492 if self.svfs.exists(b'undo.phaseroots'):
2492 if self.svfs.exists(b'undo.phaseroots'):
2493 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2493 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2494 self.invalidate()
2494 self.invalidate()
2495
2495
2496 has_node = self.changelog.index.has_node
2496 has_node = self.changelog.index.has_node
2497 parentgone = any(not has_node(p) for p in parents)
2497 parentgone = any(not has_node(p) for p in parents)
2498 if parentgone:
2498 if parentgone:
2499 # prevent dirstateguard from overwriting already restored one
2499 # prevent dirstateguard from overwriting already restored one
2500 dsguard.close()
2500 dsguard.close()
2501
2501
2502 narrowspec.restorebackup(self, b'undo.narrowspec')
2502 narrowspec.restorebackup(self, b'undo.narrowspec')
2503 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2503 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2504 self.dirstate.restorebackup(None, b'undo.dirstate')
2504 self.dirstate.restorebackup(None, b'undo.dirstate')
2505 try:
2505 try:
2506 branch = self.vfs.read(b'undo.branch')
2506 branch = self.vfs.read(b'undo.branch')
2507 self.dirstate.setbranch(encoding.tolocal(branch))
2507 self.dirstate.setbranch(encoding.tolocal(branch))
2508 except IOError:
2508 except IOError:
2509 ui.warn(
2509 ui.warn(
2510 _(
2510 _(
2511 b'named branch could not be reset: '
2511 b'named branch could not be reset: '
2512 b'current branch is still \'%s\'\n'
2512 b'current branch is still \'%s\'\n'
2513 )
2513 )
2514 % self.dirstate.branch()
2514 % self.dirstate.branch()
2515 )
2515 )
2516
2516
2517 parents = tuple([p.rev() for p in self[None].parents()])
2517 parents = tuple([p.rev() for p in self[None].parents()])
2518 if len(parents) > 1:
2518 if len(parents) > 1:
2519 ui.status(
2519 ui.status(
2520 _(
2520 _(
2521 b'working directory now based on '
2521 b'working directory now based on '
2522 b'revisions %d and %d\n'
2522 b'revisions %d and %d\n'
2523 )
2523 )
2524 % parents
2524 % parents
2525 )
2525 )
2526 else:
2526 else:
2527 ui.status(
2527 ui.status(
2528 _(b'working directory now based on revision %d\n') % parents
2528 _(b'working directory now based on revision %d\n') % parents
2529 )
2529 )
2530 mergestatemod.mergestate.clean(self)
2530 mergestatemod.mergestate.clean(self)
2531
2531
2532 # TODO: if we know which new heads may result from this rollback, pass
2532 # TODO: if we know which new heads may result from this rollback, pass
2533 # them to destroy(), which will prevent the branchhead cache from being
2533 # them to destroy(), which will prevent the branchhead cache from being
2534 # invalidated.
2534 # invalidated.
2535 self.destroyed()
2535 self.destroyed()
2536 return 0
2536 return 0
2537
2537
2538 def _buildcacheupdater(self, newtransaction):
2538 def _buildcacheupdater(self, newtransaction):
2539 """called during transaction to build the callback updating cache
2539 """called during transaction to build the callback updating cache
2540
2540
2541 Lives on the repository to help extension who might want to augment
2541 Lives on the repository to help extension who might want to augment
2542 this logic. For this purpose, the created transaction is passed to the
2542 this logic. For this purpose, the created transaction is passed to the
2543 method.
2543 method.
2544 """
2544 """
2545 # we must avoid cyclic reference between repo and transaction.
2545 # we must avoid cyclic reference between repo and transaction.
2546 reporef = weakref.ref(self)
2546 reporef = weakref.ref(self)
2547
2547
2548 def updater(tr):
2548 def updater(tr):
2549 repo = reporef()
2549 repo = reporef()
2550 repo.updatecaches(tr)
2550 repo.updatecaches(tr)
2551
2551
2552 return updater
2552 return updater
2553
2553
2554 @unfilteredmethod
2554 @unfilteredmethod
2555 def updatecaches(self, tr=None, full=False):
2555 def updatecaches(self, tr=None, full=False):
2556 """warm appropriate caches
2556 """warm appropriate caches
2557
2557
2558 If this function is called after a transaction closed. The transaction
2558 If this function is called after a transaction closed. The transaction
2559 will be available in the 'tr' argument. This can be used to selectively
2559 will be available in the 'tr' argument. This can be used to selectively
2560 update caches relevant to the changes in that transaction.
2560 update caches relevant to the changes in that transaction.
2561
2561
2562 If 'full' is set, make sure all caches the function knows about have
2562 If 'full' is set, make sure all caches the function knows about have
2563 up-to-date data. Even the ones usually loaded more lazily.
2563 up-to-date data. Even the ones usually loaded more lazily.
2564 """
2564 """
2565 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2565 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2566 # During strip, many caches are invalid but
2566 # During strip, many caches are invalid but
2567 # later call to `destroyed` will refresh them.
2567 # later call to `destroyed` will refresh them.
2568 return
2568 return
2569
2569
2570 if tr is None or tr.changes[b'origrepolen'] < len(self):
2570 if tr is None or tr.changes[b'origrepolen'] < len(self):
2571 # accessing the 'ser ved' branchmap should refresh all the others,
2571 # accessing the 'ser ved' branchmap should refresh all the others,
2572 self.ui.debug(b'updating the branch cache\n')
2572 self.ui.debug(b'updating the branch cache\n')
2573 self.filtered(b'served').branchmap()
2573 self.filtered(b'served').branchmap()
2574 self.filtered(b'served.hidden').branchmap()
2574 self.filtered(b'served.hidden').branchmap()
2575
2575
2576 if full:
2576 if full:
2577 unfi = self.unfiltered()
2577 unfi = self.unfiltered()
2578
2578
2579 self.changelog.update_caches(transaction=tr)
2579 self.changelog.update_caches(transaction=tr)
2580 self.manifestlog.update_caches(transaction=tr)
2580 self.manifestlog.update_caches(transaction=tr)
2581
2581
2582 rbc = unfi.revbranchcache()
2582 rbc = unfi.revbranchcache()
2583 for r in unfi.changelog:
2583 for r in unfi.changelog:
2584 rbc.branchinfo(r)
2584 rbc.branchinfo(r)
2585 rbc.write()
2585 rbc.write()
2586
2586
2587 # ensure the working copy parents are in the manifestfulltextcache
2587 # ensure the working copy parents are in the manifestfulltextcache
2588 for ctx in self[b'.'].parents():
2588 for ctx in self[b'.'].parents():
2589 ctx.manifest() # accessing the manifest is enough
2589 ctx.manifest() # accessing the manifest is enough
2590
2590
2591 # accessing fnode cache warms the cache
2591 # accessing fnode cache warms the cache
2592 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2592 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2593 # accessing tags warm the cache
2593 # accessing tags warm the cache
2594 self.tags()
2594 self.tags()
2595 self.filtered(b'served').tags()
2595 self.filtered(b'served').tags()
2596
2596
2597 # The `full` arg is documented as updating even the lazily-loaded
2597 # The `full` arg is documented as updating even the lazily-loaded
2598 # caches immediately, so we're forcing a write to cause these caches
2598 # caches immediately, so we're forcing a write to cause these caches
2599 # to be warmed up even if they haven't explicitly been requested
2599 # to be warmed up even if they haven't explicitly been requested
2600 # yet (if they've never been used by hg, they won't ever have been
2600 # yet (if they've never been used by hg, they won't ever have been
2601 # written, even if they're a subset of another kind of cache that
2601 # written, even if they're a subset of another kind of cache that
2602 # *has* been used).
2602 # *has* been used).
2603 for filt in repoview.filtertable.keys():
2603 for filt in repoview.filtertable.keys():
2604 filtered = self.filtered(filt)
2604 filtered = self.filtered(filt)
2605 filtered.branchmap().write(filtered)
2605 filtered.branchmap().write(filtered)
2606
2606
2607 def invalidatecaches(self):
2607 def invalidatecaches(self):
2608
2608
2609 if '_tagscache' in vars(self):
2609 if '_tagscache' in vars(self):
2610 # can't use delattr on proxy
2610 # can't use delattr on proxy
2611 del self.__dict__['_tagscache']
2611 del self.__dict__['_tagscache']
2612
2612
2613 self._branchcaches.clear()
2613 self._branchcaches.clear()
2614 self.invalidatevolatilesets()
2614 self.invalidatevolatilesets()
2615 self._sparsesignaturecache.clear()
2615 self._sparsesignaturecache.clear()
2616
2616
2617 def invalidatevolatilesets(self):
2617 def invalidatevolatilesets(self):
2618 self.filteredrevcache.clear()
2618 self.filteredrevcache.clear()
2619 obsolete.clearobscaches(self)
2619 obsolete.clearobscaches(self)
2620 self._quick_access_changeid_invalidate()
2620 self._quick_access_changeid_invalidate()
2621
2621
2622 def invalidatedirstate(self):
2622 def invalidatedirstate(self):
2623 '''Invalidates the dirstate, causing the next call to dirstate
2623 '''Invalidates the dirstate, causing the next call to dirstate
2624 to check if it was modified since the last time it was read,
2624 to check if it was modified since the last time it was read,
2625 rereading it if it has.
2625 rereading it if it has.
2626
2626
2627 This is different to dirstate.invalidate() that it doesn't always
2627 This is different to dirstate.invalidate() that it doesn't always
2628 rereads the dirstate. Use dirstate.invalidate() if you want to
2628 rereads the dirstate. Use dirstate.invalidate() if you want to
2629 explicitly read the dirstate again (i.e. restoring it to a previous
2629 explicitly read the dirstate again (i.e. restoring it to a previous
2630 known good state).'''
2630 known good state).'''
2631 if hasunfilteredcache(self, 'dirstate'):
2631 if hasunfilteredcache(self, 'dirstate'):
2632 for k in self.dirstate._filecache:
2632 for k in self.dirstate._filecache:
2633 try:
2633 try:
2634 delattr(self.dirstate, k)
2634 delattr(self.dirstate, k)
2635 except AttributeError:
2635 except AttributeError:
2636 pass
2636 pass
2637 delattr(self.unfiltered(), 'dirstate')
2637 delattr(self.unfiltered(), 'dirstate')
2638
2638
2639 def invalidate(self, clearfilecache=False):
2639 def invalidate(self, clearfilecache=False):
2640 '''Invalidates both store and non-store parts other than dirstate
2640 '''Invalidates both store and non-store parts other than dirstate
2641
2641
2642 If a transaction is running, invalidation of store is omitted,
2642 If a transaction is running, invalidation of store is omitted,
2643 because discarding in-memory changes might cause inconsistency
2643 because discarding in-memory changes might cause inconsistency
2644 (e.g. incomplete fncache causes unintentional failure, but
2644 (e.g. incomplete fncache causes unintentional failure, but
2645 redundant one doesn't).
2645 redundant one doesn't).
2646 '''
2646 '''
2647 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2647 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2648 for k in list(self._filecache.keys()):
2648 for k in list(self._filecache.keys()):
2649 # dirstate is invalidated separately in invalidatedirstate()
2649 # dirstate is invalidated separately in invalidatedirstate()
2650 if k == b'dirstate':
2650 if k == b'dirstate':
2651 continue
2651 continue
2652 if (
2652 if (
2653 k == b'changelog'
2653 k == b'changelog'
2654 and self.currenttransaction()
2654 and self.currenttransaction()
2655 and self.changelog._delayed
2655 and self.changelog._delayed
2656 ):
2656 ):
2657 # The changelog object may store unwritten revisions. We don't
2657 # The changelog object may store unwritten revisions. We don't
2658 # want to lose them.
2658 # want to lose them.
2659 # TODO: Solve the problem instead of working around it.
2659 # TODO: Solve the problem instead of working around it.
2660 continue
2660 continue
2661
2661
2662 if clearfilecache:
2662 if clearfilecache:
2663 del self._filecache[k]
2663 del self._filecache[k]
2664 try:
2664 try:
2665 delattr(unfiltered, k)
2665 delattr(unfiltered, k)
2666 except AttributeError:
2666 except AttributeError:
2667 pass
2667 pass
2668 self.invalidatecaches()
2668 self.invalidatecaches()
2669 if not self.currenttransaction():
2669 if not self.currenttransaction():
2670 # TODO: Changing contents of store outside transaction
2670 # TODO: Changing contents of store outside transaction
2671 # causes inconsistency. We should make in-memory store
2671 # causes inconsistency. We should make in-memory store
2672 # changes detectable, and abort if changed.
2672 # changes detectable, and abort if changed.
2673 self.store.invalidatecaches()
2673 self.store.invalidatecaches()
2674
2674
2675 def invalidateall(self):
2675 def invalidateall(self):
2676 '''Fully invalidates both store and non-store parts, causing the
2676 '''Fully invalidates both store and non-store parts, causing the
2677 subsequent operation to reread any outside changes.'''
2677 subsequent operation to reread any outside changes.'''
2678 # extension should hook this to invalidate its caches
2678 # extension should hook this to invalidate its caches
2679 self.invalidate()
2679 self.invalidate()
2680 self.invalidatedirstate()
2680 self.invalidatedirstate()
2681
2681
2682 @unfilteredmethod
2682 @unfilteredmethod
2683 def _refreshfilecachestats(self, tr):
2683 def _refreshfilecachestats(self, tr):
2684 """Reload stats of cached files so that they are flagged as valid"""
2684 """Reload stats of cached files so that they are flagged as valid"""
2685 for k, ce in self._filecache.items():
2685 for k, ce in self._filecache.items():
2686 k = pycompat.sysstr(k)
2686 k = pycompat.sysstr(k)
2687 if k == 'dirstate' or k not in self.__dict__:
2687 if k == 'dirstate' or k not in self.__dict__:
2688 continue
2688 continue
2689 ce.refresh()
2689 ce.refresh()
2690
2690
2691 def _lock(
2691 def _lock(
2692 self, vfs, lockname, wait, releasefn, acquirefn, desc,
2692 self, vfs, lockname, wait, releasefn, acquirefn, desc,
2693 ):
2693 ):
2694 timeout = 0
2694 timeout = 0
2695 warntimeout = 0
2695 warntimeout = 0
2696 if wait:
2696 if wait:
2697 timeout = self.ui.configint(b"ui", b"timeout")
2697 timeout = self.ui.configint(b"ui", b"timeout")
2698 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2698 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2699 # internal config: ui.signal-safe-lock
2699 # internal config: ui.signal-safe-lock
2700 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2700 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2701
2701
2702 l = lockmod.trylock(
2702 l = lockmod.trylock(
2703 self.ui,
2703 self.ui,
2704 vfs,
2704 vfs,
2705 lockname,
2705 lockname,
2706 timeout,
2706 timeout,
2707 warntimeout,
2707 warntimeout,
2708 releasefn=releasefn,
2708 releasefn=releasefn,
2709 acquirefn=acquirefn,
2709 acquirefn=acquirefn,
2710 desc=desc,
2710 desc=desc,
2711 signalsafe=signalsafe,
2711 signalsafe=signalsafe,
2712 )
2712 )
2713 return l
2713 return l
2714
2714
2715 def _afterlock(self, callback):
2715 def _afterlock(self, callback):
2716 """add a callback to be run when the repository is fully unlocked
2716 """add a callback to be run when the repository is fully unlocked
2717
2717
2718 The callback will be executed when the outermost lock is released
2718 The callback will be executed when the outermost lock is released
2719 (with wlock being higher level than 'lock')."""
2719 (with wlock being higher level than 'lock')."""
2720 for ref in (self._wlockref, self._lockref):
2720 for ref in (self._wlockref, self._lockref):
2721 l = ref and ref()
2721 l = ref and ref()
2722 if l and l.held:
2722 if l and l.held:
2723 l.postrelease.append(callback)
2723 l.postrelease.append(callback)
2724 break
2724 break
2725 else: # no lock have been found.
2725 else: # no lock have been found.
2726 callback(True)
2726 callback(True)
2727
2727
2728 def lock(self, wait=True):
2728 def lock(self, wait=True):
2729 '''Lock the repository store (.hg/store) and return a weak reference
2729 '''Lock the repository store (.hg/store) and return a weak reference
2730 to the lock. Use this before modifying the store (e.g. committing or
2730 to the lock. Use this before modifying the store (e.g. committing or
2731 stripping). If you are opening a transaction, get a lock as well.)
2731 stripping). If you are opening a transaction, get a lock as well.)
2732
2732
2733 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2733 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2734 'wlock' first to avoid a dead-lock hazard.'''
2734 'wlock' first to avoid a dead-lock hazard.'''
2735 l = self._currentlock(self._lockref)
2735 l = self._currentlock(self._lockref)
2736 if l is not None:
2736 if l is not None:
2737 l.lock()
2737 l.lock()
2738 return l
2738 return l
2739
2739
2740 l = self._lock(
2740 l = self._lock(
2741 vfs=self.svfs,
2741 vfs=self.svfs,
2742 lockname=b"lock",
2742 lockname=b"lock",
2743 wait=wait,
2743 wait=wait,
2744 releasefn=None,
2744 releasefn=None,
2745 acquirefn=self.invalidate,
2745 acquirefn=self.invalidate,
2746 desc=_(b'repository %s') % self.origroot,
2746 desc=_(b'repository %s') % self.origroot,
2747 )
2747 )
2748 self._lockref = weakref.ref(l)
2748 self._lockref = weakref.ref(l)
2749 return l
2749 return l
2750
2750
2751 def wlock(self, wait=True):
2751 def wlock(self, wait=True):
2752 '''Lock the non-store parts of the repository (everything under
2752 '''Lock the non-store parts of the repository (everything under
2753 .hg except .hg/store) and return a weak reference to the lock.
2753 .hg except .hg/store) and return a weak reference to the lock.
2754
2754
2755 Use this before modifying files in .hg.
2755 Use this before modifying files in .hg.
2756
2756
2757 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2757 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2758 'wlock' first to avoid a dead-lock hazard.'''
2758 'wlock' first to avoid a dead-lock hazard.'''
2759 l = self._wlockref and self._wlockref()
2759 l = self._wlockref and self._wlockref()
2760 if l is not None and l.held:
2760 if l is not None and l.held:
2761 l.lock()
2761 l.lock()
2762 return l
2762 return l
2763
2763
2764 # We do not need to check for non-waiting lock acquisition. Such
2764 # We do not need to check for non-waiting lock acquisition. Such
2765 # acquisition would not cause dead-lock as they would just fail.
2765 # acquisition would not cause dead-lock as they would just fail.
2766 if wait and (
2766 if wait and (
2767 self.ui.configbool(b'devel', b'all-warnings')
2767 self.ui.configbool(b'devel', b'all-warnings')
2768 or self.ui.configbool(b'devel', b'check-locks')
2768 or self.ui.configbool(b'devel', b'check-locks')
2769 ):
2769 ):
2770 if self._currentlock(self._lockref) is not None:
2770 if self._currentlock(self._lockref) is not None:
2771 self.ui.develwarn(b'"wlock" acquired after "lock"')
2771 self.ui.develwarn(b'"wlock" acquired after "lock"')
2772
2772
2773 def unlock():
2773 def unlock():
2774 if self.dirstate.pendingparentchange():
2774 if self.dirstate.pendingparentchange():
2775 self.dirstate.invalidate()
2775 self.dirstate.invalidate()
2776 else:
2776 else:
2777 self.dirstate.write(None)
2777 self.dirstate.write(None)
2778
2778
2779 self._filecache[b'dirstate'].refresh()
2779 self._filecache[b'dirstate'].refresh()
2780
2780
2781 l = self._lock(
2781 l = self._lock(
2782 self.vfs,
2782 self.vfs,
2783 b"wlock",
2783 b"wlock",
2784 wait,
2784 wait,
2785 unlock,
2785 unlock,
2786 self.invalidatedirstate,
2786 self.invalidatedirstate,
2787 _(b'working directory of %s') % self.origroot,
2787 _(b'working directory of %s') % self.origroot,
2788 )
2788 )
2789 self._wlockref = weakref.ref(l)
2789 self._wlockref = weakref.ref(l)
2790 return l
2790 return l
2791
2791
2792 def _currentlock(self, lockref):
2792 def _currentlock(self, lockref):
2793 """Returns the lock if it's held, or None if it's not."""
2793 """Returns the lock if it's held, or None if it's not."""
2794 if lockref is None:
2794 if lockref is None:
2795 return None
2795 return None
2796 l = lockref()
2796 l = lockref()
2797 if l is None or not l.held:
2797 if l is None or not l.held:
2798 return None
2798 return None
2799 return l
2799 return l
2800
2800
2801 def currentwlock(self):
2801 def currentwlock(self):
2802 """Returns the wlock if it's held, or None if it's not."""
2802 """Returns the wlock if it's held, or None if it's not."""
2803 return self._currentlock(self._wlockref)
2803 return self._currentlock(self._wlockref)
2804
2804
2805 def checkcommitpatterns(self, wctx, match, status, fail):
2805 def checkcommitpatterns(self, wctx, match, status, fail):
2806 """check for commit arguments that aren't committable"""
2806 """check for commit arguments that aren't committable"""
2807 if match.isexact() or match.prefix():
2807 if match.isexact() or match.prefix():
2808 matched = set(status.modified + status.added + status.removed)
2808 matched = set(status.modified + status.added + status.removed)
2809
2809
2810 for f in match.files():
2810 for f in match.files():
2811 f = self.dirstate.normalize(f)
2811 f = self.dirstate.normalize(f)
2812 if f == b'.' or f in matched or f in wctx.substate:
2812 if f == b'.' or f in matched or f in wctx.substate:
2813 continue
2813 continue
2814 if f in status.deleted:
2814 if f in status.deleted:
2815 fail(f, _(b'file not found!'))
2815 fail(f, _(b'file not found!'))
2816 # Is it a directory that exists or used to exist?
2816 # Is it a directory that exists or used to exist?
2817 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2817 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2818 d = f + b'/'
2818 d = f + b'/'
2819 for mf in matched:
2819 for mf in matched:
2820 if mf.startswith(d):
2820 if mf.startswith(d):
2821 break
2821 break
2822 else:
2822 else:
2823 fail(f, _(b"no match under directory!"))
2823 fail(f, _(b"no match under directory!"))
2824 elif f not in self.dirstate:
2824 elif f not in self.dirstate:
2825 fail(f, _(b"file not tracked!"))
2825 fail(f, _(b"file not tracked!"))
2826
2826
2827 @unfilteredmethod
2827 @unfilteredmethod
2828 def commit(
2828 def commit(
2829 self,
2829 self,
2830 text=b"",
2830 text=b"",
2831 user=None,
2831 user=None,
2832 date=None,
2832 date=None,
2833 match=None,
2833 match=None,
2834 force=False,
2834 force=False,
2835 editor=None,
2835 editor=None,
2836 extra=None,
2836 extra=None,
2837 ):
2837 ):
2838 """Add a new revision to current repository.
2838 """Add a new revision to current repository.
2839
2839
2840 Revision information is gathered from the working directory,
2840 Revision information is gathered from the working directory,
2841 match can be used to filter the committed files. If editor is
2841 match can be used to filter the committed files. If editor is
2842 supplied, it is called to get a commit message.
2842 supplied, it is called to get a commit message.
2843 """
2843 """
2844 if extra is None:
2844 if extra is None:
2845 extra = {}
2845 extra = {}
2846
2846
2847 def fail(f, msg):
2847 def fail(f, msg):
2848 raise error.Abort(b'%s: %s' % (f, msg))
2848 raise error.InputError(b'%s: %s' % (f, msg))
2849
2849
2850 if not match:
2850 if not match:
2851 match = matchmod.always()
2851 match = matchmod.always()
2852
2852
2853 if not force:
2853 if not force:
2854 match.bad = fail
2854 match.bad = fail
2855
2855
2856 # lock() for recent changelog (see issue4368)
2856 # lock() for recent changelog (see issue4368)
2857 with self.wlock(), self.lock():
2857 with self.wlock(), self.lock():
2858 wctx = self[None]
2858 wctx = self[None]
2859 merge = len(wctx.parents()) > 1
2859 merge = len(wctx.parents()) > 1
2860
2860
2861 if not force and merge and not match.always():
2861 if not force and merge and not match.always():
2862 raise error.Abort(
2862 raise error.Abort(
2863 _(
2863 _(
2864 b'cannot partially commit a merge '
2864 b'cannot partially commit a merge '
2865 b'(do not specify files or patterns)'
2865 b'(do not specify files or patterns)'
2866 )
2866 )
2867 )
2867 )
2868
2868
2869 status = self.status(match=match, clean=force)
2869 status = self.status(match=match, clean=force)
2870 if force:
2870 if force:
2871 status.modified.extend(
2871 status.modified.extend(
2872 status.clean
2872 status.clean
2873 ) # mq may commit clean files
2873 ) # mq may commit clean files
2874
2874
2875 # check subrepos
2875 # check subrepos
2876 subs, commitsubs, newstate = subrepoutil.precommit(
2876 subs, commitsubs, newstate = subrepoutil.precommit(
2877 self.ui, wctx, status, match, force=force
2877 self.ui, wctx, status, match, force=force
2878 )
2878 )
2879
2879
2880 # make sure all explicit patterns are matched
2880 # make sure all explicit patterns are matched
2881 if not force:
2881 if not force:
2882 self.checkcommitpatterns(wctx, match, status, fail)
2882 self.checkcommitpatterns(wctx, match, status, fail)
2883
2883
2884 cctx = context.workingcommitctx(
2884 cctx = context.workingcommitctx(
2885 self, status, text, user, date, extra
2885 self, status, text, user, date, extra
2886 )
2886 )
2887
2887
2888 ms = mergestatemod.mergestate.read(self)
2888 ms = mergestatemod.mergestate.read(self)
2889 mergeutil.checkunresolved(ms)
2889 mergeutil.checkunresolved(ms)
2890
2890
2891 # internal config: ui.allowemptycommit
2891 # internal config: ui.allowemptycommit
2892 if cctx.isempty() and not self.ui.configbool(
2892 if cctx.isempty() and not self.ui.configbool(
2893 b'ui', b'allowemptycommit'
2893 b'ui', b'allowemptycommit'
2894 ):
2894 ):
2895 self.ui.debug(b'nothing to commit, clearing merge state\n')
2895 self.ui.debug(b'nothing to commit, clearing merge state\n')
2896 ms.reset()
2896 ms.reset()
2897 return None
2897 return None
2898
2898
2899 if merge and cctx.deleted():
2899 if merge and cctx.deleted():
2900 raise error.Abort(_(b"cannot commit merge with missing files"))
2900 raise error.Abort(_(b"cannot commit merge with missing files"))
2901
2901
2902 if editor:
2902 if editor:
2903 cctx._text = editor(self, cctx, subs)
2903 cctx._text = editor(self, cctx, subs)
2904 edited = text != cctx._text
2904 edited = text != cctx._text
2905
2905
2906 # Save commit message in case this transaction gets rolled back
2906 # Save commit message in case this transaction gets rolled back
2907 # (e.g. by a pretxncommit hook). Leave the content alone on
2907 # (e.g. by a pretxncommit hook). Leave the content alone on
2908 # the assumption that the user will use the same editor again.
2908 # the assumption that the user will use the same editor again.
2909 msgfn = self.savecommitmessage(cctx._text)
2909 msgfn = self.savecommitmessage(cctx._text)
2910
2910
2911 # commit subs and write new state
2911 # commit subs and write new state
2912 if subs:
2912 if subs:
2913 uipathfn = scmutil.getuipathfn(self)
2913 uipathfn = scmutil.getuipathfn(self)
2914 for s in sorted(commitsubs):
2914 for s in sorted(commitsubs):
2915 sub = wctx.sub(s)
2915 sub = wctx.sub(s)
2916 self.ui.status(
2916 self.ui.status(
2917 _(b'committing subrepository %s\n')
2917 _(b'committing subrepository %s\n')
2918 % uipathfn(subrepoutil.subrelpath(sub))
2918 % uipathfn(subrepoutil.subrelpath(sub))
2919 )
2919 )
2920 sr = sub.commit(cctx._text, user, date)
2920 sr = sub.commit(cctx._text, user, date)
2921 newstate[s] = (newstate[s][0], sr)
2921 newstate[s] = (newstate[s][0], sr)
2922 subrepoutil.writestate(self, newstate)
2922 subrepoutil.writestate(self, newstate)
2923
2923
2924 p1, p2 = self.dirstate.parents()
2924 p1, p2 = self.dirstate.parents()
2925 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
2925 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
2926 try:
2926 try:
2927 self.hook(
2927 self.hook(
2928 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
2928 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
2929 )
2929 )
2930 with self.transaction(b'commit'):
2930 with self.transaction(b'commit'):
2931 ret = self.commitctx(cctx, True)
2931 ret = self.commitctx(cctx, True)
2932 # update bookmarks, dirstate and mergestate
2932 # update bookmarks, dirstate and mergestate
2933 bookmarks.update(self, [p1, p2], ret)
2933 bookmarks.update(self, [p1, p2], ret)
2934 cctx.markcommitted(ret)
2934 cctx.markcommitted(ret)
2935 ms.reset()
2935 ms.reset()
2936 except: # re-raises
2936 except: # re-raises
2937 if edited:
2937 if edited:
2938 self.ui.write(
2938 self.ui.write(
2939 _(b'note: commit message saved in %s\n') % msgfn
2939 _(b'note: commit message saved in %s\n') % msgfn
2940 )
2940 )
2941 self.ui.write(
2941 self.ui.write(
2942 _(
2942 _(
2943 b"note: use 'hg commit --logfile "
2943 b"note: use 'hg commit --logfile "
2944 b".hg/last-message.txt --edit' to reuse it\n"
2944 b".hg/last-message.txt --edit' to reuse it\n"
2945 )
2945 )
2946 )
2946 )
2947 raise
2947 raise
2948
2948
2949 def commithook(unused_success):
2949 def commithook(unused_success):
2950 # hack for command that use a temporary commit (eg: histedit)
2950 # hack for command that use a temporary commit (eg: histedit)
2951 # temporary commit got stripped before hook release
2951 # temporary commit got stripped before hook release
2952 if self.changelog.hasnode(ret):
2952 if self.changelog.hasnode(ret):
2953 self.hook(
2953 self.hook(
2954 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
2954 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
2955 )
2955 )
2956
2956
2957 self._afterlock(commithook)
2957 self._afterlock(commithook)
2958 return ret
2958 return ret
2959
2959
2960 @unfilteredmethod
2960 @unfilteredmethod
2961 def commitctx(self, ctx, error=False, origctx=None):
2961 def commitctx(self, ctx, error=False, origctx=None):
2962 return commit.commitctx(self, ctx, error=error, origctx=origctx)
2962 return commit.commitctx(self, ctx, error=error, origctx=origctx)
2963
2963
2964 @unfilteredmethod
2964 @unfilteredmethod
2965 def destroying(self):
2965 def destroying(self):
2966 '''Inform the repository that nodes are about to be destroyed.
2966 '''Inform the repository that nodes are about to be destroyed.
2967 Intended for use by strip and rollback, so there's a common
2967 Intended for use by strip and rollback, so there's a common
2968 place for anything that has to be done before destroying history.
2968 place for anything that has to be done before destroying history.
2969
2969
2970 This is mostly useful for saving state that is in memory and waiting
2970 This is mostly useful for saving state that is in memory and waiting
2971 to be flushed when the current lock is released. Because a call to
2971 to be flushed when the current lock is released. Because a call to
2972 destroyed is imminent, the repo will be invalidated causing those
2972 destroyed is imminent, the repo will be invalidated causing those
2973 changes to stay in memory (waiting for the next unlock), or vanish
2973 changes to stay in memory (waiting for the next unlock), or vanish
2974 completely.
2974 completely.
2975 '''
2975 '''
2976 # When using the same lock to commit and strip, the phasecache is left
2976 # When using the same lock to commit and strip, the phasecache is left
2977 # dirty after committing. Then when we strip, the repo is invalidated,
2977 # dirty after committing. Then when we strip, the repo is invalidated,
2978 # causing those changes to disappear.
2978 # causing those changes to disappear.
2979 if '_phasecache' in vars(self):
2979 if '_phasecache' in vars(self):
2980 self._phasecache.write()
2980 self._phasecache.write()
2981
2981
2982 @unfilteredmethod
2982 @unfilteredmethod
2983 def destroyed(self):
2983 def destroyed(self):
2984 '''Inform the repository that nodes have been destroyed.
2984 '''Inform the repository that nodes have been destroyed.
2985 Intended for use by strip and rollback, so there's a common
2985 Intended for use by strip and rollback, so there's a common
2986 place for anything that has to be done after destroying history.
2986 place for anything that has to be done after destroying history.
2987 '''
2987 '''
2988 # When one tries to:
2988 # When one tries to:
2989 # 1) destroy nodes thus calling this method (e.g. strip)
2989 # 1) destroy nodes thus calling this method (e.g. strip)
2990 # 2) use phasecache somewhere (e.g. commit)
2990 # 2) use phasecache somewhere (e.g. commit)
2991 #
2991 #
2992 # then 2) will fail because the phasecache contains nodes that were
2992 # then 2) will fail because the phasecache contains nodes that were
2993 # removed. We can either remove phasecache from the filecache,
2993 # removed. We can either remove phasecache from the filecache,
2994 # causing it to reload next time it is accessed, or simply filter
2994 # causing it to reload next time it is accessed, or simply filter
2995 # the removed nodes now and write the updated cache.
2995 # the removed nodes now and write the updated cache.
2996 self._phasecache.filterunknown(self)
2996 self._phasecache.filterunknown(self)
2997 self._phasecache.write()
2997 self._phasecache.write()
2998
2998
2999 # refresh all repository caches
2999 # refresh all repository caches
3000 self.updatecaches()
3000 self.updatecaches()
3001
3001
3002 # Ensure the persistent tag cache is updated. Doing it now
3002 # Ensure the persistent tag cache is updated. Doing it now
3003 # means that the tag cache only has to worry about destroyed
3003 # means that the tag cache only has to worry about destroyed
3004 # heads immediately after a strip/rollback. That in turn
3004 # heads immediately after a strip/rollback. That in turn
3005 # guarantees that "cachetip == currenttip" (comparing both rev
3005 # guarantees that "cachetip == currenttip" (comparing both rev
3006 # and node) always means no nodes have been added or destroyed.
3006 # and node) always means no nodes have been added or destroyed.
3007
3007
3008 # XXX this is suboptimal when qrefresh'ing: we strip the current
3008 # XXX this is suboptimal when qrefresh'ing: we strip the current
3009 # head, refresh the tag cache, then immediately add a new head.
3009 # head, refresh the tag cache, then immediately add a new head.
3010 # But I think doing it this way is necessary for the "instant
3010 # But I think doing it this way is necessary for the "instant
3011 # tag cache retrieval" case to work.
3011 # tag cache retrieval" case to work.
3012 self.invalidate()
3012 self.invalidate()
3013
3013
3014 def status(
3014 def status(
3015 self,
3015 self,
3016 node1=b'.',
3016 node1=b'.',
3017 node2=None,
3017 node2=None,
3018 match=None,
3018 match=None,
3019 ignored=False,
3019 ignored=False,
3020 clean=False,
3020 clean=False,
3021 unknown=False,
3021 unknown=False,
3022 listsubrepos=False,
3022 listsubrepos=False,
3023 ):
3023 ):
3024 '''a convenience method that calls node1.status(node2)'''
3024 '''a convenience method that calls node1.status(node2)'''
3025 return self[node1].status(
3025 return self[node1].status(
3026 node2, match, ignored, clean, unknown, listsubrepos
3026 node2, match, ignored, clean, unknown, listsubrepos
3027 )
3027 )
3028
3028
3029 def addpostdsstatus(self, ps):
3029 def addpostdsstatus(self, ps):
3030 """Add a callback to run within the wlock, at the point at which status
3030 """Add a callback to run within the wlock, at the point at which status
3031 fixups happen.
3031 fixups happen.
3032
3032
3033 On status completion, callback(wctx, status) will be called with the
3033 On status completion, callback(wctx, status) will be called with the
3034 wlock held, unless the dirstate has changed from underneath or the wlock
3034 wlock held, unless the dirstate has changed from underneath or the wlock
3035 couldn't be grabbed.
3035 couldn't be grabbed.
3036
3036
3037 Callbacks should not capture and use a cached copy of the dirstate --
3037 Callbacks should not capture and use a cached copy of the dirstate --
3038 it might change in the meanwhile. Instead, they should access the
3038 it might change in the meanwhile. Instead, they should access the
3039 dirstate via wctx.repo().dirstate.
3039 dirstate via wctx.repo().dirstate.
3040
3040
3041 This list is emptied out after each status run -- extensions should
3041 This list is emptied out after each status run -- extensions should
3042 make sure it adds to this list each time dirstate.status is called.
3042 make sure it adds to this list each time dirstate.status is called.
3043 Extensions should also make sure they don't call this for statuses
3043 Extensions should also make sure they don't call this for statuses
3044 that don't involve the dirstate.
3044 that don't involve the dirstate.
3045 """
3045 """
3046
3046
3047 # The list is located here for uniqueness reasons -- it is actually
3047 # The list is located here for uniqueness reasons -- it is actually
3048 # managed by the workingctx, but that isn't unique per-repo.
3048 # managed by the workingctx, but that isn't unique per-repo.
3049 self._postdsstatus.append(ps)
3049 self._postdsstatus.append(ps)
3050
3050
3051 def postdsstatus(self):
3051 def postdsstatus(self):
3052 """Used by workingctx to get the list of post-dirstate-status hooks."""
3052 """Used by workingctx to get the list of post-dirstate-status hooks."""
3053 return self._postdsstatus
3053 return self._postdsstatus
3054
3054
3055 def clearpostdsstatus(self):
3055 def clearpostdsstatus(self):
3056 """Used by workingctx to clear post-dirstate-status hooks."""
3056 """Used by workingctx to clear post-dirstate-status hooks."""
3057 del self._postdsstatus[:]
3057 del self._postdsstatus[:]
3058
3058
3059 def heads(self, start=None):
3059 def heads(self, start=None):
3060 if start is None:
3060 if start is None:
3061 cl = self.changelog
3061 cl = self.changelog
3062 headrevs = reversed(cl.headrevs())
3062 headrevs = reversed(cl.headrevs())
3063 return [cl.node(rev) for rev in headrevs]
3063 return [cl.node(rev) for rev in headrevs]
3064
3064
3065 heads = self.changelog.heads(start)
3065 heads = self.changelog.heads(start)
3066 # sort the output in rev descending order
3066 # sort the output in rev descending order
3067 return sorted(heads, key=self.changelog.rev, reverse=True)
3067 return sorted(heads, key=self.changelog.rev, reverse=True)
3068
3068
3069 def branchheads(self, branch=None, start=None, closed=False):
3069 def branchheads(self, branch=None, start=None, closed=False):
3070 '''return a (possibly filtered) list of heads for the given branch
3070 '''return a (possibly filtered) list of heads for the given branch
3071
3071
3072 Heads are returned in topological order, from newest to oldest.
3072 Heads are returned in topological order, from newest to oldest.
3073 If branch is None, use the dirstate branch.
3073 If branch is None, use the dirstate branch.
3074 If start is not None, return only heads reachable from start.
3074 If start is not None, return only heads reachable from start.
3075 If closed is True, return heads that are marked as closed as well.
3075 If closed is True, return heads that are marked as closed as well.
3076 '''
3076 '''
3077 if branch is None:
3077 if branch is None:
3078 branch = self[None].branch()
3078 branch = self[None].branch()
3079 branches = self.branchmap()
3079 branches = self.branchmap()
3080 if not branches.hasbranch(branch):
3080 if not branches.hasbranch(branch):
3081 return []
3081 return []
3082 # the cache returns heads ordered lowest to highest
3082 # the cache returns heads ordered lowest to highest
3083 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3083 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3084 if start is not None:
3084 if start is not None:
3085 # filter out the heads that cannot be reached from startrev
3085 # filter out the heads that cannot be reached from startrev
3086 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3086 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3087 bheads = [h for h in bheads if h in fbheads]
3087 bheads = [h for h in bheads if h in fbheads]
3088 return bheads
3088 return bheads
3089
3089
3090 def branches(self, nodes):
3090 def branches(self, nodes):
3091 if not nodes:
3091 if not nodes:
3092 nodes = [self.changelog.tip()]
3092 nodes = [self.changelog.tip()]
3093 b = []
3093 b = []
3094 for n in nodes:
3094 for n in nodes:
3095 t = n
3095 t = n
3096 while True:
3096 while True:
3097 p = self.changelog.parents(n)
3097 p = self.changelog.parents(n)
3098 if p[1] != nullid or p[0] == nullid:
3098 if p[1] != nullid or p[0] == nullid:
3099 b.append((t, n, p[0], p[1]))
3099 b.append((t, n, p[0], p[1]))
3100 break
3100 break
3101 n = p[0]
3101 n = p[0]
3102 return b
3102 return b
3103
3103
3104 def between(self, pairs):
3104 def between(self, pairs):
3105 r = []
3105 r = []
3106
3106
3107 for top, bottom in pairs:
3107 for top, bottom in pairs:
3108 n, l, i = top, [], 0
3108 n, l, i = top, [], 0
3109 f = 1
3109 f = 1
3110
3110
3111 while n != bottom and n != nullid:
3111 while n != bottom and n != nullid:
3112 p = self.changelog.parents(n)[0]
3112 p = self.changelog.parents(n)[0]
3113 if i == f:
3113 if i == f:
3114 l.append(n)
3114 l.append(n)
3115 f = f * 2
3115 f = f * 2
3116 n = p
3116 n = p
3117 i += 1
3117 i += 1
3118
3118
3119 r.append(l)
3119 r.append(l)
3120
3120
3121 return r
3121 return r
3122
3122
3123 def checkpush(self, pushop):
3123 def checkpush(self, pushop):
3124 """Extensions can override this function if additional checks have
3124 """Extensions can override this function if additional checks have
3125 to be performed before pushing, or call it if they override push
3125 to be performed before pushing, or call it if they override push
3126 command.
3126 command.
3127 """
3127 """
3128
3128
3129 @unfilteredpropertycache
3129 @unfilteredpropertycache
3130 def prepushoutgoinghooks(self):
3130 def prepushoutgoinghooks(self):
3131 """Return util.hooks consists of a pushop with repo, remote, outgoing
3131 """Return util.hooks consists of a pushop with repo, remote, outgoing
3132 methods, which are called before pushing changesets.
3132 methods, which are called before pushing changesets.
3133 """
3133 """
3134 return util.hooks()
3134 return util.hooks()
3135
3135
3136 def pushkey(self, namespace, key, old, new):
3136 def pushkey(self, namespace, key, old, new):
3137 try:
3137 try:
3138 tr = self.currenttransaction()
3138 tr = self.currenttransaction()
3139 hookargs = {}
3139 hookargs = {}
3140 if tr is not None:
3140 if tr is not None:
3141 hookargs.update(tr.hookargs)
3141 hookargs.update(tr.hookargs)
3142 hookargs = pycompat.strkwargs(hookargs)
3142 hookargs = pycompat.strkwargs(hookargs)
3143 hookargs['namespace'] = namespace
3143 hookargs['namespace'] = namespace
3144 hookargs['key'] = key
3144 hookargs['key'] = key
3145 hookargs['old'] = old
3145 hookargs['old'] = old
3146 hookargs['new'] = new
3146 hookargs['new'] = new
3147 self.hook(b'prepushkey', throw=True, **hookargs)
3147 self.hook(b'prepushkey', throw=True, **hookargs)
3148 except error.HookAbort as exc:
3148 except error.HookAbort as exc:
3149 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3149 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3150 if exc.hint:
3150 if exc.hint:
3151 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3151 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3152 return False
3152 return False
3153 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3153 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3154 ret = pushkey.push(self, namespace, key, old, new)
3154 ret = pushkey.push(self, namespace, key, old, new)
3155
3155
3156 def runhook(unused_success):
3156 def runhook(unused_success):
3157 self.hook(
3157 self.hook(
3158 b'pushkey',
3158 b'pushkey',
3159 namespace=namespace,
3159 namespace=namespace,
3160 key=key,
3160 key=key,
3161 old=old,
3161 old=old,
3162 new=new,
3162 new=new,
3163 ret=ret,
3163 ret=ret,
3164 )
3164 )
3165
3165
3166 self._afterlock(runhook)
3166 self._afterlock(runhook)
3167 return ret
3167 return ret
3168
3168
3169 def listkeys(self, namespace):
3169 def listkeys(self, namespace):
3170 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3170 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3171 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3171 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3172 values = pushkey.list(self, namespace)
3172 values = pushkey.list(self, namespace)
3173 self.hook(b'listkeys', namespace=namespace, values=values)
3173 self.hook(b'listkeys', namespace=namespace, values=values)
3174 return values
3174 return values
3175
3175
3176 def debugwireargs(self, one, two, three=None, four=None, five=None):
3176 def debugwireargs(self, one, two, three=None, four=None, five=None):
3177 '''used to test argument passing over the wire'''
3177 '''used to test argument passing over the wire'''
3178 return b"%s %s %s %s %s" % (
3178 return b"%s %s %s %s %s" % (
3179 one,
3179 one,
3180 two,
3180 two,
3181 pycompat.bytestr(three),
3181 pycompat.bytestr(three),
3182 pycompat.bytestr(four),
3182 pycompat.bytestr(four),
3183 pycompat.bytestr(five),
3183 pycompat.bytestr(five),
3184 )
3184 )
3185
3185
3186 def savecommitmessage(self, text):
3186 def savecommitmessage(self, text):
3187 fp = self.vfs(b'last-message.txt', b'wb')
3187 fp = self.vfs(b'last-message.txt', b'wb')
3188 try:
3188 try:
3189 fp.write(text)
3189 fp.write(text)
3190 finally:
3190 finally:
3191 fp.close()
3191 fp.close()
3192 return self.pathto(fp.name[len(self.root) + 1 :])
3192 return self.pathto(fp.name[len(self.root) + 1 :])
3193
3193
3194
3194
3195 # used to avoid circular references so destructors work
3195 # used to avoid circular references so destructors work
3196 def aftertrans(files):
3196 def aftertrans(files):
3197 renamefiles = [tuple(t) for t in files]
3197 renamefiles = [tuple(t) for t in files]
3198
3198
3199 def a():
3199 def a():
3200 for vfs, src, dest in renamefiles:
3200 for vfs, src, dest in renamefiles:
3201 # if src and dest refer to a same file, vfs.rename is a no-op,
3201 # if src and dest refer to a same file, vfs.rename is a no-op,
3202 # leaving both src and dest on disk. delete dest to make sure
3202 # leaving both src and dest on disk. delete dest to make sure
3203 # the rename couldn't be such a no-op.
3203 # the rename couldn't be such a no-op.
3204 vfs.tryunlink(dest)
3204 vfs.tryunlink(dest)
3205 try:
3205 try:
3206 vfs.rename(src, dest)
3206 vfs.rename(src, dest)
3207 except OSError: # journal file does not yet exist
3207 except OSError: # journal file does not yet exist
3208 pass
3208 pass
3209
3209
3210 return a
3210 return a
3211
3211
3212
3212
3213 def undoname(fn):
3213 def undoname(fn):
3214 base, name = os.path.split(fn)
3214 base, name = os.path.split(fn)
3215 assert name.startswith(b'journal')
3215 assert name.startswith(b'journal')
3216 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3216 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3217
3217
3218
3218
3219 def instance(ui, path, create, intents=None, createopts=None):
3219 def instance(ui, path, create, intents=None, createopts=None):
3220 localpath = util.urllocalpath(path)
3220 localpath = util.urllocalpath(path)
3221 if create:
3221 if create:
3222 createrepository(ui, localpath, createopts=createopts)
3222 createrepository(ui, localpath, createopts=createopts)
3223
3223
3224 return makelocalrepository(ui, localpath, intents=intents)
3224 return makelocalrepository(ui, localpath, intents=intents)
3225
3225
3226
3226
3227 def islocal(path):
3227 def islocal(path):
3228 return True
3228 return True
3229
3229
3230
3230
3231 def defaultcreateopts(ui, createopts=None):
3231 def defaultcreateopts(ui, createopts=None):
3232 """Populate the default creation options for a repository.
3232 """Populate the default creation options for a repository.
3233
3233
3234 A dictionary of explicitly requested creation options can be passed
3234 A dictionary of explicitly requested creation options can be passed
3235 in. Missing keys will be populated.
3235 in. Missing keys will be populated.
3236 """
3236 """
3237 createopts = dict(createopts or {})
3237 createopts = dict(createopts or {})
3238
3238
3239 if b'backend' not in createopts:
3239 if b'backend' not in createopts:
3240 # experimental config: storage.new-repo-backend
3240 # experimental config: storage.new-repo-backend
3241 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3241 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3242
3242
3243 return createopts
3243 return createopts
3244
3244
3245
3245
3246 def newreporequirements(ui, createopts):
3246 def newreporequirements(ui, createopts):
3247 """Determine the set of requirements for a new local repository.
3247 """Determine the set of requirements for a new local repository.
3248
3248
3249 Extensions can wrap this function to specify custom requirements for
3249 Extensions can wrap this function to specify custom requirements for
3250 new repositories.
3250 new repositories.
3251 """
3251 """
3252 # If the repo is being created from a shared repository, we copy
3252 # If the repo is being created from a shared repository, we copy
3253 # its requirements.
3253 # its requirements.
3254 if b'sharedrepo' in createopts:
3254 if b'sharedrepo' in createopts:
3255 requirements = set(createopts[b'sharedrepo'].requirements)
3255 requirements = set(createopts[b'sharedrepo'].requirements)
3256 if createopts.get(b'sharedrelative'):
3256 if createopts.get(b'sharedrelative'):
3257 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3257 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3258 else:
3258 else:
3259 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3259 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3260
3260
3261 return requirements
3261 return requirements
3262
3262
3263 if b'backend' not in createopts:
3263 if b'backend' not in createopts:
3264 raise error.ProgrammingError(
3264 raise error.ProgrammingError(
3265 b'backend key not present in createopts; '
3265 b'backend key not present in createopts; '
3266 b'was defaultcreateopts() called?'
3266 b'was defaultcreateopts() called?'
3267 )
3267 )
3268
3268
3269 if createopts[b'backend'] != b'revlogv1':
3269 if createopts[b'backend'] != b'revlogv1':
3270 raise error.Abort(
3270 raise error.Abort(
3271 _(
3271 _(
3272 b'unable to determine repository requirements for '
3272 b'unable to determine repository requirements for '
3273 b'storage backend: %s'
3273 b'storage backend: %s'
3274 )
3274 )
3275 % createopts[b'backend']
3275 % createopts[b'backend']
3276 )
3276 )
3277
3277
3278 requirements = {b'revlogv1'}
3278 requirements = {b'revlogv1'}
3279 if ui.configbool(b'format', b'usestore'):
3279 if ui.configbool(b'format', b'usestore'):
3280 requirements.add(b'store')
3280 requirements.add(b'store')
3281 if ui.configbool(b'format', b'usefncache'):
3281 if ui.configbool(b'format', b'usefncache'):
3282 requirements.add(b'fncache')
3282 requirements.add(b'fncache')
3283 if ui.configbool(b'format', b'dotencode'):
3283 if ui.configbool(b'format', b'dotencode'):
3284 requirements.add(b'dotencode')
3284 requirements.add(b'dotencode')
3285
3285
3286 compengines = ui.configlist(b'format', b'revlog-compression')
3286 compengines = ui.configlist(b'format', b'revlog-compression')
3287 for compengine in compengines:
3287 for compengine in compengines:
3288 if compengine in util.compengines:
3288 if compengine in util.compengines:
3289 break
3289 break
3290 else:
3290 else:
3291 raise error.Abort(
3291 raise error.Abort(
3292 _(
3292 _(
3293 b'compression engines %s defined by '
3293 b'compression engines %s defined by '
3294 b'format.revlog-compression not available'
3294 b'format.revlog-compression not available'
3295 )
3295 )
3296 % b', '.join(b'"%s"' % e for e in compengines),
3296 % b', '.join(b'"%s"' % e for e in compengines),
3297 hint=_(
3297 hint=_(
3298 b'run "hg debuginstall" to list available '
3298 b'run "hg debuginstall" to list available '
3299 b'compression engines'
3299 b'compression engines'
3300 ),
3300 ),
3301 )
3301 )
3302
3302
3303 # zlib is the historical default and doesn't need an explicit requirement.
3303 # zlib is the historical default and doesn't need an explicit requirement.
3304 if compengine == b'zstd':
3304 if compengine == b'zstd':
3305 requirements.add(b'revlog-compression-zstd')
3305 requirements.add(b'revlog-compression-zstd')
3306 elif compengine != b'zlib':
3306 elif compengine != b'zlib':
3307 requirements.add(b'exp-compression-%s' % compengine)
3307 requirements.add(b'exp-compression-%s' % compengine)
3308
3308
3309 if scmutil.gdinitconfig(ui):
3309 if scmutil.gdinitconfig(ui):
3310 requirements.add(b'generaldelta')
3310 requirements.add(b'generaldelta')
3311 if ui.configbool(b'format', b'sparse-revlog'):
3311 if ui.configbool(b'format', b'sparse-revlog'):
3312 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3312 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3313
3313
3314 # experimental config: format.exp-use-side-data
3314 # experimental config: format.exp-use-side-data
3315 if ui.configbool(b'format', b'exp-use-side-data'):
3315 if ui.configbool(b'format', b'exp-use-side-data'):
3316 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3316 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3317 # experimental config: format.exp-use-copies-side-data-changeset
3317 # experimental config: format.exp-use-copies-side-data-changeset
3318 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3318 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3319 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3319 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3320 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3320 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3321 if ui.configbool(b'experimental', b'treemanifest'):
3321 if ui.configbool(b'experimental', b'treemanifest'):
3322 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3322 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3323
3323
3324 revlogv2 = ui.config(b'experimental', b'revlogv2')
3324 revlogv2 = ui.config(b'experimental', b'revlogv2')
3325 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3325 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3326 requirements.remove(b'revlogv1')
3326 requirements.remove(b'revlogv1')
3327 # generaldelta is implied by revlogv2.
3327 # generaldelta is implied by revlogv2.
3328 requirements.discard(b'generaldelta')
3328 requirements.discard(b'generaldelta')
3329 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3329 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3330 # experimental config: format.internal-phase
3330 # experimental config: format.internal-phase
3331 if ui.configbool(b'format', b'internal-phase'):
3331 if ui.configbool(b'format', b'internal-phase'):
3332 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3332 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3333
3333
3334 if createopts.get(b'narrowfiles'):
3334 if createopts.get(b'narrowfiles'):
3335 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3335 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3336
3336
3337 if createopts.get(b'lfs'):
3337 if createopts.get(b'lfs'):
3338 requirements.add(b'lfs')
3338 requirements.add(b'lfs')
3339
3339
3340 if ui.configbool(b'format', b'bookmarks-in-store'):
3340 if ui.configbool(b'format', b'bookmarks-in-store'):
3341 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3341 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3342
3342
3343 if ui.configbool(b'format', b'use-persistent-nodemap'):
3343 if ui.configbool(b'format', b'use-persistent-nodemap'):
3344 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3344 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3345
3345
3346 # if share-safe is enabled, let's create the new repository with the new
3346 # if share-safe is enabled, let's create the new repository with the new
3347 # requirement
3347 # requirement
3348 if ui.configbool(b'format', b'exp-share-safe'):
3348 if ui.configbool(b'format', b'exp-share-safe'):
3349 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3349 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3350
3350
3351 return requirements
3351 return requirements
3352
3352
3353
3353
3354 def checkrequirementscompat(ui, requirements):
3354 def checkrequirementscompat(ui, requirements):
3355 """ Checks compatibility of repository requirements enabled and disabled.
3355 """ Checks compatibility of repository requirements enabled and disabled.
3356
3356
3357 Returns a set of requirements which needs to be dropped because dependend
3357 Returns a set of requirements which needs to be dropped because dependend
3358 requirements are not enabled. Also warns users about it """
3358 requirements are not enabled. Also warns users about it """
3359
3359
3360 dropped = set()
3360 dropped = set()
3361
3361
3362 if b'store' not in requirements:
3362 if b'store' not in requirements:
3363 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3363 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3364 ui.warn(
3364 ui.warn(
3365 _(
3365 _(
3366 b'ignoring enabled \'format.bookmarks-in-store\' config '
3366 b'ignoring enabled \'format.bookmarks-in-store\' config '
3367 b'beacuse it is incompatible with disabled '
3367 b'beacuse it is incompatible with disabled '
3368 b'\'format.usestore\' config\n'
3368 b'\'format.usestore\' config\n'
3369 )
3369 )
3370 )
3370 )
3371 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3371 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3372
3372
3373 if (
3373 if (
3374 requirementsmod.SHARED_REQUIREMENT in requirements
3374 requirementsmod.SHARED_REQUIREMENT in requirements
3375 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3375 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3376 ):
3376 ):
3377 raise error.Abort(
3377 raise error.Abort(
3378 _(
3378 _(
3379 b"cannot create shared repository as source was created"
3379 b"cannot create shared repository as source was created"
3380 b" with 'format.usestore' config disabled"
3380 b" with 'format.usestore' config disabled"
3381 )
3381 )
3382 )
3382 )
3383
3383
3384 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3384 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3385 ui.warn(
3385 ui.warn(
3386 _(
3386 _(
3387 b"ignoring enabled 'format.exp-share-safe' config because "
3387 b"ignoring enabled 'format.exp-share-safe' config because "
3388 b"it is incompatible with disabled 'format.usestore'"
3388 b"it is incompatible with disabled 'format.usestore'"
3389 b" config\n"
3389 b" config\n"
3390 )
3390 )
3391 )
3391 )
3392 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3392 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3393
3393
3394 return dropped
3394 return dropped
3395
3395
3396
3396
3397 def filterknowncreateopts(ui, createopts):
3397 def filterknowncreateopts(ui, createopts):
3398 """Filters a dict of repo creation options against options that are known.
3398 """Filters a dict of repo creation options against options that are known.
3399
3399
3400 Receives a dict of repo creation options and returns a dict of those
3400 Receives a dict of repo creation options and returns a dict of those
3401 options that we don't know how to handle.
3401 options that we don't know how to handle.
3402
3402
3403 This function is called as part of repository creation. If the
3403 This function is called as part of repository creation. If the
3404 returned dict contains any items, repository creation will not
3404 returned dict contains any items, repository creation will not
3405 be allowed, as it means there was a request to create a repository
3405 be allowed, as it means there was a request to create a repository
3406 with options not recognized by loaded code.
3406 with options not recognized by loaded code.
3407
3407
3408 Extensions can wrap this function to filter out creation options
3408 Extensions can wrap this function to filter out creation options
3409 they know how to handle.
3409 they know how to handle.
3410 """
3410 """
3411 known = {
3411 known = {
3412 b'backend',
3412 b'backend',
3413 b'lfs',
3413 b'lfs',
3414 b'narrowfiles',
3414 b'narrowfiles',
3415 b'sharedrepo',
3415 b'sharedrepo',
3416 b'sharedrelative',
3416 b'sharedrelative',
3417 b'shareditems',
3417 b'shareditems',
3418 b'shallowfilestore',
3418 b'shallowfilestore',
3419 }
3419 }
3420
3420
3421 return {k: v for k, v in createopts.items() if k not in known}
3421 return {k: v for k, v in createopts.items() if k not in known}
3422
3422
3423
3423
3424 def createrepository(ui, path, createopts=None):
3424 def createrepository(ui, path, createopts=None):
3425 """Create a new repository in a vfs.
3425 """Create a new repository in a vfs.
3426
3426
3427 ``path`` path to the new repo's working directory.
3427 ``path`` path to the new repo's working directory.
3428 ``createopts`` options for the new repository.
3428 ``createopts`` options for the new repository.
3429
3429
3430 The following keys for ``createopts`` are recognized:
3430 The following keys for ``createopts`` are recognized:
3431
3431
3432 backend
3432 backend
3433 The storage backend to use.
3433 The storage backend to use.
3434 lfs
3434 lfs
3435 Repository will be created with ``lfs`` requirement. The lfs extension
3435 Repository will be created with ``lfs`` requirement. The lfs extension
3436 will automatically be loaded when the repository is accessed.
3436 will automatically be loaded when the repository is accessed.
3437 narrowfiles
3437 narrowfiles
3438 Set up repository to support narrow file storage.
3438 Set up repository to support narrow file storage.
3439 sharedrepo
3439 sharedrepo
3440 Repository object from which storage should be shared.
3440 Repository object from which storage should be shared.
3441 sharedrelative
3441 sharedrelative
3442 Boolean indicating if the path to the shared repo should be
3442 Boolean indicating if the path to the shared repo should be
3443 stored as relative. By default, the pointer to the "parent" repo
3443 stored as relative. By default, the pointer to the "parent" repo
3444 is stored as an absolute path.
3444 is stored as an absolute path.
3445 shareditems
3445 shareditems
3446 Set of items to share to the new repository (in addition to storage).
3446 Set of items to share to the new repository (in addition to storage).
3447 shallowfilestore
3447 shallowfilestore
3448 Indicates that storage for files should be shallow (not all ancestor
3448 Indicates that storage for files should be shallow (not all ancestor
3449 revisions are known).
3449 revisions are known).
3450 """
3450 """
3451 createopts = defaultcreateopts(ui, createopts=createopts)
3451 createopts = defaultcreateopts(ui, createopts=createopts)
3452
3452
3453 unknownopts = filterknowncreateopts(ui, createopts)
3453 unknownopts = filterknowncreateopts(ui, createopts)
3454
3454
3455 if not isinstance(unknownopts, dict):
3455 if not isinstance(unknownopts, dict):
3456 raise error.ProgrammingError(
3456 raise error.ProgrammingError(
3457 b'filterknowncreateopts() did not return a dict'
3457 b'filterknowncreateopts() did not return a dict'
3458 )
3458 )
3459
3459
3460 if unknownopts:
3460 if unknownopts:
3461 raise error.Abort(
3461 raise error.Abort(
3462 _(
3462 _(
3463 b'unable to create repository because of unknown '
3463 b'unable to create repository because of unknown '
3464 b'creation option: %s'
3464 b'creation option: %s'
3465 )
3465 )
3466 % b', '.join(sorted(unknownopts)),
3466 % b', '.join(sorted(unknownopts)),
3467 hint=_(b'is a required extension not loaded?'),
3467 hint=_(b'is a required extension not loaded?'),
3468 )
3468 )
3469
3469
3470 requirements = newreporequirements(ui, createopts=createopts)
3470 requirements = newreporequirements(ui, createopts=createopts)
3471 requirements -= checkrequirementscompat(ui, requirements)
3471 requirements -= checkrequirementscompat(ui, requirements)
3472
3472
3473 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3473 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3474
3474
3475 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3475 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3476 if hgvfs.exists():
3476 if hgvfs.exists():
3477 raise error.RepoError(_(b'repository %s already exists') % path)
3477 raise error.RepoError(_(b'repository %s already exists') % path)
3478
3478
3479 if b'sharedrepo' in createopts:
3479 if b'sharedrepo' in createopts:
3480 sharedpath = createopts[b'sharedrepo'].sharedpath
3480 sharedpath = createopts[b'sharedrepo'].sharedpath
3481
3481
3482 if createopts.get(b'sharedrelative'):
3482 if createopts.get(b'sharedrelative'):
3483 try:
3483 try:
3484 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3484 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3485 except (IOError, ValueError) as e:
3485 except (IOError, ValueError) as e:
3486 # ValueError is raised on Windows if the drive letters differ
3486 # ValueError is raised on Windows if the drive letters differ
3487 # on each path.
3487 # on each path.
3488 raise error.Abort(
3488 raise error.Abort(
3489 _(b'cannot calculate relative path'),
3489 _(b'cannot calculate relative path'),
3490 hint=stringutil.forcebytestr(e),
3490 hint=stringutil.forcebytestr(e),
3491 )
3491 )
3492
3492
3493 if not wdirvfs.exists():
3493 if not wdirvfs.exists():
3494 wdirvfs.makedirs()
3494 wdirvfs.makedirs()
3495
3495
3496 hgvfs.makedir(notindexed=True)
3496 hgvfs.makedir(notindexed=True)
3497 if b'sharedrepo' not in createopts:
3497 if b'sharedrepo' not in createopts:
3498 hgvfs.mkdir(b'cache')
3498 hgvfs.mkdir(b'cache')
3499 hgvfs.mkdir(b'wcache')
3499 hgvfs.mkdir(b'wcache')
3500
3500
3501 if b'store' in requirements and b'sharedrepo' not in createopts:
3501 if b'store' in requirements and b'sharedrepo' not in createopts:
3502 hgvfs.mkdir(b'store')
3502 hgvfs.mkdir(b'store')
3503
3503
3504 # We create an invalid changelog outside the store so very old
3504 # We create an invalid changelog outside the store so very old
3505 # Mercurial versions (which didn't know about the requirements
3505 # Mercurial versions (which didn't know about the requirements
3506 # file) encounter an error on reading the changelog. This
3506 # file) encounter an error on reading the changelog. This
3507 # effectively locks out old clients and prevents them from
3507 # effectively locks out old clients and prevents them from
3508 # mucking with a repo in an unknown format.
3508 # mucking with a repo in an unknown format.
3509 #
3509 #
3510 # The revlog header has version 2, which won't be recognized by
3510 # The revlog header has version 2, which won't be recognized by
3511 # such old clients.
3511 # such old clients.
3512 hgvfs.append(
3512 hgvfs.append(
3513 b'00changelog.i',
3513 b'00changelog.i',
3514 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3514 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3515 b'layout',
3515 b'layout',
3516 )
3516 )
3517
3517
3518 # Filter the requirements into working copy and store ones
3518 # Filter the requirements into working copy and store ones
3519 wcreq, storereq = scmutil.filterrequirements(requirements)
3519 wcreq, storereq = scmutil.filterrequirements(requirements)
3520 # write working copy ones
3520 # write working copy ones
3521 scmutil.writerequires(hgvfs, wcreq)
3521 scmutil.writerequires(hgvfs, wcreq)
3522 # If there are store requirements and the current repository
3522 # If there are store requirements and the current repository
3523 # is not a shared one, write stored requirements
3523 # is not a shared one, write stored requirements
3524 # For new shared repository, we don't need to write the store
3524 # For new shared repository, we don't need to write the store
3525 # requirements as they are already present in store requires
3525 # requirements as they are already present in store requires
3526 if storereq and b'sharedrepo' not in createopts:
3526 if storereq and b'sharedrepo' not in createopts:
3527 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3527 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3528 scmutil.writerequires(storevfs, storereq)
3528 scmutil.writerequires(storevfs, storereq)
3529
3529
3530 # Write out file telling readers where to find the shared store.
3530 # Write out file telling readers where to find the shared store.
3531 if b'sharedrepo' in createopts:
3531 if b'sharedrepo' in createopts:
3532 hgvfs.write(b'sharedpath', sharedpath)
3532 hgvfs.write(b'sharedpath', sharedpath)
3533
3533
3534 if createopts.get(b'shareditems'):
3534 if createopts.get(b'shareditems'):
3535 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3535 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3536 hgvfs.write(b'shared', shared)
3536 hgvfs.write(b'shared', shared)
3537
3537
3538
3538
3539 def poisonrepository(repo):
3539 def poisonrepository(repo):
3540 """Poison a repository instance so it can no longer be used."""
3540 """Poison a repository instance so it can no longer be used."""
3541 # Perform any cleanup on the instance.
3541 # Perform any cleanup on the instance.
3542 repo.close()
3542 repo.close()
3543
3543
3544 # Our strategy is to replace the type of the object with one that
3544 # Our strategy is to replace the type of the object with one that
3545 # has all attribute lookups result in error.
3545 # has all attribute lookups result in error.
3546 #
3546 #
3547 # But we have to allow the close() method because some constructors
3547 # But we have to allow the close() method because some constructors
3548 # of repos call close() on repo references.
3548 # of repos call close() on repo references.
3549 class poisonedrepository(object):
3549 class poisonedrepository(object):
3550 def __getattribute__(self, item):
3550 def __getattribute__(self, item):
3551 if item == 'close':
3551 if item == 'close':
3552 return object.__getattribute__(self, item)
3552 return object.__getattribute__(self, item)
3553
3553
3554 raise error.ProgrammingError(
3554 raise error.ProgrammingError(
3555 b'repo instances should not be used after unshare'
3555 b'repo instances should not be used after unshare'
3556 )
3556 )
3557
3557
3558 def close(self):
3558 def close(self):
3559 pass
3559 pass
3560
3560
3561 # We may have a repoview, which intercepts __setattr__. So be sure
3561 # We may have a repoview, which intercepts __setattr__. So be sure
3562 # we operate at the lowest level possible.
3562 # we operate at the lowest level possible.
3563 object.__setattr__(repo, '__class__', poisonedrepository)
3563 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,1984 +1,1984 b''
1 Set up a repo
1 Set up a repo
2
2
3 $ cat <<EOF >> $HGRCPATH
3 $ cat <<EOF >> $HGRCPATH
4 > [ui]
4 > [ui]
5 > interactive = true
5 > interactive = true
6 > [extensions]
6 > [extensions]
7 > record =
7 > record =
8 > EOF
8 > EOF
9
9
10 $ hg init a
10 $ hg init a
11 $ cd a
11 $ cd a
12
12
13 Select no files
13 Select no files
14
14
15 $ touch empty-rw
15 $ touch empty-rw
16 $ hg add empty-rw
16 $ hg add empty-rw
17
17
18 $ hg record --config ui.interactive=false
18 $ hg record --config ui.interactive=false
19 abort: running non-interactively, use commit instead
19 abort: running non-interactively, use commit instead
20 [255]
20 [255]
21 $ hg commit -i --config ui.interactive=false
21 $ hg commit -i --config ui.interactive=false
22 abort: running non-interactively
22 abort: running non-interactively
23 [10]
23 [10]
24 $ hg commit -i empty-rw<<EOF
24 $ hg commit -i empty-rw<<EOF
25 > n
25 > n
26 > EOF
26 > EOF
27 diff --git a/empty-rw b/empty-rw
27 diff --git a/empty-rw b/empty-rw
28 new file mode 100644
28 new file mode 100644
29 abort: empty commit message
29 abort: empty commit message
30 [10]
30 [10]
31
31
32 $ hg tip -p
32 $ hg tip -p
33 changeset: -1:000000000000
33 changeset: -1:000000000000
34 tag: tip
34 tag: tip
35 user:
35 user:
36 date: Thu Jan 01 00:00:00 1970 +0000
36 date: Thu Jan 01 00:00:00 1970 +0000
37
37
38
38
39
39
40 Select files but no hunks
40 Select files but no hunks
41
41
42 $ hg commit -i empty-rw<<EOF
42 $ hg commit -i empty-rw<<EOF
43 > y
43 > y
44 > n
44 > n
45 > EOF
45 > EOF
46 diff --git a/empty-rw b/empty-rw
46 diff --git a/empty-rw b/empty-rw
47 new file mode 100644
47 new file mode 100644
48 abort: empty commit message
48 abort: empty commit message
49 [10]
49 [10]
50
50
51 $ hg tip -p
51 $ hg tip -p
52 changeset: -1:000000000000
52 changeset: -1:000000000000
53 tag: tip
53 tag: tip
54 user:
54 user:
55 date: Thu Jan 01 00:00:00 1970 +0000
55 date: Thu Jan 01 00:00:00 1970 +0000
56
56
57
57
58
58
59 Abort for untracked
59 Abort for untracked
60
60
61 $ touch untracked
61 $ touch untracked
62 $ hg commit -i -m should-fail empty-rw untracked
62 $ hg commit -i -m should-fail empty-rw untracked
63 abort: untracked: file not tracked!
63 abort: untracked: file not tracked!
64 [255]
64 [10]
65 $ rm untracked
65 $ rm untracked
66
66
67 Record empty file
67 Record empty file
68
68
69 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
69 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
70 > y
70 > y
71 > EOF
71 > EOF
72 diff --git a/empty-rw b/empty-rw
72 diff --git a/empty-rw b/empty-rw
73 new file mode 100644
73 new file mode 100644
74
74
75 $ hg tip -p
75 $ hg tip -p
76 changeset: 0:c0708cf4e46e
76 changeset: 0:c0708cf4e46e
77 tag: tip
77 tag: tip
78 user: test
78 user: test
79 date: Thu Jan 01 00:00:00 1970 +0000
79 date: Thu Jan 01 00:00:00 1970 +0000
80 summary: empty
80 summary: empty
81
81
82
82
83
83
84 Summary shows we updated to the new cset
84 Summary shows we updated to the new cset
85
85
86 $ hg summary
86 $ hg summary
87 parent: 0:c0708cf4e46e tip
87 parent: 0:c0708cf4e46e tip
88 empty
88 empty
89 branch: default
89 branch: default
90 commit: (clean)
90 commit: (clean)
91 update: (current)
91 update: (current)
92 phases: 1 draft
92 phases: 1 draft
93
93
94 Rename empty file
94 Rename empty file
95
95
96 $ hg mv empty-rw empty-rename
96 $ hg mv empty-rw empty-rename
97 $ hg commit -i -d '1 0' -m rename<<EOF
97 $ hg commit -i -d '1 0' -m rename<<EOF
98 > y
98 > y
99 > EOF
99 > EOF
100 diff --git a/empty-rw b/empty-rename
100 diff --git a/empty-rw b/empty-rename
101 rename from empty-rw
101 rename from empty-rw
102 rename to empty-rename
102 rename to empty-rename
103 examine changes to 'empty-rw' and 'empty-rename'?
103 examine changes to 'empty-rw' and 'empty-rename'?
104 (enter ? for help) [Ynesfdaq?] y
104 (enter ? for help) [Ynesfdaq?] y
105
105
106
106
107 $ hg tip -p
107 $ hg tip -p
108 changeset: 1:d695e8dcb197
108 changeset: 1:d695e8dcb197
109 tag: tip
109 tag: tip
110 user: test
110 user: test
111 date: Thu Jan 01 00:00:01 1970 +0000
111 date: Thu Jan 01 00:00:01 1970 +0000
112 summary: rename
112 summary: rename
113
113
114
114
115
115
116 Copy empty file
116 Copy empty file
117
117
118 $ hg cp empty-rename empty-copy
118 $ hg cp empty-rename empty-copy
119 $ hg commit -i -d '2 0' -m copy<<EOF
119 $ hg commit -i -d '2 0' -m copy<<EOF
120 > y
120 > y
121 > EOF
121 > EOF
122 diff --git a/empty-rename b/empty-copy
122 diff --git a/empty-rename b/empty-copy
123 copy from empty-rename
123 copy from empty-rename
124 copy to empty-copy
124 copy to empty-copy
125 examine changes to 'empty-rename' and 'empty-copy'?
125 examine changes to 'empty-rename' and 'empty-copy'?
126 (enter ? for help) [Ynesfdaq?] y
126 (enter ? for help) [Ynesfdaq?] y
127
127
128
128
129 $ hg tip -p
129 $ hg tip -p
130 changeset: 2:1d4b90bea524
130 changeset: 2:1d4b90bea524
131 tag: tip
131 tag: tip
132 user: test
132 user: test
133 date: Thu Jan 01 00:00:02 1970 +0000
133 date: Thu Jan 01 00:00:02 1970 +0000
134 summary: copy
134 summary: copy
135
135
136
136
137
137
138 Delete empty file
138 Delete empty file
139
139
140 $ hg rm empty-copy
140 $ hg rm empty-copy
141 $ hg commit -i -d '3 0' -m delete<<EOF
141 $ hg commit -i -d '3 0' -m delete<<EOF
142 > y
142 > y
143 > EOF
143 > EOF
144 diff --git a/empty-copy b/empty-copy
144 diff --git a/empty-copy b/empty-copy
145 deleted file mode 100644
145 deleted file mode 100644
146 examine changes to 'empty-copy'?
146 examine changes to 'empty-copy'?
147 (enter ? for help) [Ynesfdaq?] y
147 (enter ? for help) [Ynesfdaq?] y
148
148
149
149
150 $ hg tip -p
150 $ hg tip -p
151 changeset: 3:b39a238f01a1
151 changeset: 3:b39a238f01a1
152 tag: tip
152 tag: tip
153 user: test
153 user: test
154 date: Thu Jan 01 00:00:03 1970 +0000
154 date: Thu Jan 01 00:00:03 1970 +0000
155 summary: delete
155 summary: delete
156
156
157
157
158
158
159 Add binary file
159 Add binary file
160
160
161 $ hg bundle --type v1 --base -2 tip.bundle
161 $ hg bundle --type v1 --base -2 tip.bundle
162 1 changesets found
162 1 changesets found
163 $ hg add tip.bundle
163 $ hg add tip.bundle
164 $ hg commit -i -d '4 0' -m binary<<EOF
164 $ hg commit -i -d '4 0' -m binary<<EOF
165 > y
165 > y
166 > EOF
166 > EOF
167 diff --git a/tip.bundle b/tip.bundle
167 diff --git a/tip.bundle b/tip.bundle
168 new file mode 100644
168 new file mode 100644
169 this is a binary file
169 this is a binary file
170 examine changes to 'tip.bundle'?
170 examine changes to 'tip.bundle'?
171 (enter ? for help) [Ynesfdaq?] y
171 (enter ? for help) [Ynesfdaq?] y
172
172
173
173
174 $ hg tip -p
174 $ hg tip -p
175 changeset: 4:ad816da3711e
175 changeset: 4:ad816da3711e
176 tag: tip
176 tag: tip
177 user: test
177 user: test
178 date: Thu Jan 01 00:00:04 1970 +0000
178 date: Thu Jan 01 00:00:04 1970 +0000
179 summary: binary
179 summary: binary
180
180
181 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
181 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
182 Binary file tip.bundle has changed
182 Binary file tip.bundle has changed
183
183
184
184
185 Change binary file
185 Change binary file
186
186
187 $ hg bundle --base -2 --type v1 tip.bundle
187 $ hg bundle --base -2 --type v1 tip.bundle
188 1 changesets found
188 1 changesets found
189 $ hg commit -i -d '5 0' -m binary-change<<EOF
189 $ hg commit -i -d '5 0' -m binary-change<<EOF
190 > y
190 > y
191 > EOF
191 > EOF
192 diff --git a/tip.bundle b/tip.bundle
192 diff --git a/tip.bundle b/tip.bundle
193 this modifies a binary file (all or nothing)
193 this modifies a binary file (all or nothing)
194 examine changes to 'tip.bundle'?
194 examine changes to 'tip.bundle'?
195 (enter ? for help) [Ynesfdaq?] y
195 (enter ? for help) [Ynesfdaq?] y
196
196
197
197
198 $ hg tip -p
198 $ hg tip -p
199 changeset: 5:dccd6f3eb485
199 changeset: 5:dccd6f3eb485
200 tag: tip
200 tag: tip
201 user: test
201 user: test
202 date: Thu Jan 01 00:00:05 1970 +0000
202 date: Thu Jan 01 00:00:05 1970 +0000
203 summary: binary-change
203 summary: binary-change
204
204
205 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
205 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
206 Binary file tip.bundle has changed
206 Binary file tip.bundle has changed
207
207
208
208
209 Rename and change binary file
209 Rename and change binary file
210
210
211 $ hg mv tip.bundle top.bundle
211 $ hg mv tip.bundle top.bundle
212 $ hg bundle --base -2 --type v1 top.bundle
212 $ hg bundle --base -2 --type v1 top.bundle
213 1 changesets found
213 1 changesets found
214 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
214 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
215 > y
215 > y
216 > EOF
216 > EOF
217 diff --git a/tip.bundle b/top.bundle
217 diff --git a/tip.bundle b/top.bundle
218 rename from tip.bundle
218 rename from tip.bundle
219 rename to top.bundle
219 rename to top.bundle
220 this modifies a binary file (all or nothing)
220 this modifies a binary file (all or nothing)
221 examine changes to 'tip.bundle' and 'top.bundle'?
221 examine changes to 'tip.bundle' and 'top.bundle'?
222 (enter ? for help) [Ynesfdaq?] y
222 (enter ? for help) [Ynesfdaq?] y
223
223
224
224
225 $ hg tip -p
225 $ hg tip -p
226 changeset: 6:7fa44105f5b3
226 changeset: 6:7fa44105f5b3
227 tag: tip
227 tag: tip
228 user: test
228 user: test
229 date: Thu Jan 01 00:00:06 1970 +0000
229 date: Thu Jan 01 00:00:06 1970 +0000
230 summary: binary-change-rename
230 summary: binary-change-rename
231
231
232 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
232 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
233 Binary file tip.bundle has changed
233 Binary file tip.bundle has changed
234 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
234 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
235 Binary file top.bundle has changed
235 Binary file top.bundle has changed
236
236
237
237
238 Add plain file
238 Add plain file
239
239
240 $ for i in 1 2 3 4 5 6 7 8 9 10; do
240 $ for i in 1 2 3 4 5 6 7 8 9 10; do
241 > echo $i >> plain
241 > echo $i >> plain
242 > done
242 > done
243
243
244 $ hg add plain
244 $ hg add plain
245 $ hg commit -i -d '7 0' -m plain plain<<EOF
245 $ hg commit -i -d '7 0' -m plain plain<<EOF
246 > y
246 > y
247 > y
247 > y
248 > EOF
248 > EOF
249 diff --git a/plain b/plain
249 diff --git a/plain b/plain
250 new file mode 100644
250 new file mode 100644
251 @@ -0,0 +1,10 @@
251 @@ -0,0 +1,10 @@
252 +1
252 +1
253 +2
253 +2
254 +3
254 +3
255 +4
255 +4
256 +5
256 +5
257 +6
257 +6
258 +7
258 +7
259 +8
259 +8
260 +9
260 +9
261 +10
261 +10
262 record this change to 'plain'?
262 record this change to 'plain'?
263 (enter ? for help) [Ynesfdaq?] y
263 (enter ? for help) [Ynesfdaq?] y
264
264
265 $ hg tip -p
265 $ hg tip -p
266 changeset: 7:11fb457c1be4
266 changeset: 7:11fb457c1be4
267 tag: tip
267 tag: tip
268 user: test
268 user: test
269 date: Thu Jan 01 00:00:07 1970 +0000
269 date: Thu Jan 01 00:00:07 1970 +0000
270 summary: plain
270 summary: plain
271
271
272 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
272 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
273 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
273 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
274 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
274 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
275 @@ -0,0 +1,10 @@
275 @@ -0,0 +1,10 @@
276 +1
276 +1
277 +2
277 +2
278 +3
278 +3
279 +4
279 +4
280 +5
280 +5
281 +6
281 +6
282 +7
282 +7
283 +8
283 +8
284 +9
284 +9
285 +10
285 +10
286
286
287 Modify end of plain file with username unset
287 Modify end of plain file with username unset
288
288
289 $ echo 11 >> plain
289 $ echo 11 >> plain
290 $ unset HGUSER
290 $ unset HGUSER
291 $ hg commit -i --config ui.username= -d '8 0' -m end plain
291 $ hg commit -i --config ui.username= -d '8 0' -m end plain
292 abort: no username supplied
292 abort: no username supplied
293 (use 'hg config --edit' to set your username)
293 (use 'hg config --edit' to set your username)
294 [255]
294 [255]
295
295
296
296
297 Modify end of plain file, also test that diffopts are accounted for
297 Modify end of plain file, also test that diffopts are accounted for
298
298
299 $ HGUSER="test"
299 $ HGUSER="test"
300 $ export HGUSER
300 $ export HGUSER
301 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
301 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
302 > y
302 > y
303 > y
303 > y
304 > EOF
304 > EOF
305 diff --git a/plain b/plain
305 diff --git a/plain b/plain
306 1 hunks, 1 lines changed
306 1 hunks, 1 lines changed
307 @@ -8,3 +8,4 @@ 7
307 @@ -8,3 +8,4 @@ 7
308 8
308 8
309 9
309 9
310 10
310 10
311 +11
311 +11
312 record this change to 'plain'?
312 record this change to 'plain'?
313 (enter ? for help) [Ynesfdaq?] y
313 (enter ? for help) [Ynesfdaq?] y
314
314
315
315
316 Modify end of plain file, no EOL
316 Modify end of plain file, no EOL
317
317
318 $ hg tip --template '{node}' >> plain
318 $ hg tip --template '{node}' >> plain
319 $ hg commit -i -d '9 0' -m noeol plain <<EOF
319 $ hg commit -i -d '9 0' -m noeol plain <<EOF
320 > y
320 > y
321 > y
321 > y
322 > EOF
322 > EOF
323 diff --git a/plain b/plain
323 diff --git a/plain b/plain
324 1 hunks, 1 lines changed
324 1 hunks, 1 lines changed
325 @@ -9,3 +9,4 @@ 8
325 @@ -9,3 +9,4 @@ 8
326 9
326 9
327 10
327 10
328 11
328 11
329 +7264f99c5f5ff3261504828afa4fb4d406c3af54
329 +7264f99c5f5ff3261504828afa4fb4d406c3af54
330 \ No newline at end of file
330 \ No newline at end of file
331 record this change to 'plain'?
331 record this change to 'plain'?
332 (enter ? for help) [Ynesfdaq?] y
332 (enter ? for help) [Ynesfdaq?] y
333
333
334
334
335 Record showfunc should preserve function across sections
335 Record showfunc should preserve function across sections
336
336
337 $ cat > f1.py <<NO_CHECK_EOF
337 $ cat > f1.py <<NO_CHECK_EOF
338 > def annotate(ui, repo, *pats, **opts):
338 > def annotate(ui, repo, *pats, **opts):
339 > """show changeset information by line for each file
339 > """show changeset information by line for each file
340 >
340 >
341 > List changes in files, showing the revision id responsible for
341 > List changes in files, showing the revision id responsible for
342 > each line.
342 > each line.
343 >
343 >
344 > This command is useful for discovering when a change was made and
344 > This command is useful for discovering when a change was made and
345 > by whom.
345 > by whom.
346 >
346 >
347 > If you include -f/-u/-d, the revision number is suppressed unless
347 > If you include -f/-u/-d, the revision number is suppressed unless
348 > you also include -the revision number is suppressed unless
348 > you also include -the revision number is suppressed unless
349 > you also include -n.
349 > you also include -n.
350 >
350 >
351 > Without the -a/--text option, annotate will avoid processing files
351 > Without the -a/--text option, annotate will avoid processing files
352 > it detects as binary. With -a, annotate will annotate the file
352 > it detects as binary. With -a, annotate will annotate the file
353 > anyway, although the results will probably be neither useful
353 > anyway, although the results will probably be neither useful
354 > nor desirable.
354 > nor desirable.
355 >
355 >
356 > Returns 0 on success.
356 > Returns 0 on success.
357 > """
357 > """
358 > return 0
358 > return 0
359 > def archive(ui, repo, dest, **opts):
359 > def archive(ui, repo, dest, **opts):
360 > '''create an unversioned archive of a repository revision
360 > '''create an unversioned archive of a repository revision
361 >
361 >
362 > By default, the revision used is the parent of the working
362 > By default, the revision used is the parent of the working
363 > directory; use -r/--rev to specify a different revision.
363 > directory; use -r/--rev to specify a different revision.
364 >
364 >
365 > The archive type is automatically detected based on file
365 > The archive type is automatically detected based on file
366 > extension (to override, use -t/--type).
366 > extension (to override, use -t/--type).
367 >
367 >
368 > .. container:: verbose
368 > .. container:: verbose
369 >
369 >
370 > Valid types are:
370 > Valid types are:
371 > NO_CHECK_EOF
371 > NO_CHECK_EOF
372 $ hg add f1.py
372 $ hg add f1.py
373 $ hg commit -m funcs
373 $ hg commit -m funcs
374 $ cat > f1.py <<NO_CHECK_EOF
374 $ cat > f1.py <<NO_CHECK_EOF
375 > def annotate(ui, repo, *pats, **opts):
375 > def annotate(ui, repo, *pats, **opts):
376 > """show changeset information by line for each file
376 > """show changeset information by line for each file
377 >
377 >
378 > List changes in files, showing the revision id responsible for
378 > List changes in files, showing the revision id responsible for
379 > each line
379 > each line
380 >
380 >
381 > This command is useful for discovering when a change was made and
381 > This command is useful for discovering when a change was made and
382 > by whom.
382 > by whom.
383 >
383 >
384 > Without the -a/--text option, annotate will avoid processing files
384 > Without the -a/--text option, annotate will avoid processing files
385 > it detects as binary. With -a, annotate will annotate the file
385 > it detects as binary. With -a, annotate will annotate the file
386 > anyway, although the results will probably be neither useful
386 > anyway, although the results will probably be neither useful
387 > nor desirable.
387 > nor desirable.
388 >
388 >
389 > Returns 0 on success.
389 > Returns 0 on success.
390 > """
390 > """
391 > return 0
391 > return 0
392 > def archive(ui, repo, dest, **opts):
392 > def archive(ui, repo, dest, **opts):
393 > '''create an unversioned archive of a repository revision
393 > '''create an unversioned archive of a repository revision
394 >
394 >
395 > By default, the revision used is the parent of the working
395 > By default, the revision used is the parent of the working
396 > directory; use -r/--rev to specify a different revision.
396 > directory; use -r/--rev to specify a different revision.
397 >
397 >
398 > The archive type is automatically detected based on file
398 > The archive type is automatically detected based on file
399 > extension (or override using -t/--type).
399 > extension (or override using -t/--type).
400 >
400 >
401 > .. container:: verbose
401 > .. container:: verbose
402 >
402 >
403 > Valid types are:
403 > Valid types are:
404 > NO_CHECK_EOF
404 > NO_CHECK_EOF
405 $ hg commit -i -m interactive <<EOF
405 $ hg commit -i -m interactive <<EOF
406 > y
406 > y
407 > y
407 > y
408 > y
408 > y
409 > y
409 > y
410 > EOF
410 > EOF
411 diff --git a/f1.py b/f1.py
411 diff --git a/f1.py b/f1.py
412 3 hunks, 6 lines changed
412 3 hunks, 6 lines changed
413 examine changes to 'f1.py'?
413 examine changes to 'f1.py'?
414 (enter ? for help) [Ynesfdaq?] y
414 (enter ? for help) [Ynesfdaq?] y
415
415
416 @@ -2,8 +2,8 @@ def annotate(ui, repo, *pats, **opts):
416 @@ -2,8 +2,8 @@ def annotate(ui, repo, *pats, **opts):
417 """show changeset information by line for each file
417 """show changeset information by line for each file
418
418
419 List changes in files, showing the revision id responsible for
419 List changes in files, showing the revision id responsible for
420 - each line.
420 - each line.
421 + each line
421 + each line
422
422
423 This command is useful for discovering when a change was made and
423 This command is useful for discovering when a change was made and
424 by whom.
424 by whom.
425
425
426 record change 1/3 to 'f1.py'?
426 record change 1/3 to 'f1.py'?
427 (enter ? for help) [Ynesfdaq?] y
427 (enter ? for help) [Ynesfdaq?] y
428
428
429 @@ -6,11 +6,7 @@ def annotate(ui, repo, *pats, **opts):
429 @@ -6,11 +6,7 @@ def annotate(ui, repo, *pats, **opts):
430
430
431 This command is useful for discovering when a change was made and
431 This command is useful for discovering when a change was made and
432 by whom.
432 by whom.
433
433
434 - If you include -f/-u/-d, the revision number is suppressed unless
434 - If you include -f/-u/-d, the revision number is suppressed unless
435 - you also include -the revision number is suppressed unless
435 - you also include -the revision number is suppressed unless
436 - you also include -n.
436 - you also include -n.
437 -
437 -
438 Without the -a/--text option, annotate will avoid processing files
438 Without the -a/--text option, annotate will avoid processing files
439 it detects as binary. With -a, annotate will annotate the file
439 it detects as binary. With -a, annotate will annotate the file
440 anyway, although the results will probably be neither useful
440 anyway, although the results will probably be neither useful
441 record change 2/3 to 'f1.py'?
441 record change 2/3 to 'f1.py'?
442 (enter ? for help) [Ynesfdaq?] y
442 (enter ? for help) [Ynesfdaq?] y
443
443
444 @@ -26,7 +22,7 @@ def archive(ui, repo, dest, **opts):
444 @@ -26,7 +22,7 @@ def archive(ui, repo, dest, **opts):
445 directory; use -r/--rev to specify a different revision.
445 directory; use -r/--rev to specify a different revision.
446
446
447 The archive type is automatically detected based on file
447 The archive type is automatically detected based on file
448 - extension (to override, use -t/--type).
448 - extension (to override, use -t/--type).
449 + extension (or override using -t/--type).
449 + extension (or override using -t/--type).
450
450
451 .. container:: verbose
451 .. container:: verbose
452
452
453 record change 3/3 to 'f1.py'?
453 record change 3/3 to 'f1.py'?
454 (enter ? for help) [Ynesfdaq?] y
454 (enter ? for help) [Ynesfdaq?] y
455
455
456
456
457 Modify end of plain file, add EOL
457 Modify end of plain file, add EOL
458
458
459 $ echo >> plain
459 $ echo >> plain
460 $ echo 1 > plain2
460 $ echo 1 > plain2
461 $ hg add plain2
461 $ hg add plain2
462 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
462 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
463 > y
463 > y
464 > y
464 > y
465 > y
465 > y
466 > y
466 > y
467 > EOF
467 > EOF
468 diff --git a/plain b/plain
468 diff --git a/plain b/plain
469 1 hunks, 1 lines changed
469 1 hunks, 1 lines changed
470 @@ -9,4 +9,4 @@ 8
470 @@ -9,4 +9,4 @@ 8
471 9
471 9
472 10
472 10
473 11
473 11
474 -7264f99c5f5ff3261504828afa4fb4d406c3af54
474 -7264f99c5f5ff3261504828afa4fb4d406c3af54
475 \ No newline at end of file
475 \ No newline at end of file
476 +7264f99c5f5ff3261504828afa4fb4d406c3af54
476 +7264f99c5f5ff3261504828afa4fb4d406c3af54
477 record change 1/2 to 'plain'?
477 record change 1/2 to 'plain'?
478 (enter ? for help) [Ynesfdaq?] y
478 (enter ? for help) [Ynesfdaq?] y
479
479
480 diff --git a/plain2 b/plain2
480 diff --git a/plain2 b/plain2
481 new file mode 100644
481 new file mode 100644
482 @@ -0,0 +1,1 @@
482 @@ -0,0 +1,1 @@
483 +1
483 +1
484 record change 2/2 to 'plain2'?
484 record change 2/2 to 'plain2'?
485 (enter ? for help) [Ynesfdaq?] y
485 (enter ? for help) [Ynesfdaq?] y
486
486
487 Modify beginning, trim end, record both, add another file to test
487 Modify beginning, trim end, record both, add another file to test
488 changes numbering
488 changes numbering
489
489
490 $ rm plain
490 $ rm plain
491 $ for i in 2 2 3 4 5 6 7 8 9 10; do
491 $ for i in 2 2 3 4 5 6 7 8 9 10; do
492 > echo $i >> plain
492 > echo $i >> plain
493 > done
493 > done
494 $ echo 2 >> plain2
494 $ echo 2 >> plain2
495
495
496 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
496 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
497 > y
497 > y
498 > y
498 > y
499 > y
499 > y
500 > y
500 > y
501 > y
501 > y
502 > EOF
502 > EOF
503 diff --git a/plain b/plain
503 diff --git a/plain b/plain
504 2 hunks, 3 lines changed
504 2 hunks, 3 lines changed
505 @@ -1,4 +1,4 @@
505 @@ -1,4 +1,4 @@
506 -1
506 -1
507 +2
507 +2
508 2
508 2
509 3
509 3
510 4
510 4
511 record change 1/3 to 'plain'?
511 record change 1/3 to 'plain'?
512 (enter ? for help) [Ynesfdaq?] y
512 (enter ? for help) [Ynesfdaq?] y
513
513
514 @@ -8,5 +8,3 @@ 7
514 @@ -8,5 +8,3 @@ 7
515 8
515 8
516 9
516 9
517 10
517 10
518 -11
518 -11
519 -7264f99c5f5ff3261504828afa4fb4d406c3af54
519 -7264f99c5f5ff3261504828afa4fb4d406c3af54
520 record change 2/3 to 'plain'?
520 record change 2/3 to 'plain'?
521 (enter ? for help) [Ynesfdaq?] y
521 (enter ? for help) [Ynesfdaq?] y
522
522
523 diff --git a/plain2 b/plain2
523 diff --git a/plain2 b/plain2
524 1 hunks, 1 lines changed
524 1 hunks, 1 lines changed
525 @@ -1,1 +1,2 @@
525 @@ -1,1 +1,2 @@
526 1
526 1
527 +2
527 +2
528 record change 3/3 to 'plain2'?
528 record change 3/3 to 'plain2'?
529 (enter ? for help) [Ynesfdaq?] y
529 (enter ? for help) [Ynesfdaq?] y
530
530
531
531
532 $ hg tip -p
532 $ hg tip -p
533 changeset: 13:f941910cff62
533 changeset: 13:f941910cff62
534 tag: tip
534 tag: tip
535 user: test
535 user: test
536 date: Thu Jan 01 00:00:10 1970 +0000
536 date: Thu Jan 01 00:00:10 1970 +0000
537 summary: begin-and-end
537 summary: begin-and-end
538
538
539 diff -r 33abe24d946c -r f941910cff62 plain
539 diff -r 33abe24d946c -r f941910cff62 plain
540 --- a/plain Thu Jan 01 00:00:10 1970 +0000
540 --- a/plain Thu Jan 01 00:00:10 1970 +0000
541 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
541 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
542 @@ -1,4 +1,4 @@
542 @@ -1,4 +1,4 @@
543 -1
543 -1
544 +2
544 +2
545 2
545 2
546 3
546 3
547 4
547 4
548 @@ -8,5 +8,3 @@
548 @@ -8,5 +8,3 @@
549 8
549 8
550 9
550 9
551 10
551 10
552 -11
552 -11
553 -7264f99c5f5ff3261504828afa4fb4d406c3af54
553 -7264f99c5f5ff3261504828afa4fb4d406c3af54
554 diff -r 33abe24d946c -r f941910cff62 plain2
554 diff -r 33abe24d946c -r f941910cff62 plain2
555 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
555 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
556 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
556 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
557 @@ -1,1 +1,2 @@
557 @@ -1,1 +1,2 @@
558 1
558 1
559 +2
559 +2
560
560
561
561
562 Trim beginning, modify end
562 Trim beginning, modify end
563
563
564 $ rm plain
564 $ rm plain
565 > for i in 4 5 6 7 8 9 10.new; do
565 > for i in 4 5 6 7 8 9 10.new; do
566 > echo $i >> plain
566 > echo $i >> plain
567 > done
567 > done
568
568
569 Record end
569 Record end
570
570
571 $ hg commit -i -d '11 0' -m end-only plain <<EOF
571 $ hg commit -i -d '11 0' -m end-only plain <<EOF
572 > n
572 > n
573 > y
573 > y
574 > EOF
574 > EOF
575 diff --git a/plain b/plain
575 diff --git a/plain b/plain
576 2 hunks, 4 lines changed
576 2 hunks, 4 lines changed
577 @@ -1,9 +1,6 @@
577 @@ -1,9 +1,6 @@
578 -2
578 -2
579 -2
579 -2
580 -3
580 -3
581 4
581 4
582 5
582 5
583 6
583 6
584 7
584 7
585 8
585 8
586 9
586 9
587 record change 1/2 to 'plain'?
587 record change 1/2 to 'plain'?
588 (enter ? for help) [Ynesfdaq?] n
588 (enter ? for help) [Ynesfdaq?] n
589
589
590 @@ -4,7 +1,7 @@
590 @@ -4,7 +1,7 @@
591 4
591 4
592 5
592 5
593 6
593 6
594 7
594 7
595 8
595 8
596 9
596 9
597 -10
597 -10
598 +10.new
598 +10.new
599 record change 2/2 to 'plain'?
599 record change 2/2 to 'plain'?
600 (enter ? for help) [Ynesfdaq?] y
600 (enter ? for help) [Ynesfdaq?] y
601
601
602
602
603 $ hg tip -p
603 $ hg tip -p
604 changeset: 14:4915f538659b
604 changeset: 14:4915f538659b
605 tag: tip
605 tag: tip
606 user: test
606 user: test
607 date: Thu Jan 01 00:00:11 1970 +0000
607 date: Thu Jan 01 00:00:11 1970 +0000
608 summary: end-only
608 summary: end-only
609
609
610 diff -r f941910cff62 -r 4915f538659b plain
610 diff -r f941910cff62 -r 4915f538659b plain
611 --- a/plain Thu Jan 01 00:00:10 1970 +0000
611 --- a/plain Thu Jan 01 00:00:10 1970 +0000
612 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
612 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
613 @@ -7,4 +7,4 @@
613 @@ -7,4 +7,4 @@
614 7
614 7
615 8
615 8
616 9
616 9
617 -10
617 -10
618 +10.new
618 +10.new
619
619
620
620
621 Record beginning
621 Record beginning
622
622
623 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
623 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
624 > y
624 > y
625 > y
625 > y
626 > EOF
626 > EOF
627 diff --git a/plain b/plain
627 diff --git a/plain b/plain
628 1 hunks, 3 lines changed
628 1 hunks, 3 lines changed
629 @@ -1,6 +1,3 @@
629 @@ -1,6 +1,3 @@
630 -2
630 -2
631 -2
631 -2
632 -3
632 -3
633 4
633 4
634 5
634 5
635 6
635 6
636 record this change to 'plain'?
636 record this change to 'plain'?
637 (enter ? for help) [Ynesfdaq?] y
637 (enter ? for help) [Ynesfdaq?] y
638
638
639
639
640 $ hg tip -p
640 $ hg tip -p
641 changeset: 15:1b1f93d4b94b
641 changeset: 15:1b1f93d4b94b
642 tag: tip
642 tag: tip
643 user: test
643 user: test
644 date: Thu Jan 01 00:00:12 1970 +0000
644 date: Thu Jan 01 00:00:12 1970 +0000
645 summary: begin-only
645 summary: begin-only
646
646
647 diff -r 4915f538659b -r 1b1f93d4b94b plain
647 diff -r 4915f538659b -r 1b1f93d4b94b plain
648 --- a/plain Thu Jan 01 00:00:11 1970 +0000
648 --- a/plain Thu Jan 01 00:00:11 1970 +0000
649 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
649 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
650 @@ -1,6 +1,3 @@
650 @@ -1,6 +1,3 @@
651 -2
651 -2
652 -2
652 -2
653 -3
653 -3
654 4
654 4
655 5
655 5
656 6
656 6
657
657
658
658
659 Add to beginning, trim from end
659 Add to beginning, trim from end
660
660
661 $ rm plain
661 $ rm plain
662 $ for i in 1 2 3 4 5 6 7 8 9; do
662 $ for i in 1 2 3 4 5 6 7 8 9; do
663 > echo $i >> plain
663 > echo $i >> plain
664 > done
664 > done
665
665
666 Record end
666 Record end
667
667
668 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
668 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
669 > n
669 > n
670 > y
670 > y
671 > EOF
671 > EOF
672 diff --git a/plain b/plain
672 diff --git a/plain b/plain
673 2 hunks, 4 lines changed
673 2 hunks, 4 lines changed
674 @@ -1,6 +1,9 @@
674 @@ -1,6 +1,9 @@
675 +1
675 +1
676 +2
676 +2
677 +3
677 +3
678 4
678 4
679 5
679 5
680 6
680 6
681 7
681 7
682 8
682 8
683 9
683 9
684 record change 1/2 to 'plain'?
684 record change 1/2 to 'plain'?
685 (enter ? for help) [Ynesfdaq?] n
685 (enter ? for help) [Ynesfdaq?] n
686
686
687 @@ -1,7 +4,6 @@
687 @@ -1,7 +4,6 @@
688 4
688 4
689 5
689 5
690 6
690 6
691 7
691 7
692 8
692 8
693 9
693 9
694 -10.new
694 -10.new
695 record change 2/2 to 'plain'?
695 record change 2/2 to 'plain'?
696 (enter ? for help) [Ynesfdaq?] y
696 (enter ? for help) [Ynesfdaq?] y
697
697
698
698
699 Add to beginning, middle, end
699 Add to beginning, middle, end
700
700
701 $ rm plain
701 $ rm plain
702 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
702 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
703 > echo $i >> plain
703 > echo $i >> plain
704 > done
704 > done
705
705
706 Record beginning, middle, and test that format-breaking diffopts are ignored
706 Record beginning, middle, and test that format-breaking diffopts are ignored
707
707
708 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
708 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
709 > y
709 > y
710 > y
710 > y
711 > n
711 > n
712 > EOF
712 > EOF
713 diff --git a/plain b/plain
713 diff --git a/plain b/plain
714 3 hunks, 7 lines changed
714 3 hunks, 7 lines changed
715 @@ -1,2 +1,5 @@
715 @@ -1,2 +1,5 @@
716 +1
716 +1
717 +2
717 +2
718 +3
718 +3
719 4
719 4
720 5
720 5
721 record change 1/3 to 'plain'?
721 record change 1/3 to 'plain'?
722 (enter ? for help) [Ynesfdaq?] y
722 (enter ? for help) [Ynesfdaq?] y
723
723
724 @@ -1,6 +4,8 @@
724 @@ -1,6 +4,8 @@
725 4
725 4
726 5
726 5
727 +5.new
727 +5.new
728 +5.reallynew
728 +5.reallynew
729 6
729 6
730 7
730 7
731 8
731 8
732 9
732 9
733 record change 2/3 to 'plain'?
733 record change 2/3 to 'plain'?
734 (enter ? for help) [Ynesfdaq?] y
734 (enter ? for help) [Ynesfdaq?] y
735
735
736 @@ -3,4 +8,6 @@
736 @@ -3,4 +8,6 @@
737 6
737 6
738 7
738 7
739 8
739 8
740 9
740 9
741 +10
741 +10
742 +11
742 +11
743 record change 3/3 to 'plain'?
743 record change 3/3 to 'plain'?
744 (enter ? for help) [Ynesfdaq?] n
744 (enter ? for help) [Ynesfdaq?] n
745
745
746
746
747 $ hg tip -p
747 $ hg tip -p
748 changeset: 17:41cf3f5c55ae
748 changeset: 17:41cf3f5c55ae
749 tag: tip
749 tag: tip
750 user: test
750 user: test
751 date: Thu Jan 01 00:00:14 1970 +0000
751 date: Thu Jan 01 00:00:14 1970 +0000
752 summary: middle-only
752 summary: middle-only
753
753
754 diff -r a69d252246e1 -r 41cf3f5c55ae plain
754 diff -r a69d252246e1 -r 41cf3f5c55ae plain
755 --- a/plain Thu Jan 01 00:00:13 1970 +0000
755 --- a/plain Thu Jan 01 00:00:13 1970 +0000
756 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
756 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
757 @@ -1,5 +1,10 @@
757 @@ -1,5 +1,10 @@
758 +1
758 +1
759 +2
759 +2
760 +3
760 +3
761 4
761 4
762 5
762 5
763 +5.new
763 +5.new
764 +5.reallynew
764 +5.reallynew
765 6
765 6
766 7
766 7
767 8
767 8
768
768
769
769
770 Record end
770 Record end
771
771
772 $ hg commit -i -d '15 0' -m end-only plain <<EOF
772 $ hg commit -i -d '15 0' -m end-only plain <<EOF
773 > y
773 > y
774 > y
774 > y
775 > EOF
775 > EOF
776 diff --git a/plain b/plain
776 diff --git a/plain b/plain
777 1 hunks, 2 lines changed
777 1 hunks, 2 lines changed
778 @@ -9,3 +9,5 @@ 6
778 @@ -9,3 +9,5 @@ 6
779 7
779 7
780 8
780 8
781 9
781 9
782 +10
782 +10
783 +11
783 +11
784 record this change to 'plain'?
784 record this change to 'plain'?
785 (enter ? for help) [Ynesfdaq?] y
785 (enter ? for help) [Ynesfdaq?] y
786
786
787
787
788 $ hg tip -p
788 $ hg tip -p
789 changeset: 18:58a72f46bc24
789 changeset: 18:58a72f46bc24
790 tag: tip
790 tag: tip
791 user: test
791 user: test
792 date: Thu Jan 01 00:00:15 1970 +0000
792 date: Thu Jan 01 00:00:15 1970 +0000
793 summary: end-only
793 summary: end-only
794
794
795 diff -r 41cf3f5c55ae -r 58a72f46bc24 plain
795 diff -r 41cf3f5c55ae -r 58a72f46bc24 plain
796 --- a/plain Thu Jan 01 00:00:14 1970 +0000
796 --- a/plain Thu Jan 01 00:00:14 1970 +0000
797 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
797 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
798 @@ -9,3 +9,5 @@
798 @@ -9,3 +9,5 @@
799 7
799 7
800 8
800 8
801 9
801 9
802 +10
802 +10
803 +11
803 +11
804
804
805 Interactive commit can name a directory instead of files (issue6131)
805 Interactive commit can name a directory instead of files (issue6131)
806
806
807 $ mkdir subdir
807 $ mkdir subdir
808 $ echo a > subdir/a
808 $ echo a > subdir/a
809 $ hg ci -d '16 0' -i subdir -Amsubdir <<EOF
809 $ hg ci -d '16 0' -i subdir -Amsubdir <<EOF
810 > y
810 > y
811 > y
811 > y
812 > EOF
812 > EOF
813 adding subdir/a
813 adding subdir/a
814 diff --git a/subdir/a b/subdir/a
814 diff --git a/subdir/a b/subdir/a
815 new file mode 100644
815 new file mode 100644
816 examine changes to 'subdir/a'?
816 examine changes to 'subdir/a'?
817 (enter ? for help) [Ynesfdaq?] y
817 (enter ? for help) [Ynesfdaq?] y
818
818
819 @@ -0,0 +1,1 @@
819 @@ -0,0 +1,1 @@
820 +a
820 +a
821 record this change to 'subdir/a'?
821 record this change to 'subdir/a'?
822 (enter ? for help) [Ynesfdaq?] y
822 (enter ? for help) [Ynesfdaq?] y
823
823
824 $ cd subdir
824 $ cd subdir
825
825
826 $ echo a >> a
826 $ echo a >> a
827 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
827 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
828 > y
828 > y
829 > y
829 > y
830 > EOF
830 > EOF
831 diff --git a/subdir/a b/subdir/a
831 diff --git a/subdir/a b/subdir/a
832 1 hunks, 1 lines changed
832 1 hunks, 1 lines changed
833 @@ -1,1 +1,2 @@
833 @@ -1,1 +1,2 @@
834 a
834 a
835 +a
835 +a
836 record this change to 'subdir/a'?
836 record this change to 'subdir/a'?
837 (enter ? for help) [Ynesfdaq?] y
837 (enter ? for help) [Ynesfdaq?] y
838
838
839
839
840 $ hg tip -p
840 $ hg tip -p
841 changeset: 20:e0f6b99f6c49
841 changeset: 20:e0f6b99f6c49
842 tag: tip
842 tag: tip
843 user: test
843 user: test
844 date: Thu Jan 01 00:00:16 1970 +0000
844 date: Thu Jan 01 00:00:16 1970 +0000
845 summary: subdir-change
845 summary: subdir-change
846
846
847 diff -r abd26b51de37 -r e0f6b99f6c49 subdir/a
847 diff -r abd26b51de37 -r e0f6b99f6c49 subdir/a
848 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
848 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
849 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
849 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
850 @@ -1,1 +1,2 @@
850 @@ -1,1 +1,2 @@
851 a
851 a
852 +a
852 +a
853
853
854
854
855 $ echo a > f1
855 $ echo a > f1
856 $ echo b > f2
856 $ echo b > f2
857 $ hg add f1 f2
857 $ hg add f1 f2
858
858
859 $ hg ci -mz -d '17 0'
859 $ hg ci -mz -d '17 0'
860
860
861 $ echo a >> f1
861 $ echo a >> f1
862 $ echo b >> f2
862 $ echo b >> f2
863
863
864 Help, quit
864 Help, quit
865
865
866 $ hg commit -i <<EOF
866 $ hg commit -i <<EOF
867 > ?
867 > ?
868 > q
868 > q
869 > EOF
869 > EOF
870 diff --git a/subdir/f1 b/subdir/f1
870 diff --git a/subdir/f1 b/subdir/f1
871 1 hunks, 1 lines changed
871 1 hunks, 1 lines changed
872 examine changes to 'subdir/f1'?
872 examine changes to 'subdir/f1'?
873 (enter ? for help) [Ynesfdaq?] ?
873 (enter ? for help) [Ynesfdaq?] ?
874
874
875 y - yes, record this change
875 y - yes, record this change
876 n - no, skip this change
876 n - no, skip this change
877 e - edit this change manually
877 e - edit this change manually
878 s - skip remaining changes to this file
878 s - skip remaining changes to this file
879 f - record remaining changes to this file
879 f - record remaining changes to this file
880 d - done, skip remaining changes and files
880 d - done, skip remaining changes and files
881 a - record all changes to all remaining files
881 a - record all changes to all remaining files
882 q - quit, recording no changes
882 q - quit, recording no changes
883 ? - ? (display help)
883 ? - ? (display help)
884 examine changes to 'subdir/f1'?
884 examine changes to 'subdir/f1'?
885 (enter ? for help) [Ynesfdaq?] q
885 (enter ? for help) [Ynesfdaq?] q
886
886
887 abort: user quit
887 abort: user quit
888 [255]
888 [255]
889
889
890 Patterns
890 Patterns
891
891
892 $ hg commit -i 'glob:f*' << EOF
892 $ hg commit -i 'glob:f*' << EOF
893 > y
893 > y
894 > n
894 > n
895 > y
895 > y
896 > n
896 > n
897 > EOF
897 > EOF
898 diff --git a/subdir/f1 b/subdir/f1
898 diff --git a/subdir/f1 b/subdir/f1
899 1 hunks, 1 lines changed
899 1 hunks, 1 lines changed
900 examine changes to 'subdir/f1'?
900 examine changes to 'subdir/f1'?
901 (enter ? for help) [Ynesfdaq?] y
901 (enter ? for help) [Ynesfdaq?] y
902
902
903 @@ -1,1 +1,2 @@
903 @@ -1,1 +1,2 @@
904 a
904 a
905 +a
905 +a
906 record change 1/2 to 'subdir/f1'?
906 record change 1/2 to 'subdir/f1'?
907 (enter ? for help) [Ynesfdaq?] n
907 (enter ? for help) [Ynesfdaq?] n
908
908
909 diff --git a/subdir/f2 b/subdir/f2
909 diff --git a/subdir/f2 b/subdir/f2
910 1 hunks, 1 lines changed
910 1 hunks, 1 lines changed
911 examine changes to 'subdir/f2'?
911 examine changes to 'subdir/f2'?
912 (enter ? for help) [Ynesfdaq?] y
912 (enter ? for help) [Ynesfdaq?] y
913
913
914 @@ -1,1 +1,2 @@
914 @@ -1,1 +1,2 @@
915 b
915 b
916 +b
916 +b
917 record change 2/2 to 'subdir/f2'?
917 record change 2/2 to 'subdir/f2'?
918 (enter ? for help) [Ynesfdaq?] n
918 (enter ? for help) [Ynesfdaq?] n
919
919
920 no changes to record
920 no changes to record
921 [1]
921 [1]
922
922
923 #if gettext
923 #if gettext
924
924
925 Test translated help message
925 Test translated help message
926
926
927 str.lower() instead of encoding.lower(str) on translated message might
927 str.lower() instead of encoding.lower(str) on translated message might
928 make message meaningless, because some encoding uses 0x41(A) - 0x5a(Z)
928 make message meaningless, because some encoding uses 0x41(A) - 0x5a(Z)
929 as the second or later byte of multi-byte character.
929 as the second or later byte of multi-byte character.
930
930
931 For example, "\x8bL\x98^" (translation of "record" in ja_JP.cp932)
931 For example, "\x8bL\x98^" (translation of "record" in ja_JP.cp932)
932 contains 0x4c (L). str.lower() replaces 0x4c(L) by 0x6c(l) and this
932 contains 0x4c (L). str.lower() replaces 0x4c(L) by 0x6c(l) and this
933 replacement makes message meaningless.
933 replacement makes message meaningless.
934
934
935 This tests that translated help message is lower()-ed correctly.
935 This tests that translated help message is lower()-ed correctly.
936
936
937 $ LANGUAGE=ja
937 $ LANGUAGE=ja
938 $ export LANGUAGE
938 $ export LANGUAGE
939
939
940 $ cat > $TESTTMP/escape.py <<EOF
940 $ cat > $TESTTMP/escape.py <<EOF
941 > from __future__ import absolute_import
941 > from __future__ import absolute_import
942 > from mercurial import (
942 > from mercurial import (
943 > pycompat,
943 > pycompat,
944 > )
944 > )
945 > from mercurial.utils import (
945 > from mercurial.utils import (
946 > procutil,
946 > procutil,
947 > )
947 > )
948 > def escape(c):
948 > def escape(c):
949 > o = ord(c)
949 > o = ord(c)
950 > if o < 0x80:
950 > if o < 0x80:
951 > return c
951 > return c
952 > else:
952 > else:
953 > return br'\x%02x' % o # escape char setting MSB
953 > return br'\x%02x' % o # escape char setting MSB
954 > for l in procutil.stdin:
954 > for l in procutil.stdin:
955 > procutil.stdout.write(
955 > procutil.stdout.write(
956 > b''.join(escape(c) for c in pycompat.iterbytestr(l)))
956 > b''.join(escape(c) for c in pycompat.iterbytestr(l)))
957 > EOF
957 > EOF
958
958
959 $ hg commit -i --encoding cp932 2>&1 <<EOF | "$PYTHON" $TESTTMP/escape.py | grep '^y - '
959 $ hg commit -i --encoding cp932 2>&1 <<EOF | "$PYTHON" $TESTTMP/escape.py | grep '^y - '
960 > ?
960 > ?
961 > q
961 > q
962 > EOF
962 > EOF
963 y - \x82\xb1\x82\xcc\x95\xcf\x8dX\x82\xf0\x8bL\x98^(yes)
963 y - \x82\xb1\x82\xcc\x95\xcf\x8dX\x82\xf0\x8bL\x98^(yes)
964
964
965 $ LANGUAGE=
965 $ LANGUAGE=
966 #endif
966 #endif
967
967
968 Skip
968 Skip
969
969
970 $ hg commit -i <<EOF
970 $ hg commit -i <<EOF
971 > s
971 > s
972 > EOF
972 > EOF
973 diff --git a/subdir/f1 b/subdir/f1
973 diff --git a/subdir/f1 b/subdir/f1
974 1 hunks, 1 lines changed
974 1 hunks, 1 lines changed
975 examine changes to 'subdir/f1'?
975 examine changes to 'subdir/f1'?
976 (enter ? for help) [Ynesfdaq?] s
976 (enter ? for help) [Ynesfdaq?] s
977
977
978 diff --git a/subdir/f2 b/subdir/f2
978 diff --git a/subdir/f2 b/subdir/f2
979 1 hunks, 1 lines changed
979 1 hunks, 1 lines changed
980 examine changes to 'subdir/f2'?
980 examine changes to 'subdir/f2'?
981 (enter ? for help) [Ynesfdaq?] abort: response expected
981 (enter ? for help) [Ynesfdaq?] abort: response expected
982 [255]
982 [255]
983
983
984 No
984 No
985
985
986 $ hg commit -i <<EOF
986 $ hg commit -i <<EOF
987 > n
987 > n
988 > EOF
988 > EOF
989 diff --git a/subdir/f1 b/subdir/f1
989 diff --git a/subdir/f1 b/subdir/f1
990 1 hunks, 1 lines changed
990 1 hunks, 1 lines changed
991 examine changes to 'subdir/f1'?
991 examine changes to 'subdir/f1'?
992 (enter ? for help) [Ynesfdaq?] n
992 (enter ? for help) [Ynesfdaq?] n
993
993
994 diff --git a/subdir/f2 b/subdir/f2
994 diff --git a/subdir/f2 b/subdir/f2
995 1 hunks, 1 lines changed
995 1 hunks, 1 lines changed
996 examine changes to 'subdir/f2'?
996 examine changes to 'subdir/f2'?
997 (enter ? for help) [Ynesfdaq?] abort: response expected
997 (enter ? for help) [Ynesfdaq?] abort: response expected
998 [255]
998 [255]
999
999
1000 f, quit
1000 f, quit
1001
1001
1002 $ hg commit -i <<EOF
1002 $ hg commit -i <<EOF
1003 > f
1003 > f
1004 > q
1004 > q
1005 > EOF
1005 > EOF
1006 diff --git a/subdir/f1 b/subdir/f1
1006 diff --git a/subdir/f1 b/subdir/f1
1007 1 hunks, 1 lines changed
1007 1 hunks, 1 lines changed
1008 examine changes to 'subdir/f1'?
1008 examine changes to 'subdir/f1'?
1009 (enter ? for help) [Ynesfdaq?] f
1009 (enter ? for help) [Ynesfdaq?] f
1010
1010
1011 diff --git a/subdir/f2 b/subdir/f2
1011 diff --git a/subdir/f2 b/subdir/f2
1012 1 hunks, 1 lines changed
1012 1 hunks, 1 lines changed
1013 examine changes to 'subdir/f2'?
1013 examine changes to 'subdir/f2'?
1014 (enter ? for help) [Ynesfdaq?] q
1014 (enter ? for help) [Ynesfdaq?] q
1015
1015
1016 abort: user quit
1016 abort: user quit
1017 [255]
1017 [255]
1018
1018
1019 s, all
1019 s, all
1020
1020
1021 $ hg commit -i -d '18 0' -mx <<EOF
1021 $ hg commit -i -d '18 0' -mx <<EOF
1022 > s
1022 > s
1023 > a
1023 > a
1024 > EOF
1024 > EOF
1025 diff --git a/subdir/f1 b/subdir/f1
1025 diff --git a/subdir/f1 b/subdir/f1
1026 1 hunks, 1 lines changed
1026 1 hunks, 1 lines changed
1027 examine changes to 'subdir/f1'?
1027 examine changes to 'subdir/f1'?
1028 (enter ? for help) [Ynesfdaq?] s
1028 (enter ? for help) [Ynesfdaq?] s
1029
1029
1030 diff --git a/subdir/f2 b/subdir/f2
1030 diff --git a/subdir/f2 b/subdir/f2
1031 1 hunks, 1 lines changed
1031 1 hunks, 1 lines changed
1032 examine changes to 'subdir/f2'?
1032 examine changes to 'subdir/f2'?
1033 (enter ? for help) [Ynesfdaq?] a
1033 (enter ? for help) [Ynesfdaq?] a
1034
1034
1035
1035
1036 $ hg tip -p
1036 $ hg tip -p
1037 changeset: 22:6afbbefacf35
1037 changeset: 22:6afbbefacf35
1038 tag: tip
1038 tag: tip
1039 user: test
1039 user: test
1040 date: Thu Jan 01 00:00:18 1970 +0000
1040 date: Thu Jan 01 00:00:18 1970 +0000
1041 summary: x
1041 summary: x
1042
1042
1043 diff -r b73c401c693c -r 6afbbefacf35 subdir/f2
1043 diff -r b73c401c693c -r 6afbbefacf35 subdir/f2
1044 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
1044 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
1045 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
1045 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
1046 @@ -1,1 +1,2 @@
1046 @@ -1,1 +1,2 @@
1047 b
1047 b
1048 +b
1048 +b
1049
1049
1050
1050
1051 f
1051 f
1052
1052
1053 $ hg commit -i -d '19 0' -my <<EOF
1053 $ hg commit -i -d '19 0' -my <<EOF
1054 > f
1054 > f
1055 > EOF
1055 > EOF
1056 diff --git a/subdir/f1 b/subdir/f1
1056 diff --git a/subdir/f1 b/subdir/f1
1057 1 hunks, 1 lines changed
1057 1 hunks, 1 lines changed
1058 examine changes to 'subdir/f1'?
1058 examine changes to 'subdir/f1'?
1059 (enter ? for help) [Ynesfdaq?] f
1059 (enter ? for help) [Ynesfdaq?] f
1060
1060
1061
1061
1062 $ hg tip -p
1062 $ hg tip -p
1063 changeset: 23:715028a33949
1063 changeset: 23:715028a33949
1064 tag: tip
1064 tag: tip
1065 user: test
1065 user: test
1066 date: Thu Jan 01 00:00:19 1970 +0000
1066 date: Thu Jan 01 00:00:19 1970 +0000
1067 summary: y
1067 summary: y
1068
1068
1069 diff -r 6afbbefacf35 -r 715028a33949 subdir/f1
1069 diff -r 6afbbefacf35 -r 715028a33949 subdir/f1
1070 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
1070 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
1071 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
1071 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
1072 @@ -1,1 +1,2 @@
1072 @@ -1,1 +1,2 @@
1073 a
1073 a
1074 +a
1074 +a
1075
1075
1076
1076
1077 #if execbit
1077 #if execbit
1078
1078
1079 Preserve chmod +x
1079 Preserve chmod +x
1080
1080
1081 $ chmod +x f1
1081 $ chmod +x f1
1082 $ echo a >> f1
1082 $ echo a >> f1
1083 $ hg commit -i -d '20 0' -mz <<EOF
1083 $ hg commit -i -d '20 0' -mz <<EOF
1084 > y
1084 > y
1085 > y
1085 > y
1086 > y
1086 > y
1087 > EOF
1087 > EOF
1088 diff --git a/subdir/f1 b/subdir/f1
1088 diff --git a/subdir/f1 b/subdir/f1
1089 old mode 100644
1089 old mode 100644
1090 new mode 100755
1090 new mode 100755
1091 1 hunks, 1 lines changed
1091 1 hunks, 1 lines changed
1092 examine changes to 'subdir/f1'?
1092 examine changes to 'subdir/f1'?
1093 (enter ? for help) [Ynesfdaq?] y
1093 (enter ? for help) [Ynesfdaq?] y
1094
1094
1095 @@ -1,2 +1,3 @@
1095 @@ -1,2 +1,3 @@
1096 a
1096 a
1097 a
1097 a
1098 +a
1098 +a
1099 record this change to 'subdir/f1'?
1099 record this change to 'subdir/f1'?
1100 (enter ? for help) [Ynesfdaq?] y
1100 (enter ? for help) [Ynesfdaq?] y
1101
1101
1102
1102
1103 $ hg tip --config diff.git=True -p
1103 $ hg tip --config diff.git=True -p
1104 changeset: 24:db967c1e5884
1104 changeset: 24:db967c1e5884
1105 tag: tip
1105 tag: tip
1106 user: test
1106 user: test
1107 date: Thu Jan 01 00:00:20 1970 +0000
1107 date: Thu Jan 01 00:00:20 1970 +0000
1108 summary: z
1108 summary: z
1109
1109
1110 diff --git a/subdir/f1 b/subdir/f1
1110 diff --git a/subdir/f1 b/subdir/f1
1111 old mode 100644
1111 old mode 100644
1112 new mode 100755
1112 new mode 100755
1113 --- a/subdir/f1
1113 --- a/subdir/f1
1114 +++ b/subdir/f1
1114 +++ b/subdir/f1
1115 @@ -1,2 +1,3 @@
1115 @@ -1,2 +1,3 @@
1116 a
1116 a
1117 a
1117 a
1118 +a
1118 +a
1119
1119
1120
1120
1121 Preserve execute permission on original
1121 Preserve execute permission on original
1122
1122
1123 $ echo b >> f1
1123 $ echo b >> f1
1124 $ hg commit -i -d '21 0' -maa <<EOF
1124 $ hg commit -i -d '21 0' -maa <<EOF
1125 > y
1125 > y
1126 > y
1126 > y
1127 > y
1127 > y
1128 > EOF
1128 > EOF
1129 diff --git a/subdir/f1 b/subdir/f1
1129 diff --git a/subdir/f1 b/subdir/f1
1130 1 hunks, 1 lines changed
1130 1 hunks, 1 lines changed
1131 examine changes to 'subdir/f1'?
1131 examine changes to 'subdir/f1'?
1132 (enter ? for help) [Ynesfdaq?] y
1132 (enter ? for help) [Ynesfdaq?] y
1133
1133
1134 @@ -1,3 +1,4 @@
1134 @@ -1,3 +1,4 @@
1135 a
1135 a
1136 a
1136 a
1137 a
1137 a
1138 +b
1138 +b
1139 record this change to 'subdir/f1'?
1139 record this change to 'subdir/f1'?
1140 (enter ? for help) [Ynesfdaq?] y
1140 (enter ? for help) [Ynesfdaq?] y
1141
1141
1142
1142
1143 $ hg tip --config diff.git=True -p
1143 $ hg tip --config diff.git=True -p
1144 changeset: 25:88903aef81c3
1144 changeset: 25:88903aef81c3
1145 tag: tip
1145 tag: tip
1146 user: test
1146 user: test
1147 date: Thu Jan 01 00:00:21 1970 +0000
1147 date: Thu Jan 01 00:00:21 1970 +0000
1148 summary: aa
1148 summary: aa
1149
1149
1150 diff --git a/subdir/f1 b/subdir/f1
1150 diff --git a/subdir/f1 b/subdir/f1
1151 --- a/subdir/f1
1151 --- a/subdir/f1
1152 +++ b/subdir/f1
1152 +++ b/subdir/f1
1153 @@ -1,3 +1,4 @@
1153 @@ -1,3 +1,4 @@
1154 a
1154 a
1155 a
1155 a
1156 a
1156 a
1157 +b
1157 +b
1158
1158
1159
1159
1160 Preserve chmod -x
1160 Preserve chmod -x
1161
1161
1162 $ chmod -x f1
1162 $ chmod -x f1
1163 $ echo c >> f1
1163 $ echo c >> f1
1164 $ hg commit -i -d '22 0' -mab <<EOF
1164 $ hg commit -i -d '22 0' -mab <<EOF
1165 > y
1165 > y
1166 > y
1166 > y
1167 > y
1167 > y
1168 > EOF
1168 > EOF
1169 diff --git a/subdir/f1 b/subdir/f1
1169 diff --git a/subdir/f1 b/subdir/f1
1170 old mode 100755
1170 old mode 100755
1171 new mode 100644
1171 new mode 100644
1172 1 hunks, 1 lines changed
1172 1 hunks, 1 lines changed
1173 examine changes to 'subdir/f1'?
1173 examine changes to 'subdir/f1'?
1174 (enter ? for help) [Ynesfdaq?] y
1174 (enter ? for help) [Ynesfdaq?] y
1175
1175
1176 @@ -2,3 +2,4 @@ a
1176 @@ -2,3 +2,4 @@ a
1177 a
1177 a
1178 a
1178 a
1179 b
1179 b
1180 +c
1180 +c
1181 record this change to 'subdir/f1'?
1181 record this change to 'subdir/f1'?
1182 (enter ? for help) [Ynesfdaq?] y
1182 (enter ? for help) [Ynesfdaq?] y
1183
1183
1184
1184
1185 $ hg tip --config diff.git=True -p
1185 $ hg tip --config diff.git=True -p
1186 changeset: 26:7af84b6cf560
1186 changeset: 26:7af84b6cf560
1187 tag: tip
1187 tag: tip
1188 user: test
1188 user: test
1189 date: Thu Jan 01 00:00:22 1970 +0000
1189 date: Thu Jan 01 00:00:22 1970 +0000
1190 summary: ab
1190 summary: ab
1191
1191
1192 diff --git a/subdir/f1 b/subdir/f1
1192 diff --git a/subdir/f1 b/subdir/f1
1193 old mode 100755
1193 old mode 100755
1194 new mode 100644
1194 new mode 100644
1195 --- a/subdir/f1
1195 --- a/subdir/f1
1196 +++ b/subdir/f1
1196 +++ b/subdir/f1
1197 @@ -2,3 +2,4 @@
1197 @@ -2,3 +2,4 @@
1198 a
1198 a
1199 a
1199 a
1200 b
1200 b
1201 +c
1201 +c
1202
1202
1203
1203
1204 #else
1204 #else
1205
1205
1206 Slightly bogus tests to get almost same repo structure as when x bit is used
1206 Slightly bogus tests to get almost same repo structure as when x bit is used
1207 - but with different hashes.
1207 - but with different hashes.
1208
1208
1209 Mock "Preserve chmod +x"
1209 Mock "Preserve chmod +x"
1210
1210
1211 $ echo a >> f1
1211 $ echo a >> f1
1212 $ hg commit -i -d '20 0' -mz <<EOF
1212 $ hg commit -i -d '20 0' -mz <<EOF
1213 > y
1213 > y
1214 > y
1214 > y
1215 > y
1215 > y
1216 > EOF
1216 > EOF
1217 diff --git a/subdir/f1 b/subdir/f1
1217 diff --git a/subdir/f1 b/subdir/f1
1218 1 hunks, 1 lines changed
1218 1 hunks, 1 lines changed
1219 examine changes to 'subdir/f1'?
1219 examine changes to 'subdir/f1'?
1220 (enter ? for help) [Ynesfdaq?] y
1220 (enter ? for help) [Ynesfdaq?] y
1221
1221
1222 @@ -1,2 +1,3 @@
1222 @@ -1,2 +1,3 @@
1223 a
1223 a
1224 a
1224 a
1225 +a
1225 +a
1226 record this change to 'subdir/f1'?
1226 record this change to 'subdir/f1'?
1227 (enter ? for help) [Ynesfdaq?] y
1227 (enter ? for help) [Ynesfdaq?] y
1228
1228
1229
1229
1230 $ hg tip --config diff.git=True -p
1230 $ hg tip --config diff.git=True -p
1231 changeset: 24:c26cfe2c4eb0
1231 changeset: 24:c26cfe2c4eb0
1232 tag: tip
1232 tag: tip
1233 user: test
1233 user: test
1234 date: Thu Jan 01 00:00:20 1970 +0000
1234 date: Thu Jan 01 00:00:20 1970 +0000
1235 summary: z
1235 summary: z
1236
1236
1237 diff --git a/subdir/f1 b/subdir/f1
1237 diff --git a/subdir/f1 b/subdir/f1
1238 --- a/subdir/f1
1238 --- a/subdir/f1
1239 +++ b/subdir/f1
1239 +++ b/subdir/f1
1240 @@ -1,2 +1,3 @@
1240 @@ -1,2 +1,3 @@
1241 a
1241 a
1242 a
1242 a
1243 +a
1243 +a
1244
1244
1245
1245
1246 Mock "Preserve execute permission on original"
1246 Mock "Preserve execute permission on original"
1247
1247
1248 $ echo b >> f1
1248 $ echo b >> f1
1249 $ hg commit -i -d '21 0' -maa <<EOF
1249 $ hg commit -i -d '21 0' -maa <<EOF
1250 > y
1250 > y
1251 > y
1251 > y
1252 > y
1252 > y
1253 > EOF
1253 > EOF
1254 diff --git a/subdir/f1 b/subdir/f1
1254 diff --git a/subdir/f1 b/subdir/f1
1255 1 hunks, 1 lines changed
1255 1 hunks, 1 lines changed
1256 examine changes to 'subdir/f1'?
1256 examine changes to 'subdir/f1'?
1257 (enter ? for help) [Ynesfdaq?] y
1257 (enter ? for help) [Ynesfdaq?] y
1258
1258
1259 @@ -1,3 +1,4 @@
1259 @@ -1,3 +1,4 @@
1260 a
1260 a
1261 a
1261 a
1262 a
1262 a
1263 +b
1263 +b
1264 record this change to 'subdir/f1'?
1264 record this change to 'subdir/f1'?
1265 (enter ? for help) [Ynesfdaq?] y
1265 (enter ? for help) [Ynesfdaq?] y
1266
1266
1267
1267
1268 $ hg tip --config diff.git=True -p
1268 $ hg tip --config diff.git=True -p
1269 changeset: 25:a48d2d60adde
1269 changeset: 25:a48d2d60adde
1270 tag: tip
1270 tag: tip
1271 user: test
1271 user: test
1272 date: Thu Jan 01 00:00:21 1970 +0000
1272 date: Thu Jan 01 00:00:21 1970 +0000
1273 summary: aa
1273 summary: aa
1274
1274
1275 diff --git a/subdir/f1 b/subdir/f1
1275 diff --git a/subdir/f1 b/subdir/f1
1276 --- a/subdir/f1
1276 --- a/subdir/f1
1277 +++ b/subdir/f1
1277 +++ b/subdir/f1
1278 @@ -1,3 +1,4 @@
1278 @@ -1,3 +1,4 @@
1279 a
1279 a
1280 a
1280 a
1281 a
1281 a
1282 +b
1282 +b
1283
1283
1284
1284
1285 Mock "Preserve chmod -x"
1285 Mock "Preserve chmod -x"
1286
1286
1287 $ chmod -x f1
1287 $ chmod -x f1
1288 $ echo c >> f1
1288 $ echo c >> f1
1289 $ hg commit -i -d '22 0' -mab <<EOF
1289 $ hg commit -i -d '22 0' -mab <<EOF
1290 > y
1290 > y
1291 > y
1291 > y
1292 > y
1292 > y
1293 > EOF
1293 > EOF
1294 diff --git a/subdir/f1 b/subdir/f1
1294 diff --git a/subdir/f1 b/subdir/f1
1295 1 hunks, 1 lines changed
1295 1 hunks, 1 lines changed
1296 examine changes to 'subdir/f1'?
1296 examine changes to 'subdir/f1'?
1297 (enter ? for help) [Ynesfdaq?] y
1297 (enter ? for help) [Ynesfdaq?] y
1298
1298
1299 @@ -2,3 +2,4 @@ a
1299 @@ -2,3 +2,4 @@ a
1300 a
1300 a
1301 a
1301 a
1302 b
1302 b
1303 +c
1303 +c
1304 record this change to 'subdir/f1'?
1304 record this change to 'subdir/f1'?
1305 (enter ? for help) [Ynesfdaq?] y
1305 (enter ? for help) [Ynesfdaq?] y
1306
1306
1307
1307
1308 $ hg tip --config diff.git=True -p
1308 $ hg tip --config diff.git=True -p
1309 changeset: 26:5cc89ae210fa
1309 changeset: 26:5cc89ae210fa
1310 tag: tip
1310 tag: tip
1311 user: test
1311 user: test
1312 date: Thu Jan 01 00:00:22 1970 +0000
1312 date: Thu Jan 01 00:00:22 1970 +0000
1313 summary: ab
1313 summary: ab
1314
1314
1315 diff --git a/subdir/f1 b/subdir/f1
1315 diff --git a/subdir/f1 b/subdir/f1
1316 --- a/subdir/f1
1316 --- a/subdir/f1
1317 +++ b/subdir/f1
1317 +++ b/subdir/f1
1318 @@ -2,3 +2,4 @@
1318 @@ -2,3 +2,4 @@
1319 a
1319 a
1320 a
1320 a
1321 b
1321 b
1322 +c
1322 +c
1323
1323
1324
1324
1325 #endif
1325 #endif
1326
1326
1327 $ cd ..
1327 $ cd ..
1328
1328
1329
1329
1330 Abort early when a merge is in progress
1330 Abort early when a merge is in progress
1331
1331
1332 $ hg up 4
1332 $ hg up 4
1333 1 files updated, 0 files merged, 7 files removed, 0 files unresolved
1333 1 files updated, 0 files merged, 7 files removed, 0 files unresolved
1334
1334
1335 $ touch iwillmergethat
1335 $ touch iwillmergethat
1336 $ hg add iwillmergethat
1336 $ hg add iwillmergethat
1337
1337
1338 $ hg branch thatbranch
1338 $ hg branch thatbranch
1339 marked working directory as branch thatbranch
1339 marked working directory as branch thatbranch
1340 (branches are permanent and global, did you want a bookmark?)
1340 (branches are permanent and global, did you want a bookmark?)
1341
1341
1342 $ hg ci -m'new head'
1342 $ hg ci -m'new head'
1343
1343
1344 $ hg up default
1344 $ hg up default
1345 7 files updated, 0 files merged, 2 files removed, 0 files unresolved
1345 7 files updated, 0 files merged, 2 files removed, 0 files unresolved
1346
1346
1347 $ hg merge thatbranch
1347 $ hg merge thatbranch
1348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1349 (branch merge, don't forget to commit)
1349 (branch merge, don't forget to commit)
1350
1350
1351 $ hg commit -i -m'will abort'
1351 $ hg commit -i -m'will abort'
1352 abort: cannot partially commit a merge (use "hg commit" instead)
1352 abort: cannot partially commit a merge (use "hg commit" instead)
1353 [10]
1353 [10]
1354
1354
1355 $ hg up -C
1355 $ hg up -C
1356 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1356 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1357
1357
1358 Editing patch (and ignoring trailing text)
1358 Editing patch (and ignoring trailing text)
1359
1359
1360 $ cat > editor.sh << '__EOF__'
1360 $ cat > editor.sh << '__EOF__'
1361 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1361 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1362 > trailing\nditto' "$1" > tmp
1362 > trailing\nditto' "$1" > tmp
1363 > mv tmp "$1"
1363 > mv tmp "$1"
1364 > __EOF__
1364 > __EOF__
1365 $ cat > editedfile << '__EOF__'
1365 $ cat > editedfile << '__EOF__'
1366 > This is the first line
1366 > This is the first line
1367 > This is the second line
1367 > This is the second line
1368 > This is the third line
1368 > This is the third line
1369 > __EOF__
1369 > __EOF__
1370 $ hg add editedfile
1370 $ hg add editedfile
1371 $ hg commit -medit-patch-1
1371 $ hg commit -medit-patch-1
1372 $ cat > editedfile << '__EOF__'
1372 $ cat > editedfile << '__EOF__'
1373 > This line has changed
1373 > This line has changed
1374 > This change will be committed
1374 > This change will be committed
1375 > This is the third line
1375 > This is the third line
1376 > __EOF__
1376 > __EOF__
1377 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1377 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1378 > y
1378 > y
1379 > e
1379 > e
1380 > EOF
1380 > EOF
1381 diff --git a/editedfile b/editedfile
1381 diff --git a/editedfile b/editedfile
1382 1 hunks, 2 lines changed
1382 1 hunks, 2 lines changed
1383 examine changes to 'editedfile'?
1383 examine changes to 'editedfile'?
1384 (enter ? for help) [Ynesfdaq?] y
1384 (enter ? for help) [Ynesfdaq?] y
1385
1385
1386 @@ -1,3 +1,3 @@
1386 @@ -1,3 +1,3 @@
1387 -This is the first line
1387 -This is the first line
1388 -This is the second line
1388 -This is the second line
1389 +This line has changed
1389 +This line has changed
1390 +This change will be committed
1390 +This change will be committed
1391 This is the third line
1391 This is the third line
1392 record this change to 'editedfile'?
1392 record this change to 'editedfile'?
1393 (enter ? for help) [Ynesfdaq?] e
1393 (enter ? for help) [Ynesfdaq?] e
1394
1394
1395 $ cat editedfile
1395 $ cat editedfile
1396 This line has changed
1396 This line has changed
1397 This change will be committed
1397 This change will be committed
1398 This is the third line
1398 This is the third line
1399 $ hg cat -r tip editedfile
1399 $ hg cat -r tip editedfile
1400 This is the first line
1400 This is the first line
1401 This change will be committed
1401 This change will be committed
1402 This is the third line
1402 This is the third line
1403 $ hg revert editedfile
1403 $ hg revert editedfile
1404
1404
1405 Trying to edit patch for whole file
1405 Trying to edit patch for whole file
1406
1406
1407 $ echo "This is the fourth line" >> editedfile
1407 $ echo "This is the fourth line" >> editedfile
1408 $ hg commit -i <<EOF
1408 $ hg commit -i <<EOF
1409 > e
1409 > e
1410 > q
1410 > q
1411 > EOF
1411 > EOF
1412 diff --git a/editedfile b/editedfile
1412 diff --git a/editedfile b/editedfile
1413 1 hunks, 1 lines changed
1413 1 hunks, 1 lines changed
1414 examine changes to 'editedfile'?
1414 examine changes to 'editedfile'?
1415 (enter ? for help) [Ynesfdaq?] e
1415 (enter ? for help) [Ynesfdaq?] e
1416
1416
1417 cannot edit patch for whole file
1417 cannot edit patch for whole file
1418 examine changes to 'editedfile'?
1418 examine changes to 'editedfile'?
1419 (enter ? for help) [Ynesfdaq?] q
1419 (enter ? for help) [Ynesfdaq?] q
1420
1420
1421 abort: user quit
1421 abort: user quit
1422 [255]
1422 [255]
1423 $ hg revert editedfile
1423 $ hg revert editedfile
1424
1424
1425 Removing changes from patch
1425 Removing changes from patch
1426
1426
1427 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1427 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1428 $ mv tmp editedfile
1428 $ mv tmp editedfile
1429 $ echo "This line has been added" >> editedfile
1429 $ echo "This line has been added" >> editedfile
1430 $ cat > editor.sh << '__EOF__'
1430 $ cat > editor.sh << '__EOF__'
1431 > sed -e 's/^[-+]/ /' "$1" > tmp
1431 > sed -e 's/^[-+]/ /' "$1" > tmp
1432 > mv tmp "$1"
1432 > mv tmp "$1"
1433 > __EOF__
1433 > __EOF__
1434 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1434 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1435 > y
1435 > y
1436 > e
1436 > e
1437 > EOF
1437 > EOF
1438 diff --git a/editedfile b/editedfile
1438 diff --git a/editedfile b/editedfile
1439 1 hunks, 3 lines changed
1439 1 hunks, 3 lines changed
1440 examine changes to 'editedfile'?
1440 examine changes to 'editedfile'?
1441 (enter ? for help) [Ynesfdaq?] y
1441 (enter ? for help) [Ynesfdaq?] y
1442
1442
1443 @@ -1,3 +1,3 @@
1443 @@ -1,3 +1,3 @@
1444 -This is the first line
1444 -This is the first line
1445 -This change will be committed
1445 -This change will be committed
1446 -This is the third line
1446 -This is the third line
1447 +This change will not be committed
1447 +This change will not be committed
1448 +This is the second line
1448 +This is the second line
1449 +This line has been added
1449 +This line has been added
1450 record this change to 'editedfile'?
1450 record this change to 'editedfile'?
1451 (enter ? for help) [Ynesfdaq?] e
1451 (enter ? for help) [Ynesfdaq?] e
1452
1452
1453 no changes to record
1453 no changes to record
1454 [1]
1454 [1]
1455 $ cat editedfile
1455 $ cat editedfile
1456 This change will not be committed
1456 This change will not be committed
1457 This is the second line
1457 This is the second line
1458 This line has been added
1458 This line has been added
1459 $ hg cat -r tip editedfile
1459 $ hg cat -r tip editedfile
1460 This is the first line
1460 This is the first line
1461 This change will be committed
1461 This change will be committed
1462 This is the third line
1462 This is the third line
1463 $ hg revert editedfile
1463 $ hg revert editedfile
1464
1464
1465 Invalid patch
1465 Invalid patch
1466
1466
1467 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1467 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1468 $ mv tmp editedfile
1468 $ mv tmp editedfile
1469 $ echo "This line has been added" >> editedfile
1469 $ echo "This line has been added" >> editedfile
1470 $ cat > editor.sh << '__EOF__'
1470 $ cat > editor.sh << '__EOF__'
1471 > sed s/This/That/ "$1" > tmp
1471 > sed s/This/That/ "$1" > tmp
1472 > mv tmp "$1"
1472 > mv tmp "$1"
1473 > __EOF__
1473 > __EOF__
1474 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1474 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1475 > y
1475 > y
1476 > e
1476 > e
1477 > EOF
1477 > EOF
1478 diff --git a/editedfile b/editedfile
1478 diff --git a/editedfile b/editedfile
1479 1 hunks, 3 lines changed
1479 1 hunks, 3 lines changed
1480 examine changes to 'editedfile'?
1480 examine changes to 'editedfile'?
1481 (enter ? for help) [Ynesfdaq?] y
1481 (enter ? for help) [Ynesfdaq?] y
1482
1482
1483 @@ -1,3 +1,3 @@
1483 @@ -1,3 +1,3 @@
1484 -This is the first line
1484 -This is the first line
1485 -This change will be committed
1485 -This change will be committed
1486 -This is the third line
1486 -This is the third line
1487 +This change will not be committed
1487 +This change will not be committed
1488 +This is the second line
1488 +This is the second line
1489 +This line has been added
1489 +This line has been added
1490 record this change to 'editedfile'?
1490 record this change to 'editedfile'?
1491 (enter ? for help) [Ynesfdaq?] e
1491 (enter ? for help) [Ynesfdaq?] e
1492
1492
1493 patching file editedfile
1493 patching file editedfile
1494 Hunk #1 FAILED at 0
1494 Hunk #1 FAILED at 0
1495 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1495 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1496 abort: patch failed to apply
1496 abort: patch failed to apply
1497 [10]
1497 [10]
1498 $ cat editedfile
1498 $ cat editedfile
1499 This change will not be committed
1499 This change will not be committed
1500 This is the second line
1500 This is the second line
1501 This line has been added
1501 This line has been added
1502 $ hg cat -r tip editedfile
1502 $ hg cat -r tip editedfile
1503 This is the first line
1503 This is the first line
1504 This change will be committed
1504 This change will be committed
1505 This is the third line
1505 This is the third line
1506 $ cat editedfile.rej
1506 $ cat editedfile.rej
1507 --- editedfile
1507 --- editedfile
1508 +++ editedfile
1508 +++ editedfile
1509 @@ -1,3 +1,3 @@
1509 @@ -1,3 +1,3 @@
1510 -That is the first line
1510 -That is the first line
1511 -That change will be committed
1511 -That change will be committed
1512 -That is the third line
1512 -That is the third line
1513 +That change will not be committed
1513 +That change will not be committed
1514 +That is the second line
1514 +That is the second line
1515 +That line has been added
1515 +That line has been added
1516
1516
1517 Malformed patch - error handling
1517 Malformed patch - error handling
1518
1518
1519 $ cat > editor.sh << '__EOF__'
1519 $ cat > editor.sh << '__EOF__'
1520 > sed -e '/^@/p' "$1" > tmp
1520 > sed -e '/^@/p' "$1" > tmp
1521 > mv tmp "$1"
1521 > mv tmp "$1"
1522 > __EOF__
1522 > __EOF__
1523 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1523 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1524 > y
1524 > y
1525 > e
1525 > e
1526 > EOF
1526 > EOF
1527 diff --git a/editedfile b/editedfile
1527 diff --git a/editedfile b/editedfile
1528 1 hunks, 3 lines changed
1528 1 hunks, 3 lines changed
1529 examine changes to 'editedfile'?
1529 examine changes to 'editedfile'?
1530 (enter ? for help) [Ynesfdaq?] y
1530 (enter ? for help) [Ynesfdaq?] y
1531
1531
1532 @@ -1,3 +1,3 @@
1532 @@ -1,3 +1,3 @@
1533 -This is the first line
1533 -This is the first line
1534 -This change will be committed
1534 -This change will be committed
1535 -This is the third line
1535 -This is the third line
1536 +This change will not be committed
1536 +This change will not be committed
1537 +This is the second line
1537 +This is the second line
1538 +This line has been added
1538 +This line has been added
1539 record this change to 'editedfile'?
1539 record this change to 'editedfile'?
1540 (enter ? for help) [Ynesfdaq?] e
1540 (enter ? for help) [Ynesfdaq?] e
1541
1541
1542 abort: error parsing patch: unhandled transition: range -> range
1542 abort: error parsing patch: unhandled transition: range -> range
1543 [10]
1543 [10]
1544
1544
1545 Exiting editor with status 1, ignores the edit but does not stop the recording
1545 Exiting editor with status 1, ignores the edit but does not stop the recording
1546 session
1546 session
1547
1547
1548 $ HGEDITOR=false hg commit -i <<EOF
1548 $ HGEDITOR=false hg commit -i <<EOF
1549 > y
1549 > y
1550 > e
1550 > e
1551 > n
1551 > n
1552 > EOF
1552 > EOF
1553 diff --git a/editedfile b/editedfile
1553 diff --git a/editedfile b/editedfile
1554 1 hunks, 3 lines changed
1554 1 hunks, 3 lines changed
1555 examine changes to 'editedfile'?
1555 examine changes to 'editedfile'?
1556 (enter ? for help) [Ynesfdaq?] y
1556 (enter ? for help) [Ynesfdaq?] y
1557
1557
1558 @@ -1,3 +1,3 @@
1558 @@ -1,3 +1,3 @@
1559 -This is the first line
1559 -This is the first line
1560 -This change will be committed
1560 -This change will be committed
1561 -This is the third line
1561 -This is the third line
1562 +This change will not be committed
1562 +This change will not be committed
1563 +This is the second line
1563 +This is the second line
1564 +This line has been added
1564 +This line has been added
1565 record this change to 'editedfile'?
1565 record this change to 'editedfile'?
1566 (enter ? for help) [Ynesfdaq?] e
1566 (enter ? for help) [Ynesfdaq?] e
1567
1567
1568 editor exited with exit code 1
1568 editor exited with exit code 1
1569 record this change to 'editedfile'?
1569 record this change to 'editedfile'?
1570 (enter ? for help) [Ynesfdaq?] n
1570 (enter ? for help) [Ynesfdaq?] n
1571
1571
1572 no changes to record
1572 no changes to record
1573 [1]
1573 [1]
1574
1574
1575
1575
1576 random text in random positions is still an error
1576 random text in random positions is still an error
1577
1577
1578 $ cat > editor.sh << '__EOF__'
1578 $ cat > editor.sh << '__EOF__'
1579 > sed -e '/^@/i\
1579 > sed -e '/^@/i\
1580 > other' "$1" > tmp
1580 > other' "$1" > tmp
1581 > mv tmp "$1"
1581 > mv tmp "$1"
1582 > __EOF__
1582 > __EOF__
1583 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1583 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1584 > y
1584 > y
1585 > e
1585 > e
1586 > EOF
1586 > EOF
1587 diff --git a/editedfile b/editedfile
1587 diff --git a/editedfile b/editedfile
1588 1 hunks, 3 lines changed
1588 1 hunks, 3 lines changed
1589 examine changes to 'editedfile'?
1589 examine changes to 'editedfile'?
1590 (enter ? for help) [Ynesfdaq?] y
1590 (enter ? for help) [Ynesfdaq?] y
1591
1591
1592 @@ -1,3 +1,3 @@
1592 @@ -1,3 +1,3 @@
1593 -This is the first line
1593 -This is the first line
1594 -This change will be committed
1594 -This change will be committed
1595 -This is the third line
1595 -This is the third line
1596 +This change will not be committed
1596 +This change will not be committed
1597 +This is the second line
1597 +This is the second line
1598 +This line has been added
1598 +This line has been added
1599 record this change to 'editedfile'?
1599 record this change to 'editedfile'?
1600 (enter ? for help) [Ynesfdaq?] e
1600 (enter ? for help) [Ynesfdaq?] e
1601
1601
1602 abort: error parsing patch: unhandled transition: file -> other
1602 abort: error parsing patch: unhandled transition: file -> other
1603 [10]
1603 [10]
1604
1604
1605 $ hg up -C
1605 $ hg up -C
1606 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1606 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1607
1607
1608 With win32text
1608 With win32text
1609
1609
1610 $ echo '[extensions]' >> .hg/hgrc
1610 $ echo '[extensions]' >> .hg/hgrc
1611 $ echo 'win32text = ' >> .hg/hgrc
1611 $ echo 'win32text = ' >> .hg/hgrc
1612 $ echo '[decode]' >> .hg/hgrc
1612 $ echo '[decode]' >> .hg/hgrc
1613 $ echo '** = cleverdecode:' >> .hg/hgrc
1613 $ echo '** = cleverdecode:' >> .hg/hgrc
1614 $ echo '[encode]' >> .hg/hgrc
1614 $ echo '[encode]' >> .hg/hgrc
1615 $ echo '** = cleverencode:' >> .hg/hgrc
1615 $ echo '** = cleverencode:' >> .hg/hgrc
1616 $ echo '[patch]' >> .hg/hgrc
1616 $ echo '[patch]' >> .hg/hgrc
1617 $ echo 'eol = crlf' >> .hg/hgrc
1617 $ echo 'eol = crlf' >> .hg/hgrc
1618
1618
1619 Ignore win32text deprecation warning for now:
1619 Ignore win32text deprecation warning for now:
1620
1620
1621 $ echo '[win32text]' >> .hg/hgrc
1621 $ echo '[win32text]' >> .hg/hgrc
1622 $ echo 'warn = no' >> .hg/hgrc
1622 $ echo 'warn = no' >> .hg/hgrc
1623
1623
1624 $ echo d >> subdir/f1
1624 $ echo d >> subdir/f1
1625 $ hg commit -i -d '24 0' -mw1 <<EOF
1625 $ hg commit -i -d '24 0' -mw1 <<EOF
1626 > y
1626 > y
1627 > y
1627 > y
1628 > EOF
1628 > EOF
1629 diff --git a/subdir/f1 b/subdir/f1
1629 diff --git a/subdir/f1 b/subdir/f1
1630 1 hunks, 1 lines changed
1630 1 hunks, 1 lines changed
1631 examine changes to 'subdir/f1'?
1631 examine changes to 'subdir/f1'?
1632 (enter ? for help) [Ynesfdaq?] y
1632 (enter ? for help) [Ynesfdaq?] y
1633
1633
1634 @@ -3,3 +3,4 @@ a
1634 @@ -3,3 +3,4 @@ a
1635 a
1635 a
1636 b
1636 b
1637 c
1637 c
1638 +d
1638 +d
1639 record this change to 'subdir/f1'?
1639 record this change to 'subdir/f1'?
1640 (enter ? for help) [Ynesfdaq?] y
1640 (enter ? for help) [Ynesfdaq?] y
1641
1641
1642
1642
1643 $ hg status -A subdir/f1
1643 $ hg status -A subdir/f1
1644 C subdir/f1
1644 C subdir/f1
1645 $ hg tip -p
1645 $ hg tip -p
1646 changeset: 30:* (glob)
1646 changeset: 30:* (glob)
1647 tag: tip
1647 tag: tip
1648 user: test
1648 user: test
1649 date: Thu Jan 01 00:00:24 1970 +0000
1649 date: Thu Jan 01 00:00:24 1970 +0000
1650 summary: w1
1650 summary: w1
1651
1651
1652 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1652 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1653 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1653 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1654 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1654 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1655 @@ -3,3 +3,4 @@
1655 @@ -3,3 +3,4 @@
1656 a
1656 a
1657 b
1657 b
1658 c
1658 c
1659 +d
1659 +d
1660
1660
1661
1661
1662
1662
1663 Test --user when ui.username not set
1663 Test --user when ui.username not set
1664 $ unset HGUSER
1664 $ unset HGUSER
1665 $ echo e >> subdir/f1
1665 $ echo e >> subdir/f1
1666 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1666 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1667 > y
1667 > y
1668 > y
1668 > y
1669 > EOF
1669 > EOF
1670 diff --git a/subdir/f1 b/subdir/f1
1670 diff --git a/subdir/f1 b/subdir/f1
1671 1 hunks, 1 lines changed
1671 1 hunks, 1 lines changed
1672 examine changes to 'subdir/f1'?
1672 examine changes to 'subdir/f1'?
1673 (enter ? for help) [Ynesfdaq?] y
1673 (enter ? for help) [Ynesfdaq?] y
1674
1674
1675 @@ -4,3 +4,4 @@ a
1675 @@ -4,3 +4,4 @@ a
1676 b
1676 b
1677 c
1677 c
1678 d
1678 d
1679 +e
1679 +e
1680 record this change to 'subdir/f1'?
1680 record this change to 'subdir/f1'?
1681 (enter ? for help) [Ynesfdaq?] y
1681 (enter ? for help) [Ynesfdaq?] y
1682
1682
1683 $ hg status -A subdir/f1
1683 $ hg status -A subdir/f1
1684 C subdir/f1
1684 C subdir/f1
1685 $ hg log --template '{author}\n' -l 1
1685 $ hg log --template '{author}\n' -l 1
1686 xyz
1686 xyz
1687 $ HGUSER="test"
1687 $ HGUSER="test"
1688 $ export HGUSER
1688 $ export HGUSER
1689
1689
1690
1690
1691 Moving files
1691 Moving files
1692
1692
1693 $ hg update -C .
1693 $ hg update -C .
1694 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1694 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1695 $ hg mv plain plain3
1695 $ hg mv plain plain3
1696 $ echo somechange >> plain3
1696 $ echo somechange >> plain3
1697 $ hg commit -i -d '23 0' -mmoving_files << EOF
1697 $ hg commit -i -d '23 0' -mmoving_files << EOF
1698 > y
1698 > y
1699 > y
1699 > y
1700 > EOF
1700 > EOF
1701 diff --git a/plain b/plain3
1701 diff --git a/plain b/plain3
1702 rename from plain
1702 rename from plain
1703 rename to plain3
1703 rename to plain3
1704 1 hunks, 1 lines changed
1704 1 hunks, 1 lines changed
1705 examine changes to 'plain' and 'plain3'?
1705 examine changes to 'plain' and 'plain3'?
1706 (enter ? for help) [Ynesfdaq?] y
1706 (enter ? for help) [Ynesfdaq?] y
1707
1707
1708 @@ -11,3 +11,4 @@ 8
1708 @@ -11,3 +11,4 @@ 8
1709 9
1709 9
1710 10
1710 10
1711 11
1711 11
1712 +somechange
1712 +somechange
1713 record this change to 'plain3'?
1713 record this change to 'plain3'?
1714 (enter ? for help) [Ynesfdaq?] y
1714 (enter ? for help) [Ynesfdaq?] y
1715
1715
1716 The #if execbit block above changes the hash here on some systems
1716 The #if execbit block above changes the hash here on some systems
1717 $ hg status -A plain3
1717 $ hg status -A plain3
1718 C plain3
1718 C plain3
1719 $ hg tip
1719 $ hg tip
1720 changeset: 32:* (glob)
1720 changeset: 32:* (glob)
1721 tag: tip
1721 tag: tip
1722 user: test
1722 user: test
1723 date: Thu Jan 01 00:00:23 1970 +0000
1723 date: Thu Jan 01 00:00:23 1970 +0000
1724 summary: moving_files
1724 summary: moving_files
1725
1725
1726 Editing patch of newly added file
1726 Editing patch of newly added file
1727
1727
1728 $ hg update -C .
1728 $ hg update -C .
1729 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1729 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1730 $ cat > editor.sh << '__EOF__'
1730 $ cat > editor.sh << '__EOF__'
1731 > cat "$1" | sed "s/first/very/g" > tt
1731 > cat "$1" | sed "s/first/very/g" > tt
1732 > mv tt "$1"
1732 > mv tt "$1"
1733 > __EOF__
1733 > __EOF__
1734 $ cat > newfile << '__EOF__'
1734 $ cat > newfile << '__EOF__'
1735 > This is the first line
1735 > This is the first line
1736 > This is the second line
1736 > This is the second line
1737 > This is the third line
1737 > This is the third line
1738 > __EOF__
1738 > __EOF__
1739 $ hg add newfile
1739 $ hg add newfile
1740 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1740 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1741 > y
1741 > y
1742 > e
1742 > e
1743 > EOF
1743 > EOF
1744 diff --git a/newfile b/newfile
1744 diff --git a/newfile b/newfile
1745 new file mode 100644
1745 new file mode 100644
1746 examine changes to 'newfile'?
1746 examine changes to 'newfile'?
1747 (enter ? for help) [Ynesfdaq?] y
1747 (enter ? for help) [Ynesfdaq?] y
1748
1748
1749 @@ -0,0 +1,3 @@
1749 @@ -0,0 +1,3 @@
1750 +This is the first line
1750 +This is the first line
1751 +This is the second line
1751 +This is the second line
1752 +This is the third line
1752 +This is the third line
1753 record this change to 'newfile'?
1753 record this change to 'newfile'?
1754 (enter ? for help) [Ynesfdaq?] e
1754 (enter ? for help) [Ynesfdaq?] e
1755
1755
1756 $ hg cat -r tip newfile
1756 $ hg cat -r tip newfile
1757 This is the very line
1757 This is the very line
1758 This is the second line
1758 This is the second line
1759 This is the third line
1759 This is the third line
1760
1760
1761 $ cat newfile
1761 $ cat newfile
1762 This is the first line
1762 This is the first line
1763 This is the second line
1763 This is the second line
1764 This is the third line
1764 This is the third line
1765
1765
1766 Add new file from within a subdirectory
1766 Add new file from within a subdirectory
1767 $ hg update -C .
1767 $ hg update -C .
1768 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1768 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1769 $ mkdir folder
1769 $ mkdir folder
1770 $ cd folder
1770 $ cd folder
1771 $ echo "foo" > bar
1771 $ echo "foo" > bar
1772 $ hg add bar
1772 $ hg add bar
1773 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1773 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1774 > y
1774 > y
1775 > y
1775 > y
1776 > EOF
1776 > EOF
1777 diff --git a/folder/bar b/folder/bar
1777 diff --git a/folder/bar b/folder/bar
1778 new file mode 100644
1778 new file mode 100644
1779 examine changes to 'folder/bar'?
1779 examine changes to 'folder/bar'?
1780 (enter ? for help) [Ynesfdaq?] y
1780 (enter ? for help) [Ynesfdaq?] y
1781
1781
1782 @@ -0,0 +1,1 @@
1782 @@ -0,0 +1,1 @@
1783 +foo
1783 +foo
1784 record this change to 'folder/bar'?
1784 record this change to 'folder/bar'?
1785 (enter ? for help) [Ynesfdaq?] y
1785 (enter ? for help) [Ynesfdaq?] y
1786
1786
1787 The #if execbit block above changes the hashes here on some systems
1787 The #if execbit block above changes the hashes here on some systems
1788 $ hg tip -p
1788 $ hg tip -p
1789 changeset: 34:* (glob)
1789 changeset: 34:* (glob)
1790 tag: tip
1790 tag: tip
1791 user: test
1791 user: test
1792 date: Thu Jan 01 00:00:23 1970 +0000
1792 date: Thu Jan 01 00:00:23 1970 +0000
1793 summary: newfilesubdir
1793 summary: newfilesubdir
1794
1794
1795 diff -r * -r * folder/bar (glob)
1795 diff -r * -r * folder/bar (glob)
1796 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1796 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1797 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1797 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1798 @@ -0,0 +1,1 @@
1798 @@ -0,0 +1,1 @@
1799 +foo
1799 +foo
1800
1800
1801 $ cd ..
1801 $ cd ..
1802
1802
1803 $ hg status -A folder/bar
1803 $ hg status -A folder/bar
1804 C folder/bar
1804 C folder/bar
1805
1805
1806 Clear win32text configuration before size/timestamp sensitive test
1806 Clear win32text configuration before size/timestamp sensitive test
1807
1807
1808 $ cat >> .hg/hgrc <<EOF
1808 $ cat >> .hg/hgrc <<EOF
1809 > [extensions]
1809 > [extensions]
1810 > win32text = !
1810 > win32text = !
1811 > [decode]
1811 > [decode]
1812 > ** = !
1812 > ** = !
1813 > [encode]
1813 > [encode]
1814 > ** = !
1814 > ** = !
1815 > [patch]
1815 > [patch]
1816 > eol = strict
1816 > eol = strict
1817 > EOF
1817 > EOF
1818 $ hg update -q -C null
1818 $ hg update -q -C null
1819 $ hg update -q -C tip
1819 $ hg update -q -C tip
1820
1820
1821 Test that partially committed file is still treated as "modified",
1821 Test that partially committed file is still treated as "modified",
1822 even if none of mode, size and timestamp is changed on the filesystem
1822 even if none of mode, size and timestamp is changed on the filesystem
1823 (see also issue4583).
1823 (see also issue4583).
1824
1824
1825 $ cat > subdir/f1 <<EOF
1825 $ cat > subdir/f1 <<EOF
1826 > A
1826 > A
1827 > a
1827 > a
1828 > a
1828 > a
1829 > b
1829 > b
1830 > c
1830 > c
1831 > d
1831 > d
1832 > E
1832 > E
1833 > EOF
1833 > EOF
1834 $ hg diff --git subdir/f1
1834 $ hg diff --git subdir/f1
1835 diff --git a/subdir/f1 b/subdir/f1
1835 diff --git a/subdir/f1 b/subdir/f1
1836 --- a/subdir/f1
1836 --- a/subdir/f1
1837 +++ b/subdir/f1
1837 +++ b/subdir/f1
1838 @@ -1,7 +1,7 @@
1838 @@ -1,7 +1,7 @@
1839 -a
1839 -a
1840 +A
1840 +A
1841 a
1841 a
1842 a
1842 a
1843 b
1843 b
1844 c
1844 c
1845 d
1845 d
1846 -e
1846 -e
1847 +E
1847 +E
1848
1848
1849 $ touch -t 200001010000 subdir/f1
1849 $ touch -t 200001010000 subdir/f1
1850
1850
1851 $ cat >> .hg/hgrc <<EOF
1851 $ cat >> .hg/hgrc <<EOF
1852 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
1852 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
1853 > [fakepatchtime]
1853 > [fakepatchtime]
1854 > fakenow = 200001010000
1854 > fakenow = 200001010000
1855 >
1855 >
1856 > [extensions]
1856 > [extensions]
1857 > fakepatchtime = $TESTDIR/fakepatchtime.py
1857 > fakepatchtime = $TESTDIR/fakepatchtime.py
1858 > EOF
1858 > EOF
1859 $ hg commit -i -m 'commit subdir/f1 partially' <<EOF
1859 $ hg commit -i -m 'commit subdir/f1 partially' <<EOF
1860 > y
1860 > y
1861 > y
1861 > y
1862 > n
1862 > n
1863 > EOF
1863 > EOF
1864 diff --git a/subdir/f1 b/subdir/f1
1864 diff --git a/subdir/f1 b/subdir/f1
1865 2 hunks, 2 lines changed
1865 2 hunks, 2 lines changed
1866 examine changes to 'subdir/f1'?
1866 examine changes to 'subdir/f1'?
1867 (enter ? for help) [Ynesfdaq?] y
1867 (enter ? for help) [Ynesfdaq?] y
1868
1868
1869 @@ -1,6 +1,6 @@
1869 @@ -1,6 +1,6 @@
1870 -a
1870 -a
1871 +A
1871 +A
1872 a
1872 a
1873 a
1873 a
1874 b
1874 b
1875 c
1875 c
1876 d
1876 d
1877 record change 1/2 to 'subdir/f1'?
1877 record change 1/2 to 'subdir/f1'?
1878 (enter ? for help) [Ynesfdaq?] y
1878 (enter ? for help) [Ynesfdaq?] y
1879
1879
1880 @@ -2,6 +2,6 @@
1880 @@ -2,6 +2,6 @@
1881 a
1881 a
1882 a
1882 a
1883 b
1883 b
1884 c
1884 c
1885 d
1885 d
1886 -e
1886 -e
1887 +E
1887 +E
1888 record change 2/2 to 'subdir/f1'?
1888 record change 2/2 to 'subdir/f1'?
1889 (enter ? for help) [Ynesfdaq?] n
1889 (enter ? for help) [Ynesfdaq?] n
1890
1890
1891 $ cat >> .hg/hgrc <<EOF
1891 $ cat >> .hg/hgrc <<EOF
1892 > [extensions]
1892 > [extensions]
1893 > fakepatchtime = !
1893 > fakepatchtime = !
1894 > EOF
1894 > EOF
1895
1895
1896 $ hg debugstate | grep ' subdir/f1$'
1896 $ hg debugstate | grep ' subdir/f1$'
1897 n 0 -1 unset subdir/f1
1897 n 0 -1 unset subdir/f1
1898 $ hg status -A subdir/f1
1898 $ hg status -A subdir/f1
1899 M subdir/f1
1899 M subdir/f1
1900
1900
1901 Test commands.commit.interactive.unified=0
1901 Test commands.commit.interactive.unified=0
1902
1902
1903 $ hg init $TESTTMP/b
1903 $ hg init $TESTTMP/b
1904 $ cd $TESTTMP/b
1904 $ cd $TESTTMP/b
1905 $ cat > foo <<EOF
1905 $ cat > foo <<EOF
1906 > 1
1906 > 1
1907 > 2
1907 > 2
1908 > 3
1908 > 3
1909 > 4
1909 > 4
1910 > 5
1910 > 5
1911 > EOF
1911 > EOF
1912 $ hg ci -qAm initial
1912 $ hg ci -qAm initial
1913 $ cat > foo <<EOF
1913 $ cat > foo <<EOF
1914 > 1
1914 > 1
1915 > change1
1915 > change1
1916 > 2
1916 > 2
1917 > 3
1917 > 3
1918 > change2
1918 > change2
1919 > 4
1919 > 4
1920 > 5
1920 > 5
1921 > EOF
1921 > EOF
1922 $ printf 'y\ny\ny\n' | hg ci -im initial --config commands.commit.interactive.unified=0
1922 $ printf 'y\ny\ny\n' | hg ci -im initial --config commands.commit.interactive.unified=0
1923 diff --git a/foo b/foo
1923 diff --git a/foo b/foo
1924 2 hunks, 2 lines changed
1924 2 hunks, 2 lines changed
1925 examine changes to 'foo'?
1925 examine changes to 'foo'?
1926 (enter ? for help) [Ynesfdaq?] y
1926 (enter ? for help) [Ynesfdaq?] y
1927
1927
1928 @@ -1,0 +2,1 @@ 1
1928 @@ -1,0 +2,1 @@ 1
1929 +change1
1929 +change1
1930 record change 1/2 to 'foo'?
1930 record change 1/2 to 'foo'?
1931 (enter ? for help) [Ynesfdaq?] y
1931 (enter ? for help) [Ynesfdaq?] y
1932
1932
1933 @@ -3,0 +5,1 @@ 3
1933 @@ -3,0 +5,1 @@ 3
1934 +change2
1934 +change2
1935 record change 2/2 to 'foo'?
1935 record change 2/2 to 'foo'?
1936 (enter ? for help) [Ynesfdaq?] y
1936 (enter ? for help) [Ynesfdaq?] y
1937
1937
1938 $ cd $TESTTMP
1938 $ cd $TESTTMP
1939
1939
1940 Test diff.ignoreblanklines=1
1940 Test diff.ignoreblanklines=1
1941
1941
1942 $ hg init c
1942 $ hg init c
1943 $ cd c
1943 $ cd c
1944 $ cat > foo <<EOF
1944 $ cat > foo <<EOF
1945 > 1
1945 > 1
1946 > 2
1946 > 2
1947 > 3
1947 > 3
1948 > 4
1948 > 4
1949 > 5
1949 > 5
1950 > EOF
1950 > EOF
1951 $ hg ci -qAm initial
1951 $ hg ci -qAm initial
1952 $ cat > foo <<EOF
1952 $ cat > foo <<EOF
1953 > 1
1953 > 1
1954 >
1954 >
1955 > 2
1955 > 2
1956 > 3
1956 > 3
1957 > change2
1957 > change2
1958 > 4
1958 > 4
1959 > 5
1959 > 5
1960 > EOF
1960 > EOF
1961 $ printf 'y\ny\ny\n' | hg ci -im initial --config diff.ignoreblanklines=1
1961 $ printf 'y\ny\ny\n' | hg ci -im initial --config diff.ignoreblanklines=1
1962 diff --git a/foo b/foo
1962 diff --git a/foo b/foo
1963 2 hunks, 2 lines changed
1963 2 hunks, 2 lines changed
1964 examine changes to 'foo'?
1964 examine changes to 'foo'?
1965 (enter ? for help) [Ynesfdaq?] y
1965 (enter ? for help) [Ynesfdaq?] y
1966
1966
1967 @@ -1,3 +1,4 @@
1967 @@ -1,3 +1,4 @@
1968 1
1968 1
1969 +
1969 +
1970 2
1970 2
1971 3
1971 3
1972 record change 1/2 to 'foo'?
1972 record change 1/2 to 'foo'?
1973 (enter ? for help) [Ynesfdaq?] y
1973 (enter ? for help) [Ynesfdaq?] y
1974
1974
1975 @@ -2,4 +3,5 @@
1975 @@ -2,4 +3,5 @@
1976 2
1976 2
1977 3
1977 3
1978 +change2
1978 +change2
1979 4
1979 4
1980 5
1980 5
1981 record change 2/2 to 'foo'?
1981 record change 2/2 to 'foo'?
1982 (enter ? for help) [Ynesfdaq?] y
1982 (enter ? for help) [Ynesfdaq?] y
1983
1983
1984
1984
@@ -1,879 +1,879 b''
1 commit date test
1 commit date test
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo > foo
5 $ echo foo > foo
6 $ hg add foo
6 $ hg add foo
7 $ cat > $TESTTMP/checkeditform.sh <<EOF
7 $ cat > $TESTTMP/checkeditform.sh <<EOF
8 > env | grep HGEDITFORM
8 > env | grep HGEDITFORM
9 > true
9 > true
10 > EOF
10 > EOF
11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
12 HGEDITFORM=commit.normal.normal
12 HGEDITFORM=commit.normal.normal
13 abort: empty commit message
13 abort: empty commit message
14 [10]
14 [10]
15 $ hg commit -d '0 0' -m commit-1
15 $ hg commit -d '0 0' -m commit-1
16 $ echo foo >> foo
16 $ echo foo >> foo
17 $ hg commit -d '1 4444444' -m commit-3
17 $ hg commit -d '1 4444444' -m commit-3
18 hg: parse error: impossible time zone offset: 4444444
18 hg: parse error: impossible time zone offset: 4444444
19 [255]
19 [255]
20 $ hg commit -d '1 15.1' -m commit-4
20 $ hg commit -d '1 15.1' -m commit-4
21 hg: parse error: invalid date: '1\t15.1'
21 hg: parse error: invalid date: '1\t15.1'
22 [255]
22 [255]
23 $ hg commit -d 'foo bar' -m commit-5
23 $ hg commit -d 'foo bar' -m commit-5
24 hg: parse error: invalid date: 'foo bar'
24 hg: parse error: invalid date: 'foo bar'
25 [255]
25 [255]
26 $ hg commit -d ' 1 4444' -m commit-6
26 $ hg commit -d ' 1 4444' -m commit-6
27 $ hg commit -d '111111111111 0' -m commit-7
27 $ hg commit -d '111111111111 0' -m commit-7
28 hg: parse error: date exceeds 32 bits: 111111111111
28 hg: parse error: date exceeds 32 bits: 111111111111
29 [255]
29 [255]
30 $ hg commit -d '-111111111111 0' -m commit-7
30 $ hg commit -d '-111111111111 0' -m commit-7
31 hg: parse error: date exceeds 32 bits: -111111111111
31 hg: parse error: date exceeds 32 bits: -111111111111
32 [255]
32 [255]
33 $ echo foo >> foo
33 $ echo foo >> foo
34 $ hg commit -d '1901-12-13 20:45:52 +0000' -m commit-7-2
34 $ hg commit -d '1901-12-13 20:45:52 +0000' -m commit-7-2
35 $ echo foo >> foo
35 $ echo foo >> foo
36 $ hg commit -d '-2147483648 0' -m commit-7-3
36 $ hg commit -d '-2147483648 0' -m commit-7-3
37 $ hg log -T '{rev} {date|isodatesec}\n' -l2
37 $ hg log -T '{rev} {date|isodatesec}\n' -l2
38 3 1901-12-13 20:45:52 +0000
38 3 1901-12-13 20:45:52 +0000
39 2 1901-12-13 20:45:52 +0000
39 2 1901-12-13 20:45:52 +0000
40 $ hg commit -d '1901-12-13 20:45:51 +0000' -m commit-7
40 $ hg commit -d '1901-12-13 20:45:51 +0000' -m commit-7
41 hg: parse error: date exceeds 32 bits: -2147483649
41 hg: parse error: date exceeds 32 bits: -2147483649
42 [255]
42 [255]
43 $ hg commit -d '-2147483649 0' -m commit-7
43 $ hg commit -d '-2147483649 0' -m commit-7
44 hg: parse error: date exceeds 32 bits: -2147483649
44 hg: parse error: date exceeds 32 bits: -2147483649
45 [255]
45 [255]
46
46
47 commit added file that has been deleted
47 commit added file that has been deleted
48
48
49 $ echo bar > bar
49 $ echo bar > bar
50 $ hg add bar
50 $ hg add bar
51 $ rm bar
51 $ rm bar
52 $ hg commit -m commit-8
52 $ hg commit -m commit-8
53 nothing changed (1 missing files, see 'hg status')
53 nothing changed (1 missing files, see 'hg status')
54 [1]
54 [1]
55 $ hg commit -m commit-8-2 bar
55 $ hg commit -m commit-8-2 bar
56 abort: bar: file not found!
56 abort: bar: file not found!
57 [255]
57 [10]
58
58
59 $ hg -q revert -a --no-backup
59 $ hg -q revert -a --no-backup
60
60
61 $ mkdir dir
61 $ mkdir dir
62 $ echo boo > dir/file
62 $ echo boo > dir/file
63 $ hg add
63 $ hg add
64 adding dir/file
64 adding dir/file
65 $ hg -v commit -m commit-9 dir
65 $ hg -v commit -m commit-9 dir
66 committing files:
66 committing files:
67 dir/file
67 dir/file
68 committing manifest
68 committing manifest
69 committing changelog
69 committing changelog
70 committed changeset 4:1957363f1ced
70 committed changeset 4:1957363f1ced
71
71
72 $ echo > dir.file
72 $ echo > dir.file
73 $ hg add
73 $ hg add
74 adding dir.file
74 adding dir.file
75 $ hg commit -m commit-10 dir dir.file
75 $ hg commit -m commit-10 dir dir.file
76 abort: dir: no match under directory!
76 abort: dir: no match under directory!
77 [255]
77 [10]
78
78
79 $ echo >> dir/file
79 $ echo >> dir/file
80 $ mkdir bleh
80 $ mkdir bleh
81 $ mkdir dir2
81 $ mkdir dir2
82 $ cd bleh
82 $ cd bleh
83 $ hg commit -m commit-11 .
83 $ hg commit -m commit-11 .
84 abort: bleh: no match under directory!
84 abort: bleh: no match under directory!
85 [255]
85 [10]
86 $ hg commit -m commit-12 ../dir ../dir2
86 $ hg commit -m commit-12 ../dir ../dir2
87 abort: dir2: no match under directory!
87 abort: dir2: no match under directory!
88 [255]
88 [10]
89 $ hg -v commit -m commit-13 ../dir
89 $ hg -v commit -m commit-13 ../dir
90 committing files:
90 committing files:
91 dir/file
91 dir/file
92 committing manifest
92 committing manifest
93 committing changelog
93 committing changelog
94 committed changeset 5:a31d8f87544a
94 committed changeset 5:a31d8f87544a
95 $ cd ..
95 $ cd ..
96
96
97 $ hg commit -m commit-14 does-not-exist
97 $ hg commit -m commit-14 does-not-exist
98 abort: does-not-exist: * (glob)
98 abort: does-not-exist: * (glob)
99 [255]
99 [10]
100
100
101 #if symlink
101 #if symlink
102 $ ln -s foo baz
102 $ ln -s foo baz
103 $ hg commit -m commit-15 baz
103 $ hg commit -m commit-15 baz
104 abort: baz: file not tracked!
104 abort: baz: file not tracked!
105 [255]
105 [10]
106 $ rm baz
106 $ rm baz
107 #endif
107 #endif
108
108
109 $ touch quux
109 $ touch quux
110 $ hg commit -m commit-16 quux
110 $ hg commit -m commit-16 quux
111 abort: quux: file not tracked!
111 abort: quux: file not tracked!
112 [255]
112 [10]
113 $ echo >> dir/file
113 $ echo >> dir/file
114 $ hg -v commit -m commit-17 dir/file
114 $ hg -v commit -m commit-17 dir/file
115 committing files:
115 committing files:
116 dir/file
116 dir/file
117 committing manifest
117 committing manifest
118 committing changelog
118 committing changelog
119 committed changeset 6:32d054c9d085
119 committed changeset 6:32d054c9d085
120
120
121 An empty date was interpreted as epoch origin
121 An empty date was interpreted as epoch origin
122
122
123 $ echo foo >> foo
123 $ echo foo >> foo
124 $ hg commit -d '' -m commit-no-date --config devel.default-date=
124 $ hg commit -d '' -m commit-no-date --config devel.default-date=
125 $ hg tip --template '{date|isodate}\n' | grep '1970'
125 $ hg tip --template '{date|isodate}\n' | grep '1970'
126 [1]
126 [1]
127
127
128 Using the advanced --extra flag
128 Using the advanced --extra flag
129
129
130 $ echo "[extensions]" >> $HGRCPATH
130 $ echo "[extensions]" >> $HGRCPATH
131 $ echo "commitextras=" >> $HGRCPATH
131 $ echo "commitextras=" >> $HGRCPATH
132 $ hg status
132 $ hg status
133 ? quux
133 ? quux
134 $ hg add quux
134 $ hg add quux
135 $ hg commit -m "adding internal used extras" --extra amend_source=hash
135 $ hg commit -m "adding internal used extras" --extra amend_source=hash
136 abort: key 'amend_source' is used internally, can't be set manually
136 abort: key 'amend_source' is used internally, can't be set manually
137 [255]
137 [255]
138 $ hg commit -m "special chars in extra" --extra id@phab=214
138 $ hg commit -m "special chars in extra" --extra id@phab=214
139 abort: keys can only contain ascii letters, digits, '_' and '-'
139 abort: keys can only contain ascii letters, digits, '_' and '-'
140 [255]
140 [255]
141 $ hg commit -m "empty key" --extra =value
141 $ hg commit -m "empty key" --extra =value
142 abort: unable to parse '=value', keys can't be empty
142 abort: unable to parse '=value', keys can't be empty
143 [255]
143 [255]
144 $ hg commit -m "adding extras" --extra sourcehash=foo --extra oldhash=bar
144 $ hg commit -m "adding extras" --extra sourcehash=foo --extra oldhash=bar
145 $ hg log -r . -T '{extras % "{extra}\n"}'
145 $ hg log -r . -T '{extras % "{extra}\n"}'
146 branch=default
146 branch=default
147 oldhash=bar
147 oldhash=bar
148 sourcehash=foo
148 sourcehash=foo
149
149
150 Failed commit with --addremove should not update dirstate
150 Failed commit with --addremove should not update dirstate
151
151
152 $ echo foo > newfile
152 $ echo foo > newfile
153 $ hg status
153 $ hg status
154 ? newfile
154 ? newfile
155 $ HGEDITOR=false hg ci --addremove
155 $ HGEDITOR=false hg ci --addremove
156 adding newfile
156 adding newfile
157 abort: edit failed: false exited with status 1
157 abort: edit failed: false exited with status 1
158 [255]
158 [255]
159 $ hg status
159 $ hg status
160 ? newfile
160 ? newfile
161
161
162 Make sure we do not obscure unknown requires file entries (issue2649)
162 Make sure we do not obscure unknown requires file entries (issue2649)
163
163
164 $ echo foo >> foo
164 $ echo foo >> foo
165 $ echo fake >> .hg/requires
165 $ echo fake >> .hg/requires
166 $ hg commit -m bla
166 $ hg commit -m bla
167 abort: repository requires features unknown to this Mercurial: fake!
167 abort: repository requires features unknown to this Mercurial: fake!
168 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
168 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
169 [255]
169 [255]
170
170
171 $ cd ..
171 $ cd ..
172
172
173
173
174 partial subdir commit test
174 partial subdir commit test
175
175
176 $ hg init test2
176 $ hg init test2
177 $ cd test2
177 $ cd test2
178 $ mkdir foo
178 $ mkdir foo
179 $ echo foo > foo/foo
179 $ echo foo > foo/foo
180 $ mkdir bar
180 $ mkdir bar
181 $ echo bar > bar/bar
181 $ echo bar > bar/bar
182 $ hg add
182 $ hg add
183 adding bar/bar
183 adding bar/bar
184 adding foo/foo
184 adding foo/foo
185 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
185 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
186 commit-subdir-1
186 commit-subdir-1
187
187
188
188
189 HG: Enter commit message. Lines beginning with 'HG:' are removed.
189 HG: Enter commit message. Lines beginning with 'HG:' are removed.
190 HG: Leave message empty to abort commit.
190 HG: Leave message empty to abort commit.
191 HG: --
191 HG: --
192 HG: user: test
192 HG: user: test
193 HG: branch 'default'
193 HG: branch 'default'
194 HG: added foo/foo
194 HG: added foo/foo
195
195
196
196
197 $ hg ci -m commit-subdir-2 bar
197 $ hg ci -m commit-subdir-2 bar
198
198
199 subdir log 1
199 subdir log 1
200
200
201 $ hg log -v foo
201 $ hg log -v foo
202 changeset: 0:f97e73a25882
202 changeset: 0:f97e73a25882
203 user: test
203 user: test
204 date: Thu Jan 01 00:00:00 1970 +0000
204 date: Thu Jan 01 00:00:00 1970 +0000
205 files: foo/foo
205 files: foo/foo
206 description:
206 description:
207 commit-subdir-1
207 commit-subdir-1
208
208
209
209
210
210
211 subdir log 2
211 subdir log 2
212
212
213 $ hg log -v bar
213 $ hg log -v bar
214 changeset: 1:aa809156d50d
214 changeset: 1:aa809156d50d
215 tag: tip
215 tag: tip
216 user: test
216 user: test
217 date: Thu Jan 01 00:00:00 1970 +0000
217 date: Thu Jan 01 00:00:00 1970 +0000
218 files: bar/bar
218 files: bar/bar
219 description:
219 description:
220 commit-subdir-2
220 commit-subdir-2
221
221
222
222
223
223
224 full log
224 full log
225
225
226 $ hg log -v
226 $ hg log -v
227 changeset: 1:aa809156d50d
227 changeset: 1:aa809156d50d
228 tag: tip
228 tag: tip
229 user: test
229 user: test
230 date: Thu Jan 01 00:00:00 1970 +0000
230 date: Thu Jan 01 00:00:00 1970 +0000
231 files: bar/bar
231 files: bar/bar
232 description:
232 description:
233 commit-subdir-2
233 commit-subdir-2
234
234
235
235
236 changeset: 0:f97e73a25882
236 changeset: 0:f97e73a25882
237 user: test
237 user: test
238 date: Thu Jan 01 00:00:00 1970 +0000
238 date: Thu Jan 01 00:00:00 1970 +0000
239 files: foo/foo
239 files: foo/foo
240 description:
240 description:
241 commit-subdir-1
241 commit-subdir-1
242
242
243
243
244 $ cd ..
244 $ cd ..
245
245
246
246
247 dot and subdir commit test
247 dot and subdir commit test
248
248
249 $ hg init test3
249 $ hg init test3
250 $ echo commit-foo-subdir > commit-log-test
250 $ echo commit-foo-subdir > commit-log-test
251 $ cd test3
251 $ cd test3
252 $ mkdir foo
252 $ mkdir foo
253 $ echo foo content > foo/plain-file
253 $ echo foo content > foo/plain-file
254 $ hg add foo/plain-file
254 $ hg add foo/plain-file
255 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
255 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
256 commit-foo-subdir
256 commit-foo-subdir
257
257
258
258
259 HG: Enter commit message. Lines beginning with 'HG:' are removed.
259 HG: Enter commit message. Lines beginning with 'HG:' are removed.
260 HG: Leave message empty to abort commit.
260 HG: Leave message empty to abort commit.
261 HG: --
261 HG: --
262 HG: user: test
262 HG: user: test
263 HG: branch 'default'
263 HG: branch 'default'
264 HG: added foo/plain-file
264 HG: added foo/plain-file
265
265
266
266
267 $ echo modified foo content > foo/plain-file
267 $ echo modified foo content > foo/plain-file
268 $ hg ci -m commit-foo-dot .
268 $ hg ci -m commit-foo-dot .
269
269
270 full log
270 full log
271
271
272 $ hg log -v
272 $ hg log -v
273 changeset: 1:95b38e3a5b2e
273 changeset: 1:95b38e3a5b2e
274 tag: tip
274 tag: tip
275 user: test
275 user: test
276 date: Thu Jan 01 00:00:00 1970 +0000
276 date: Thu Jan 01 00:00:00 1970 +0000
277 files: foo/plain-file
277 files: foo/plain-file
278 description:
278 description:
279 commit-foo-dot
279 commit-foo-dot
280
280
281
281
282 changeset: 0:65d4e9386227
282 changeset: 0:65d4e9386227
283 user: test
283 user: test
284 date: Thu Jan 01 00:00:00 1970 +0000
284 date: Thu Jan 01 00:00:00 1970 +0000
285 files: foo/plain-file
285 files: foo/plain-file
286 description:
286 description:
287 commit-foo-subdir
287 commit-foo-subdir
288
288
289
289
290
290
291 subdir log
291 subdir log
292
292
293 $ cd foo
293 $ cd foo
294 $ hg log .
294 $ hg log .
295 changeset: 1:95b38e3a5b2e
295 changeset: 1:95b38e3a5b2e
296 tag: tip
296 tag: tip
297 user: test
297 user: test
298 date: Thu Jan 01 00:00:00 1970 +0000
298 date: Thu Jan 01 00:00:00 1970 +0000
299 summary: commit-foo-dot
299 summary: commit-foo-dot
300
300
301 changeset: 0:65d4e9386227
301 changeset: 0:65d4e9386227
302 user: test
302 user: test
303 date: Thu Jan 01 00:00:00 1970 +0000
303 date: Thu Jan 01 00:00:00 1970 +0000
304 summary: commit-foo-subdir
304 summary: commit-foo-subdir
305
305
306 $ cd ..
306 $ cd ..
307 $ cd ..
307 $ cd ..
308
308
309 Issue1049: Hg permits partial commit of merge without warning
309 Issue1049: Hg permits partial commit of merge without warning
310
310
311 $ hg init issue1049
311 $ hg init issue1049
312 $ cd issue1049
312 $ cd issue1049
313 $ echo a > a
313 $ echo a > a
314 $ hg ci -Ama
314 $ hg ci -Ama
315 adding a
315 adding a
316 $ echo a >> a
316 $ echo a >> a
317 $ hg ci -mb
317 $ hg ci -mb
318 $ hg up 0
318 $ hg up 0
319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
320 $ echo b >> a
320 $ echo b >> a
321 $ hg ci -mc
321 $ hg ci -mc
322 created new head
322 created new head
323 $ HGMERGE=true hg merge
323 $ HGMERGE=true hg merge
324 merging a
324 merging a
325 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
325 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
326 (branch merge, don't forget to commit)
326 (branch merge, don't forget to commit)
327
327
328 should fail because we are specifying a file name
328 should fail because we are specifying a file name
329
329
330 $ hg ci -mmerge a
330 $ hg ci -mmerge a
331 abort: cannot partially commit a merge (do not specify files or patterns)
331 abort: cannot partially commit a merge (do not specify files or patterns)
332 [255]
332 [255]
333
333
334 should fail because we are specifying a pattern
334 should fail because we are specifying a pattern
335
335
336 $ hg ci -mmerge -I a
336 $ hg ci -mmerge -I a
337 abort: cannot partially commit a merge (do not specify files or patterns)
337 abort: cannot partially commit a merge (do not specify files or patterns)
338 [255]
338 [255]
339
339
340 should succeed
340 should succeed
341
341
342 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
342 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
343 HGEDITFORM=commit.normal.merge
343 HGEDITFORM=commit.normal.merge
344 $ cd ..
344 $ cd ..
345
345
346
346
347 test commit message content
347 test commit message content
348
348
349 $ hg init commitmsg
349 $ hg init commitmsg
350 $ cd commitmsg
350 $ cd commitmsg
351 $ echo changed > changed
351 $ echo changed > changed
352 $ echo removed > removed
352 $ echo removed > removed
353 $ hg book activebookmark
353 $ hg book activebookmark
354 $ hg ci -qAm init
354 $ hg ci -qAm init
355
355
356 $ hg rm removed
356 $ hg rm removed
357 $ echo changed >> changed
357 $ echo changed >> changed
358 $ echo added > added
358 $ echo added > added
359 $ hg add added
359 $ hg add added
360 $ HGEDITOR=cat hg ci -A
360 $ HGEDITOR=cat hg ci -A
361
361
362
362
363 HG: Enter commit message. Lines beginning with 'HG:' are removed.
363 HG: Enter commit message. Lines beginning with 'HG:' are removed.
364 HG: Leave message empty to abort commit.
364 HG: Leave message empty to abort commit.
365 HG: --
365 HG: --
366 HG: user: test
366 HG: user: test
367 HG: branch 'default'
367 HG: branch 'default'
368 HG: bookmark 'activebookmark'
368 HG: bookmark 'activebookmark'
369 HG: added added
369 HG: added added
370 HG: changed changed
370 HG: changed changed
371 HG: removed removed
371 HG: removed removed
372 abort: empty commit message
372 abort: empty commit message
373 [10]
373 [10]
374
374
375 test saving last-message.txt
375 test saving last-message.txt
376
376
377 $ hg init sub
377 $ hg init sub
378 $ echo a > sub/a
378 $ echo a > sub/a
379 $ hg -R sub add sub/a
379 $ hg -R sub add sub/a
380 $ cat > sub/.hg/hgrc <<EOF
380 $ cat > sub/.hg/hgrc <<EOF
381 > [hooks]
381 > [hooks]
382 > precommit.test-saving-last-message = false
382 > precommit.test-saving-last-message = false
383 > EOF
383 > EOF
384
384
385 $ echo 'sub = sub' > .hgsub
385 $ echo 'sub = sub' > .hgsub
386 $ hg add .hgsub
386 $ hg add .hgsub
387
387
388 $ cat > $TESTTMP/editor.sh <<EOF
388 $ cat > $TESTTMP/editor.sh <<EOF
389 > echo "==== before editing:"
389 > echo "==== before editing:"
390 > cat \$1
390 > cat \$1
391 > echo "===="
391 > echo "===="
392 > echo "test saving last-message.txt" >> \$1
392 > echo "test saving last-message.txt" >> \$1
393 > EOF
393 > EOF
394
394
395 $ rm -f .hg/last-message.txt
395 $ rm -f .hg/last-message.txt
396 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
396 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
397 ==== before editing:
397 ==== before editing:
398
398
399
399
400 HG: Enter commit message. Lines beginning with 'HG:' are removed.
400 HG: Enter commit message. Lines beginning with 'HG:' are removed.
401 HG: Leave message empty to abort commit.
401 HG: Leave message empty to abort commit.
402 HG: --
402 HG: --
403 HG: user: test
403 HG: user: test
404 HG: branch 'default'
404 HG: branch 'default'
405 HG: bookmark 'activebookmark'
405 HG: bookmark 'activebookmark'
406 HG: subrepo sub
406 HG: subrepo sub
407 HG: added .hgsub
407 HG: added .hgsub
408 HG: added added
408 HG: added added
409 HG: changed .hgsubstate
409 HG: changed .hgsubstate
410 HG: changed changed
410 HG: changed changed
411 HG: removed removed
411 HG: removed removed
412 ====
412 ====
413 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepository "sub")
413 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepository "sub")
414 [255]
414 [255]
415 $ cat .hg/last-message.txt
415 $ cat .hg/last-message.txt
416
416
417
417
418 test saving last-message.txt
418 test saving last-message.txt
419
419
420 test that '[committemplate] changeset' definition and commit log
420 test that '[committemplate] changeset' definition and commit log
421 specific template keywords work well
421 specific template keywords work well
422
422
423 $ cat >> .hg/hgrc <<EOF
423 $ cat >> .hg/hgrc <<EOF
424 > [committemplate]
424 > [committemplate]
425 > changeset.commit.normal = 'HG: this is "commit.normal" template
425 > changeset.commit.normal = 'HG: this is "commit.normal" template
426 > HG: {extramsg}
426 > HG: {extramsg}
427 > {if(activebookmark,
427 > {if(activebookmark,
428 > "HG: bookmark '{activebookmark}' is activated\n",
428 > "HG: bookmark '{activebookmark}' is activated\n",
429 > "HG: no bookmark is activated\n")}{subrepos %
429 > "HG: no bookmark is activated\n")}{subrepos %
430 > "HG: subrepo '{subrepo}' is changed\n"}'
430 > "HG: subrepo '{subrepo}' is changed\n"}'
431 >
431 >
432 > changeset.commit = HG: this is "commit" template
432 > changeset.commit = HG: this is "commit" template
433 > HG: {extramsg}
433 > HG: {extramsg}
434 > {if(activebookmark,
434 > {if(activebookmark,
435 > "HG: bookmark '{activebookmark}' is activated\n",
435 > "HG: bookmark '{activebookmark}' is activated\n",
436 > "HG: no bookmark is activated\n")}{subrepos %
436 > "HG: no bookmark is activated\n")}{subrepos %
437 > "HG: subrepo '{subrepo}' is changed\n"}
437 > "HG: subrepo '{subrepo}' is changed\n"}
438 >
438 >
439 > changeset = HG: this is customized commit template
439 > changeset = HG: this is customized commit template
440 > HG: {extramsg}
440 > HG: {extramsg}
441 > {if(activebookmark,
441 > {if(activebookmark,
442 > "HG: bookmark '{activebookmark}' is activated\n",
442 > "HG: bookmark '{activebookmark}' is activated\n",
443 > "HG: no bookmark is activated\n")}{subrepos %
443 > "HG: no bookmark is activated\n")}{subrepos %
444 > "HG: subrepo '{subrepo}' is changed\n"}
444 > "HG: subrepo '{subrepo}' is changed\n"}
445 > EOF
445 > EOF
446
446
447 $ hg init sub2
447 $ hg init sub2
448 $ echo a > sub2/a
448 $ echo a > sub2/a
449 $ hg -R sub2 add sub2/a
449 $ hg -R sub2 add sub2/a
450 $ echo 'sub2 = sub2' >> .hgsub
450 $ echo 'sub2 = sub2' >> .hgsub
451
451
452 $ HGEDITOR=cat hg commit -S -q
452 $ HGEDITOR=cat hg commit -S -q
453 HG: this is "commit.normal" template
453 HG: this is "commit.normal" template
454 HG: Leave message empty to abort commit.
454 HG: Leave message empty to abort commit.
455 HG: bookmark 'activebookmark' is activated
455 HG: bookmark 'activebookmark' is activated
456 HG: subrepo 'sub' is changed
456 HG: subrepo 'sub' is changed
457 HG: subrepo 'sub2' is changed
457 HG: subrepo 'sub2' is changed
458 abort: empty commit message
458 abort: empty commit message
459 [10]
459 [10]
460
460
461 $ cat >> .hg/hgrc <<EOF
461 $ cat >> .hg/hgrc <<EOF
462 > [committemplate]
462 > [committemplate]
463 > changeset.commit.normal =
463 > changeset.commit.normal =
464 > # now, "changeset.commit" should be chosen for "hg commit"
464 > # now, "changeset.commit" should be chosen for "hg commit"
465 > EOF
465 > EOF
466
466
467 $ hg bookmark --inactive activebookmark
467 $ hg bookmark --inactive activebookmark
468 $ hg forget .hgsub
468 $ hg forget .hgsub
469 $ HGEDITOR=cat hg commit -q
469 $ HGEDITOR=cat hg commit -q
470 HG: this is "commit" template
470 HG: this is "commit" template
471 HG: Leave message empty to abort commit.
471 HG: Leave message empty to abort commit.
472 HG: no bookmark is activated
472 HG: no bookmark is activated
473 abort: empty commit message
473 abort: empty commit message
474 [10]
474 [10]
475
475
476 $ cat >> .hg/hgrc <<EOF
476 $ cat >> .hg/hgrc <<EOF
477 > [committemplate]
477 > [committemplate]
478 > changeset.commit =
478 > changeset.commit =
479 > # now, "changeset" should be chosen for "hg commit"
479 > # now, "changeset" should be chosen for "hg commit"
480 > EOF
480 > EOF
481
481
482 $ HGEDITOR=cat hg commit -q
482 $ HGEDITOR=cat hg commit -q
483 HG: this is customized commit template
483 HG: this is customized commit template
484 HG: Leave message empty to abort commit.
484 HG: Leave message empty to abort commit.
485 HG: no bookmark is activated
485 HG: no bookmark is activated
486 abort: empty commit message
486 abort: empty commit message
487 [10]
487 [10]
488
488
489 $ cat >> .hg/hgrc <<EOF
489 $ cat >> .hg/hgrc <<EOF
490 > [committemplate]
490 > [committemplate]
491 > changeset = {desc}
491 > changeset = {desc}
492 > HG: mods={file_mods}
492 > HG: mods={file_mods}
493 > HG: adds={file_adds}
493 > HG: adds={file_adds}
494 > HG: dels={file_dels}
494 > HG: dels={file_dels}
495 > HG: files={files}
495 > HG: files={files}
496 > HG:
496 > HG:
497 > {splitlines(diff()) % 'HG: {line}\n'
497 > {splitlines(diff()) % 'HG: {line}\n'
498 > }HG:
498 > }HG:
499 > HG: mods={file_mods}
499 > HG: mods={file_mods}
500 > HG: adds={file_adds}
500 > HG: adds={file_adds}
501 > HG: dels={file_dels}
501 > HG: dels={file_dels}
502 > HG: files={files}\n
502 > HG: files={files}\n
503 > EOF
503 > EOF
504 $ hg status -amr
504 $ hg status -amr
505 M changed
505 M changed
506 A added
506 A added
507 R removed
507 R removed
508 $ HGEDITOR=cat hg commit -q -e -m "foo bar" changed
508 $ HGEDITOR=cat hg commit -q -e -m "foo bar" changed
509 foo bar
509 foo bar
510 HG: mods=changed
510 HG: mods=changed
511 HG: adds=
511 HG: adds=
512 HG: dels=
512 HG: dels=
513 HG: files=changed
513 HG: files=changed
514 HG:
514 HG:
515 HG: diff -r d2313f97106f changed
515 HG: diff -r d2313f97106f changed
516 HG: --- a/changed Thu Jan 01 00:00:00 1970 +0000
516 HG: --- a/changed Thu Jan 01 00:00:00 1970 +0000
517 HG: +++ b/changed Thu Jan 01 00:00:00 1970 +0000
517 HG: +++ b/changed Thu Jan 01 00:00:00 1970 +0000
518 HG: @@ -1,1 +1,2 @@
518 HG: @@ -1,1 +1,2 @@
519 HG: changed
519 HG: changed
520 HG: +changed
520 HG: +changed
521 HG:
521 HG:
522 HG: mods=changed
522 HG: mods=changed
523 HG: adds=
523 HG: adds=
524 HG: dels=
524 HG: dels=
525 HG: files=changed
525 HG: files=changed
526 $ hg status -amr
526 $ hg status -amr
527 A added
527 A added
528 R removed
528 R removed
529 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
529 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
530 M changed
530 M changed
531 A
531 A
532 R
532 R
533 $ hg rollback -q
533 $ hg rollback -q
534
534
535 $ cat >> .hg/hgrc <<EOF
535 $ cat >> .hg/hgrc <<EOF
536 > [committemplate]
536 > [committemplate]
537 > changeset = {desc}
537 > changeset = {desc}
538 > HG: mods={file_mods}
538 > HG: mods={file_mods}
539 > HG: adds={file_adds}
539 > HG: adds={file_adds}
540 > HG: dels={file_dels}
540 > HG: dels={file_dels}
541 > HG: files={files}
541 > HG: files={files}
542 > HG:
542 > HG:
543 > {splitlines(diff("changed")) % 'HG: {line}\n'
543 > {splitlines(diff("changed")) % 'HG: {line}\n'
544 > }HG:
544 > }HG:
545 > HG: mods={file_mods}
545 > HG: mods={file_mods}
546 > HG: adds={file_adds}
546 > HG: adds={file_adds}
547 > HG: dels={file_dels}
547 > HG: dels={file_dels}
548 > HG: files={files}
548 > HG: files={files}
549 > HG:
549 > HG:
550 > {splitlines(diff("added")) % 'HG: {line}\n'
550 > {splitlines(diff("added")) % 'HG: {line}\n'
551 > }HG:
551 > }HG:
552 > HG: mods={file_mods}
552 > HG: mods={file_mods}
553 > HG: adds={file_adds}
553 > HG: adds={file_adds}
554 > HG: dels={file_dels}
554 > HG: dels={file_dels}
555 > HG: files={files}
555 > HG: files={files}
556 > HG:
556 > HG:
557 > {splitlines(diff("removed")) % 'HG: {line}\n'
557 > {splitlines(diff("removed")) % 'HG: {line}\n'
558 > }HG:
558 > }HG:
559 > HG: mods={file_mods}
559 > HG: mods={file_mods}
560 > HG: adds={file_adds}
560 > HG: adds={file_adds}
561 > HG: dels={file_dels}
561 > HG: dels={file_dels}
562 > HG: files={files}\n
562 > HG: files={files}\n
563 > EOF
563 > EOF
564 $ HGEDITOR=cat hg commit -q -e -m "foo bar" added removed
564 $ HGEDITOR=cat hg commit -q -e -m "foo bar" added removed
565 foo bar
565 foo bar
566 HG: mods=
566 HG: mods=
567 HG: adds=added
567 HG: adds=added
568 HG: dels=removed
568 HG: dels=removed
569 HG: files=added removed
569 HG: files=added removed
570 HG:
570 HG:
571 HG:
571 HG:
572 HG: mods=
572 HG: mods=
573 HG: adds=added
573 HG: adds=added
574 HG: dels=removed
574 HG: dels=removed
575 HG: files=added removed
575 HG: files=added removed
576 HG:
576 HG:
577 HG: diff -r d2313f97106f added
577 HG: diff -r d2313f97106f added
578 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
578 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
579 HG: +++ b/added Thu Jan 01 00:00:00 1970 +0000
579 HG: +++ b/added Thu Jan 01 00:00:00 1970 +0000
580 HG: @@ -0,0 +1,1 @@
580 HG: @@ -0,0 +1,1 @@
581 HG: +added
581 HG: +added
582 HG:
582 HG:
583 HG: mods=
583 HG: mods=
584 HG: adds=added
584 HG: adds=added
585 HG: dels=removed
585 HG: dels=removed
586 HG: files=added removed
586 HG: files=added removed
587 HG:
587 HG:
588 HG: diff -r d2313f97106f removed
588 HG: diff -r d2313f97106f removed
589 HG: --- a/removed Thu Jan 01 00:00:00 1970 +0000
589 HG: --- a/removed Thu Jan 01 00:00:00 1970 +0000
590 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
590 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
591 HG: @@ -1,1 +0,0 @@
591 HG: @@ -1,1 +0,0 @@
592 HG: -removed
592 HG: -removed
593 HG:
593 HG:
594 HG: mods=
594 HG: mods=
595 HG: adds=added
595 HG: adds=added
596 HG: dels=removed
596 HG: dels=removed
597 HG: files=added removed
597 HG: files=added removed
598 $ hg status -amr
598 $ hg status -amr
599 M changed
599 M changed
600 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
600 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
601 M
601 M
602 A added
602 A added
603 R removed
603 R removed
604 $ hg rollback -q
604 $ hg rollback -q
605
605
606 $ cat >> .hg/hgrc <<EOF
606 $ cat >> .hg/hgrc <<EOF
607 > # disable customizing for subsequent tests
607 > # disable customizing for subsequent tests
608 > [committemplate]
608 > [committemplate]
609 > changeset =
609 > changeset =
610 > EOF
610 > EOF
611
611
612 $ cd ..
612 $ cd ..
613
613
614
614
615 commit copy
615 commit copy
616
616
617 $ hg init dir2
617 $ hg init dir2
618 $ cd dir2
618 $ cd dir2
619 $ echo bleh > bar
619 $ echo bleh > bar
620 $ hg add bar
620 $ hg add bar
621 $ hg ci -m 'add bar'
621 $ hg ci -m 'add bar'
622
622
623 $ hg cp bar foo
623 $ hg cp bar foo
624 $ echo >> bar
624 $ echo >> bar
625 $ hg ci -m 'cp bar foo; change bar'
625 $ hg ci -m 'cp bar foo; change bar'
626
626
627 $ hg debugrename foo
627 $ hg debugrename foo
628 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
628 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
629 $ hg debugindex bar
629 $ hg debugindex bar
630 rev linkrev nodeid p1 p2
630 rev linkrev nodeid p1 p2
631 0 0 26d3ca0dfd18 000000000000 000000000000
631 0 0 26d3ca0dfd18 000000000000 000000000000
632 1 1 d267bddd54f7 26d3ca0dfd18 000000000000
632 1 1 d267bddd54f7 26d3ca0dfd18 000000000000
633
633
634 Test making empty commits
634 Test making empty commits
635 $ hg commit --config ui.allowemptycommit=True -m "empty commit"
635 $ hg commit --config ui.allowemptycommit=True -m "empty commit"
636 $ hg log -r . -v --stat
636 $ hg log -r . -v --stat
637 changeset: 2:d809f3644287
637 changeset: 2:d809f3644287
638 tag: tip
638 tag: tip
639 user: test
639 user: test
640 date: Thu Jan 01 00:00:00 1970 +0000
640 date: Thu Jan 01 00:00:00 1970 +0000
641 description:
641 description:
642 empty commit
642 empty commit
643
643
644
644
645
645
646 verify pathauditor blocks evil filepaths
646 verify pathauditor blocks evil filepaths
647 $ cat > evil-commit.py <<EOF
647 $ cat > evil-commit.py <<EOF
648 > from __future__ import absolute_import
648 > from __future__ import absolute_import
649 > from mercurial import context, hg, node, ui as uimod
649 > from mercurial import context, hg, node, ui as uimod
650 > notrc = u".h\u200cg".encode('utf-8') + b'/hgrc'
650 > notrc = u".h\u200cg".encode('utf-8') + b'/hgrc'
651 > u = uimod.ui.load()
651 > u = uimod.ui.load()
652 > r = hg.repository(u, b'.')
652 > r = hg.repository(u, b'.')
653 > def filectxfn(repo, memctx, path):
653 > def filectxfn(repo, memctx, path):
654 > return context.memfilectx(repo, memctx, path,
654 > return context.memfilectx(repo, memctx, path,
655 > b'[hooks]\nupdate = echo owned')
655 > b'[hooks]\nupdate = echo owned')
656 > c = context.memctx(r, [r.changelog.tip(), node.nullid],
656 > c = context.memctx(r, [r.changelog.tip(), node.nullid],
657 > b'evil', [notrc], filectxfn, 0)
657 > b'evil', [notrc], filectxfn, 0)
658 > r.commitctx(c)
658 > r.commitctx(c)
659 > EOF
659 > EOF
660 $ "$PYTHON" evil-commit.py
660 $ "$PYTHON" evil-commit.py
661 #if windows
661 #if windows
662 $ hg co --clean tip
662 $ hg co --clean tip
663 abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc)
663 abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc)
664 [255]
664 [255]
665 #else
665 #else
666 $ hg co --clean tip
666 $ hg co --clean tip
667 abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc)
667 abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc)
668 [255]
668 [255]
669 #endif
669 #endif
670
670
671 $ hg rollback -f
671 $ hg rollback -f
672 repository tip rolled back to revision 2 (undo commit)
672 repository tip rolled back to revision 2 (undo commit)
673 $ cat > evil-commit.py <<EOF
673 $ cat > evil-commit.py <<EOF
674 > from __future__ import absolute_import
674 > from __future__ import absolute_import
675 > from mercurial import context, hg, node, ui as uimod
675 > from mercurial import context, hg, node, ui as uimod
676 > notrc = b"HG~1/hgrc"
676 > notrc = b"HG~1/hgrc"
677 > u = uimod.ui.load()
677 > u = uimod.ui.load()
678 > r = hg.repository(u, b'.')
678 > r = hg.repository(u, b'.')
679 > def filectxfn(repo, memctx, path):
679 > def filectxfn(repo, memctx, path):
680 > return context.memfilectx(repo, memctx, path,
680 > return context.memfilectx(repo, memctx, path,
681 > b'[hooks]\nupdate = echo owned')
681 > b'[hooks]\nupdate = echo owned')
682 > c = context.memctx(r, [r[b'tip'].node(), node.nullid],
682 > c = context.memctx(r, [r[b'tip'].node(), node.nullid],
683 > b'evil', [notrc], filectxfn, 0)
683 > b'evil', [notrc], filectxfn, 0)
684 > r.commitctx(c)
684 > r.commitctx(c)
685 > EOF
685 > EOF
686 $ "$PYTHON" evil-commit.py
686 $ "$PYTHON" evil-commit.py
687 $ hg co --clean tip
687 $ hg co --clean tip
688 abort: path contains illegal component: HG~1/hgrc
688 abort: path contains illegal component: HG~1/hgrc
689 [255]
689 [255]
690
690
691 $ hg rollback -f
691 $ hg rollback -f
692 repository tip rolled back to revision 2 (undo commit)
692 repository tip rolled back to revision 2 (undo commit)
693 $ cat > evil-commit.py <<EOF
693 $ cat > evil-commit.py <<EOF
694 > from __future__ import absolute_import
694 > from __future__ import absolute_import
695 > from mercurial import context, hg, node, ui as uimod
695 > from mercurial import context, hg, node, ui as uimod
696 > notrc = b"HG8B6C~2/hgrc"
696 > notrc = b"HG8B6C~2/hgrc"
697 > u = uimod.ui.load()
697 > u = uimod.ui.load()
698 > r = hg.repository(u, b'.')
698 > r = hg.repository(u, b'.')
699 > def filectxfn(repo, memctx, path):
699 > def filectxfn(repo, memctx, path):
700 > return context.memfilectx(repo, memctx, path,
700 > return context.memfilectx(repo, memctx, path,
701 > b'[hooks]\nupdate = echo owned')
701 > b'[hooks]\nupdate = echo owned')
702 > c = context.memctx(r, [r[b'tip'].node(), node.nullid],
702 > c = context.memctx(r, [r[b'tip'].node(), node.nullid],
703 > b'evil', [notrc], filectxfn, 0)
703 > b'evil', [notrc], filectxfn, 0)
704 > r.commitctx(c)
704 > r.commitctx(c)
705 > EOF
705 > EOF
706 $ "$PYTHON" evil-commit.py
706 $ "$PYTHON" evil-commit.py
707 $ hg co --clean tip
707 $ hg co --clean tip
708 abort: path contains illegal component: HG8B6C~2/hgrc
708 abort: path contains illegal component: HG8B6C~2/hgrc
709 [255]
709 [255]
710
710
711 $ cd ..
711 $ cd ..
712
712
713 # test that an unmodified commit template message aborts
713 # test that an unmodified commit template message aborts
714
714
715 $ hg init unmodified_commit_template
715 $ hg init unmodified_commit_template
716 $ cd unmodified_commit_template
716 $ cd unmodified_commit_template
717 $ echo foo > foo
717 $ echo foo > foo
718 $ hg add foo
718 $ hg add foo
719 $ hg commit -m "foo"
719 $ hg commit -m "foo"
720 $ cat >> .hg/hgrc <<EOF
720 $ cat >> .hg/hgrc <<EOF
721 > [committemplate]
721 > [committemplate]
722 > changeset.commit = HI THIS IS NOT STRIPPED
722 > changeset.commit = HI THIS IS NOT STRIPPED
723 > HG: this is customized commit template
723 > HG: this is customized commit template
724 > HG: {extramsg}
724 > HG: {extramsg}
725 > {if(activebookmark,
725 > {if(activebookmark,
726 > "HG: bookmark '{activebookmark}' is activated\n",
726 > "HG: bookmark '{activebookmark}' is activated\n",
727 > "HG: no bookmark is activated\n")}{subrepos %
727 > "HG: no bookmark is activated\n")}{subrepos %
728 > "HG: subrepo '{subrepo}' is changed\n"}
728 > "HG: subrepo '{subrepo}' is changed\n"}
729 > EOF
729 > EOF
730 $ cat > $TESTTMP/notouching.sh <<EOF
730 $ cat > $TESTTMP/notouching.sh <<EOF
731 > true
731 > true
732 > EOF
732 > EOF
733 $ echo foo2 > foo2
733 $ echo foo2 > foo2
734 $ hg add foo2
734 $ hg add foo2
735 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg commit
735 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg commit
736 abort: commit message unchanged
736 abort: commit message unchanged
737 [10]
737 [10]
738
738
739 $ cd ..
739 $ cd ..
740
740
741 test that text below the --- >8 --- special string is ignored
741 test that text below the --- >8 --- special string is ignored
742
742
743 $ cat <<'EOF' > $TESTTMP/lowercaseline.sh
743 $ cat <<'EOF' > $TESTTMP/lowercaseline.sh
744 > cat $1 | sed s/LINE/line/ | tee $1.new
744 > cat $1 | sed s/LINE/line/ | tee $1.new
745 > mv $1.new $1
745 > mv $1.new $1
746 > EOF
746 > EOF
747
747
748 $ hg init ignore_below_special_string
748 $ hg init ignore_below_special_string
749 $ cd ignore_below_special_string
749 $ cd ignore_below_special_string
750 $ echo foo > foo
750 $ echo foo > foo
751 $ hg add foo
751 $ hg add foo
752 $ hg commit -m "foo"
752 $ hg commit -m "foo"
753 $ cat >> .hg/hgrc <<EOF
753 $ cat >> .hg/hgrc <<EOF
754 > [committemplate]
754 > [committemplate]
755 > changeset.commit = first LINE
755 > changeset.commit = first LINE
756 > HG: this is customized commit template
756 > HG: this is customized commit template
757 > HG: {extramsg}
757 > HG: {extramsg}
758 > HG: ------------------------ >8 ------------------------
758 > HG: ------------------------ >8 ------------------------
759 > {diff()}
759 > {diff()}
760 > EOF
760 > EOF
761 $ echo foo2 > foo2
761 $ echo foo2 > foo2
762 $ hg add foo2
762 $ hg add foo2
763 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg ci
763 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg ci
764 abort: commit message unchanged
764 abort: commit message unchanged
765 [10]
765 [10]
766 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
766 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
767 first line
767 first line
768 HG: this is customized commit template
768 HG: this is customized commit template
769 HG: Leave message empty to abort commit.
769 HG: Leave message empty to abort commit.
770 HG: ------------------------ >8 ------------------------
770 HG: ------------------------ >8 ------------------------
771 diff -r e63c23eaa88a foo2
771 diff -r e63c23eaa88a foo2
772 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
772 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
773 +++ b/foo2 Thu Jan 01 00:00:00 1970 +0000
773 +++ b/foo2 Thu Jan 01 00:00:00 1970 +0000
774 @@ -0,0 +1,1 @@
774 @@ -0,0 +1,1 @@
775 +foo2
775 +foo2
776 $ hg log -T '{desc}\n' -r .
776 $ hg log -T '{desc}\n' -r .
777 first line
777 first line
778
778
779 test that the special string --- >8 --- isn't used when not at the beginning of
779 test that the special string --- >8 --- isn't used when not at the beginning of
780 a line
780 a line
781
781
782 $ cat >> .hg/hgrc <<EOF
782 $ cat >> .hg/hgrc <<EOF
783 > [committemplate]
783 > [committemplate]
784 > changeset.commit = first LINE2
784 > changeset.commit = first LINE2
785 > another line HG: ------------------------ >8 ------------------------
785 > another line HG: ------------------------ >8 ------------------------
786 > HG: this is customized commit template
786 > HG: this is customized commit template
787 > HG: {extramsg}
787 > HG: {extramsg}
788 > HG: ------------------------ >8 ------------------------
788 > HG: ------------------------ >8 ------------------------
789 > {diff()}
789 > {diff()}
790 > EOF
790 > EOF
791 $ echo foo >> foo
791 $ echo foo >> foo
792 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
792 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
793 first line2
793 first line2
794 another line HG: ------------------------ >8 ------------------------
794 another line HG: ------------------------ >8 ------------------------
795 HG: this is customized commit template
795 HG: this is customized commit template
796 HG: Leave message empty to abort commit.
796 HG: Leave message empty to abort commit.
797 HG: ------------------------ >8 ------------------------
797 HG: ------------------------ >8 ------------------------
798 diff -r 3661b22b0702 foo
798 diff -r 3661b22b0702 foo
799 --- a/foo Thu Jan 01 00:00:00 1970 +0000
799 --- a/foo Thu Jan 01 00:00:00 1970 +0000
800 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
800 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
801 @@ -1,1 +1,2 @@
801 @@ -1,1 +1,2 @@
802 foo
802 foo
803 +foo
803 +foo
804 $ hg log -T '{desc}\n' -r .
804 $ hg log -T '{desc}\n' -r .
805 first line2
805 first line2
806 another line HG: ------------------------ >8 ------------------------
806 another line HG: ------------------------ >8 ------------------------
807
807
808 also test that this special string isn't accepted when there is some extra text
808 also test that this special string isn't accepted when there is some extra text
809 at the end
809 at the end
810
810
811 $ cat >> .hg/hgrc <<EOF
811 $ cat >> .hg/hgrc <<EOF
812 > [committemplate]
812 > [committemplate]
813 > changeset.commit = first LINE3
813 > changeset.commit = first LINE3
814 > HG: ------------------------ >8 ------------------------foobar
814 > HG: ------------------------ >8 ------------------------foobar
815 > second line
815 > second line
816 > HG: this is customized commit template
816 > HG: this is customized commit template
817 > HG: {extramsg}
817 > HG: {extramsg}
818 > HG: ------------------------ >8 ------------------------
818 > HG: ------------------------ >8 ------------------------
819 > {diff()}
819 > {diff()}
820 > EOF
820 > EOF
821 $ echo foo >> foo
821 $ echo foo >> foo
822 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
822 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
823 first line3
823 first line3
824 HG: ------------------------ >8 ------------------------foobar
824 HG: ------------------------ >8 ------------------------foobar
825 second line
825 second line
826 HG: this is customized commit template
826 HG: this is customized commit template
827 HG: Leave message empty to abort commit.
827 HG: Leave message empty to abort commit.
828 HG: ------------------------ >8 ------------------------
828 HG: ------------------------ >8 ------------------------
829 diff -r ce648f5f066f foo
829 diff -r ce648f5f066f foo
830 --- a/foo Thu Jan 01 00:00:00 1970 +0000
830 --- a/foo Thu Jan 01 00:00:00 1970 +0000
831 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
831 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
832 @@ -1,2 +1,3 @@
832 @@ -1,2 +1,3 @@
833 foo
833 foo
834 foo
834 foo
835 +foo
835 +foo
836 $ hg log -T '{desc}\n' -r .
836 $ hg log -T '{desc}\n' -r .
837 first line3
837 first line3
838 second line
838 second line
839
839
840 $ cd ..
840 $ cd ..
841
841
842 testing commands.commit.post-status config option
842 testing commands.commit.post-status config option
843
843
844 $ hg init ci-post-st
844 $ hg init ci-post-st
845 $ cd ci-post-st
845 $ cd ci-post-st
846 $ echo '[commands]' > .hg/hgrc
846 $ echo '[commands]' > .hg/hgrc
847 $ echo 'commit.post-status = 1' >> .hg/hgrc
847 $ echo 'commit.post-status = 1' >> .hg/hgrc
848
848
849 $ echo 'ignored-file' > .hgignore
849 $ echo 'ignored-file' > .hgignore
850 $ hg ci -qAm 0
850 $ hg ci -qAm 0
851
851
852 $ echo 'c' > clean-file
852 $ echo 'c' > clean-file
853 $ echo 'a' > added-file
853 $ echo 'a' > added-file
854 $ echo '?' > unknown-file
854 $ echo '?' > unknown-file
855 $ echo 'i' > ignored-file
855 $ echo 'i' > ignored-file
856 $ hg add clean-file added-file
856 $ hg add clean-file added-file
857 $ hg ci -m 1 clean-file
857 $ hg ci -m 1 clean-file
858 A added-file
858 A added-file
859 ? unknown-file
859 ? unknown-file
860 $ hg st -mardu
860 $ hg st -mardu
861 A added-file
861 A added-file
862 ? unknown-file
862 ? unknown-file
863
863
864 $ touch modified-file
864 $ touch modified-file
865 $ hg add modified-file
865 $ hg add modified-file
866 $ hg ci -m 2 modified-file -q
866 $ hg ci -m 2 modified-file -q
867
867
868 $ echo 'm' > modified-file
868 $ echo 'm' > modified-file
869 $ hg ci --amend -m 'reworded' -X 're:'
869 $ hg ci --amend -m 'reworded' -X 're:'
870 saved backup bundle to $TESTTMP/ci-post-st/.hg/strip-backup/*-amend.hg (glob)
870 saved backup bundle to $TESTTMP/ci-post-st/.hg/strip-backup/*-amend.hg (glob)
871 M modified-file
871 M modified-file
872 A added-file
872 A added-file
873 ? unknown-file
873 ? unknown-file
874 $ hg st -mardu
874 $ hg st -mardu
875 M modified-file
875 M modified-file
876 A added-file
876 A added-file
877 ? unknown-file
877 ? unknown-file
878
878
879 $ cd ..
879 $ cd ..
@@ -1,1892 +1,1892 b''
1 This file used to contains all largefile tests.
1 This file used to contains all largefile tests.
2 Do not add any new tests in this file as it his already far too long to run.
2 Do not add any new tests in this file as it his already far too long to run.
3
3
4 It contains all the testing of the basic concepts of large file in a single block.
4 It contains all the testing of the basic concepts of large file in a single block.
5
5
6 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
6 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
7 $ mkdir "${USERCACHE}"
7 $ mkdir "${USERCACHE}"
8 $ cat >> $HGRCPATH <<EOF
8 $ cat >> $HGRCPATH <<EOF
9 > [extensions]
9 > [extensions]
10 > largefiles=
10 > largefiles=
11 > purge=
11 > purge=
12 > rebase=
12 > rebase=
13 > transplant=
13 > transplant=
14 > [phases]
14 > [phases]
15 > publish=False
15 > publish=False
16 > [largefiles]
16 > [largefiles]
17 > minsize=2
17 > minsize=2
18 > patterns=glob:**.dat
18 > patterns=glob:**.dat
19 > usercache=${USERCACHE}
19 > usercache=${USERCACHE}
20 > [hooks]
20 > [hooks]
21 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
21 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
22 > EOF
22 > EOF
23
23
24 Create the repo with a couple of revisions of both large and normal
24 Create the repo with a couple of revisions of both large and normal
25 files.
25 files.
26 Test status and dirstate of largefiles and that summary output is correct.
26 Test status and dirstate of largefiles and that summary output is correct.
27
27
28 $ hg init a
28 $ hg init a
29 $ cd a
29 $ cd a
30 $ mkdir sub
30 $ mkdir sub
31 $ echo normal1 > normal1
31 $ echo normal1 > normal1
32 $ echo normal2 > sub/normal2
32 $ echo normal2 > sub/normal2
33 $ echo large1 > large1
33 $ echo large1 > large1
34 $ echo large2 > sub/large2
34 $ echo large2 > sub/large2
35 $ hg add normal1 sub/normal2
35 $ hg add normal1 sub/normal2
36 $ hg add --large large1 sub/large2
36 $ hg add --large large1 sub/large2
37 $ hg commit -m "add files"
37 $ hg commit -m "add files"
38 Invoking status precommit hook
38 Invoking status precommit hook
39 A large1
39 A large1
40 A normal1
40 A normal1
41 A sub/large2
41 A sub/large2
42 A sub/normal2
42 A sub/normal2
43 $ touch large1 sub/large2
43 $ touch large1 sub/large2
44 $ sleep 1
44 $ sleep 1
45 $ hg st
45 $ hg st
46 $ hg debugstate --no-dates
46 $ hg debugstate --no-dates
47 n 644 41 set .hglf/large1
47 n 644 41 set .hglf/large1
48 n 644 41 set .hglf/sub/large2
48 n 644 41 set .hglf/sub/large2
49 n 644 8 set normal1
49 n 644 8 set normal1
50 n 644 8 set sub/normal2
50 n 644 8 set sub/normal2
51 $ hg debugstate --large --no-dates
51 $ hg debugstate --large --no-dates
52 n 644 7 set large1
52 n 644 7 set large1
53 n 644 7 set sub/large2
53 n 644 7 set sub/large2
54 $ echo normal11 > normal1
54 $ echo normal11 > normal1
55 $ echo normal22 > sub/normal2
55 $ echo normal22 > sub/normal2
56 $ echo large11 > large1
56 $ echo large11 > large1
57 $ echo large22 > sub/large2
57 $ echo large22 > sub/large2
58 $ hg commit -m "edit files"
58 $ hg commit -m "edit files"
59 Invoking status precommit hook
59 Invoking status precommit hook
60 M large1
60 M large1
61 M normal1
61 M normal1
62 M sub/large2
62 M sub/large2
63 M sub/normal2
63 M sub/normal2
64 $ hg sum --large
64 $ hg sum --large
65 parent: 1:ce8896473775 tip
65 parent: 1:ce8896473775 tip
66 edit files
66 edit files
67 branch: default
67 branch: default
68 commit: (clean)
68 commit: (clean)
69 update: (current)
69 update: (current)
70 phases: 2 draft
70 phases: 2 draft
71 largefiles: (no remote repo)
71 largefiles: (no remote repo)
72
72
73 Commit preserved largefile contents.
73 Commit preserved largefile contents.
74
74
75 $ cat normal1
75 $ cat normal1
76 normal11
76 normal11
77 $ cat large1
77 $ cat large1
78 large11
78 large11
79 $ cat sub/normal2
79 $ cat sub/normal2
80 normal22
80 normal22
81 $ cat sub/large2
81 $ cat sub/large2
82 large22
82 large22
83
83
84 Test status, subdir and unknown files
84 Test status, subdir and unknown files
85
85
86 $ echo unknown > sub/unknown
86 $ echo unknown > sub/unknown
87 $ hg st --all
87 $ hg st --all
88 ? sub/unknown
88 ? sub/unknown
89 C large1
89 C large1
90 C normal1
90 C normal1
91 C sub/large2
91 C sub/large2
92 C sub/normal2
92 C sub/normal2
93 $ hg st --all sub
93 $ hg st --all sub
94 ? sub/unknown
94 ? sub/unknown
95 C sub/large2
95 C sub/large2
96 C sub/normal2
96 C sub/normal2
97 $ rm sub/unknown
97 $ rm sub/unknown
98
98
99 Test messages and exit codes for remove warning cases
99 Test messages and exit codes for remove warning cases
100
100
101 $ hg remove -A large1
101 $ hg remove -A large1
102 not removing large1: file still exists
102 not removing large1: file still exists
103 [1]
103 [1]
104 $ echo 'modified' > large1
104 $ echo 'modified' > large1
105 $ hg remove large1
105 $ hg remove large1
106 not removing large1: file is modified (use -f to force removal)
106 not removing large1: file is modified (use -f to force removal)
107 [1]
107 [1]
108 $ echo 'new' > normalnew
108 $ echo 'new' > normalnew
109 $ hg add normalnew
109 $ hg add normalnew
110 $ echo 'new' > largenew
110 $ echo 'new' > largenew
111 $ hg add --large normalnew
111 $ hg add --large normalnew
112 normalnew already tracked!
112 normalnew already tracked!
113 $ hg remove normalnew largenew
113 $ hg remove normalnew largenew
114 not removing largenew: file is untracked
114 not removing largenew: file is untracked
115 not removing normalnew: file has been marked for add (use 'hg forget' to undo add)
115 not removing normalnew: file has been marked for add (use 'hg forget' to undo add)
116 [1]
116 [1]
117 $ rm normalnew largenew
117 $ rm normalnew largenew
118 $ hg up -Cq
118 $ hg up -Cq
119
119
120 Remove both largefiles and normal files.
120 Remove both largefiles and normal files.
121
121
122 $ hg remove normal1 large1
122 $ hg remove normal1 large1
123 $ hg status large1
123 $ hg status large1
124 R large1
124 R large1
125 $ hg commit -m "remove files"
125 $ hg commit -m "remove files"
126 Invoking status precommit hook
126 Invoking status precommit hook
127 R large1
127 R large1
128 R normal1
128 R normal1
129 $ ls -A
129 $ ls -A
130 .hg
130 .hg
131 .hglf
131 .hglf
132 sub
132 sub
133 $ echo "testlargefile" > large1-test
133 $ echo "testlargefile" > large1-test
134 $ hg add --large large1-test
134 $ hg add --large large1-test
135 $ hg st
135 $ hg st
136 A large1-test
136 A large1-test
137 $ hg rm large1-test
137 $ hg rm large1-test
138 not removing large1-test: file has been marked for add (use forget to undo)
138 not removing large1-test: file has been marked for add (use forget to undo)
139 [1]
139 [1]
140 $ hg st
140 $ hg st
141 A large1-test
141 A large1-test
142 $ hg forget large1-test
142 $ hg forget large1-test
143 $ hg st
143 $ hg st
144 ? large1-test
144 ? large1-test
145 $ hg remove large1-test
145 $ hg remove large1-test
146 not removing large1-test: file is untracked
146 not removing large1-test: file is untracked
147 [1]
147 [1]
148 $ hg forget large1-test
148 $ hg forget large1-test
149 not removing large1-test: file is already untracked
149 not removing large1-test: file is already untracked
150 [1]
150 [1]
151 $ rm large1-test
151 $ rm large1-test
152
152
153 Copy both largefiles and normal files (testing that status output is correct).
153 Copy both largefiles and normal files (testing that status output is correct).
154
154
155 $ hg cp sub/normal2 normal1
155 $ hg cp sub/normal2 normal1
156 $ hg cp sub/large2 large1
156 $ hg cp sub/large2 large1
157 $ hg commit -m "copy files"
157 $ hg commit -m "copy files"
158 Invoking status precommit hook
158 Invoking status precommit hook
159 A large1
159 A large1
160 A normal1
160 A normal1
161 $ cat normal1
161 $ cat normal1
162 normal22
162 normal22
163 $ cat large1
163 $ cat large1
164 large22
164 large22
165
165
166 Test moving largefiles and verify that normal files are also unaffected.
166 Test moving largefiles and verify that normal files are also unaffected.
167
167
168 $ hg mv normal1 normal3
168 $ hg mv normal1 normal3
169 $ hg mv large1 large3
169 $ hg mv large1 large3
170 $ hg mv sub/normal2 sub/normal4
170 $ hg mv sub/normal2 sub/normal4
171 $ hg mv sub/large2 sub/large4
171 $ hg mv sub/large2 sub/large4
172 $ hg commit -m "move files"
172 $ hg commit -m "move files"
173 Invoking status precommit hook
173 Invoking status precommit hook
174 A large3
174 A large3
175 A normal3
175 A normal3
176 A sub/large4
176 A sub/large4
177 A sub/normal4
177 A sub/normal4
178 R large1
178 R large1
179 R normal1
179 R normal1
180 R sub/large2
180 R sub/large2
181 R sub/normal2
181 R sub/normal2
182 $ cat normal3
182 $ cat normal3
183 normal22
183 normal22
184 $ cat large3
184 $ cat large3
185 large22
185 large22
186 $ cat sub/normal4
186 $ cat sub/normal4
187 normal22
187 normal22
188 $ cat sub/large4
188 $ cat sub/large4
189 large22
189 large22
190
190
191
191
192 #if serve
192 #if serve
193 Test display of largefiles in hgweb
193 Test display of largefiles in hgweb
194
194
195 $ hg serve -d -p $HGPORT --pid-file ../hg.pid
195 $ hg serve -d -p $HGPORT --pid-file ../hg.pid
196 $ cat ../hg.pid >> $DAEMON_PIDS
196 $ cat ../hg.pid >> $DAEMON_PIDS
197 $ get-with-headers.py $LOCALIP:$HGPORT 'file/tip/?style=raw'
197 $ get-with-headers.py $LOCALIP:$HGPORT 'file/tip/?style=raw'
198 200 Script output follows
198 200 Script output follows
199
199
200
200
201 drwxr-xr-x sub
201 drwxr-xr-x sub
202 -rw-r--r-- 41 large3
202 -rw-r--r-- 41 large3
203 -rw-r--r-- 9 normal3
203 -rw-r--r-- 9 normal3
204
204
205
205
206 $ get-with-headers.py $LOCALIP:$HGPORT 'file/tip/sub/?style=raw'
206 $ get-with-headers.py $LOCALIP:$HGPORT 'file/tip/sub/?style=raw'
207 200 Script output follows
207 200 Script output follows
208
208
209
209
210 -rw-r--r-- 41 large4
210 -rw-r--r-- 41 large4
211 -rw-r--r-- 9 normal4
211 -rw-r--r-- 9 normal4
212
212
213
213
214 $ killdaemons.py
214 $ killdaemons.py
215 #endif
215 #endif
216
216
217 Test largefiles can be loaded in hgweb (wrapcommand() shouldn't fail)
217 Test largefiles can be loaded in hgweb (wrapcommand() shouldn't fail)
218
218
219 $ cat <<EOF > "$TESTTMP/hgweb.cgi"
219 $ cat <<EOF > "$TESTTMP/hgweb.cgi"
220 > #!$PYTHON
220 > #!$PYTHON
221 > from mercurial import demandimport; demandimport.enable()
221 > from mercurial import demandimport; demandimport.enable()
222 > from mercurial.hgweb import hgweb
222 > from mercurial.hgweb import hgweb
223 > from mercurial.hgweb import wsgicgi
223 > from mercurial.hgweb import wsgicgi
224 > application = hgweb(b'.', b'test repo')
224 > application = hgweb(b'.', b'test repo')
225 > wsgicgi.launch(application)
225 > wsgicgi.launch(application)
226 > EOF
226 > EOF
227 $ . "$TESTDIR/cgienv"
227 $ . "$TESTDIR/cgienv"
228
228
229 $ SCRIPT_NAME='' \
229 $ SCRIPT_NAME='' \
230 > "$PYTHON" "$TESTTMP/hgweb.cgi" > /dev/null
230 > "$PYTHON" "$TESTTMP/hgweb.cgi" > /dev/null
231
231
232 Test archiving the various revisions. These hit corner cases known with
232 Test archiving the various revisions. These hit corner cases known with
233 archiving.
233 archiving.
234
234
235 $ hg archive -r 0 ../archive0
235 $ hg archive -r 0 ../archive0
236 $ hg archive -r 1 ../archive1
236 $ hg archive -r 1 ../archive1
237 $ hg archive -r 2 ../archive2
237 $ hg archive -r 2 ../archive2
238 $ hg archive -r 3 ../archive3
238 $ hg archive -r 3 ../archive3
239 $ hg archive -r 4 ../archive4
239 $ hg archive -r 4 ../archive4
240 $ cd ../archive0
240 $ cd ../archive0
241 $ cat normal1
241 $ cat normal1
242 normal1
242 normal1
243 $ cat large1
243 $ cat large1
244 large1
244 large1
245 $ cat sub/normal2
245 $ cat sub/normal2
246 normal2
246 normal2
247 $ cat sub/large2
247 $ cat sub/large2
248 large2
248 large2
249 $ cd ../archive1
249 $ cd ../archive1
250 $ cat normal1
250 $ cat normal1
251 normal11
251 normal11
252 $ cat large1
252 $ cat large1
253 large11
253 large11
254 $ cat sub/normal2
254 $ cat sub/normal2
255 normal22
255 normal22
256 $ cat sub/large2
256 $ cat sub/large2
257 large22
257 large22
258 $ cd ../archive2
258 $ cd ../archive2
259 $ ls -A
259 $ ls -A
260 .hg_archival.txt
260 .hg_archival.txt
261 sub
261 sub
262 $ cat sub/normal2
262 $ cat sub/normal2
263 normal22
263 normal22
264 $ cat sub/large2
264 $ cat sub/large2
265 large22
265 large22
266 $ cd ../archive3
266 $ cd ../archive3
267 $ cat normal1
267 $ cat normal1
268 normal22
268 normal22
269 $ cat large1
269 $ cat large1
270 large22
270 large22
271 $ cat sub/normal2
271 $ cat sub/normal2
272 normal22
272 normal22
273 $ cat sub/large2
273 $ cat sub/large2
274 large22
274 large22
275 $ cd ../archive4
275 $ cd ../archive4
276 $ cat normal3
276 $ cat normal3
277 normal22
277 normal22
278 $ cat large3
278 $ cat large3
279 large22
279 large22
280 $ cat sub/normal4
280 $ cat sub/normal4
281 normal22
281 normal22
282 $ cat sub/large4
282 $ cat sub/large4
283 large22
283 large22
284
284
285 Commit corner case: specify files to commit.
285 Commit corner case: specify files to commit.
286
286
287 $ cd ../a
287 $ cd ../a
288 $ echo normal3 > normal3
288 $ echo normal3 > normal3
289 $ echo large3 > large3
289 $ echo large3 > large3
290 $ echo normal4 > sub/normal4
290 $ echo normal4 > sub/normal4
291 $ echo large4 > sub/large4
291 $ echo large4 > sub/large4
292 $ hg commit normal3 large3 sub/normal4 sub/large4 -m "edit files again"
292 $ hg commit normal3 large3 sub/normal4 sub/large4 -m "edit files again"
293 Invoking status precommit hook
293 Invoking status precommit hook
294 M large3
294 M large3
295 M normal3
295 M normal3
296 M sub/large4
296 M sub/large4
297 M sub/normal4
297 M sub/normal4
298 $ cat normal3
298 $ cat normal3
299 normal3
299 normal3
300 $ cat large3
300 $ cat large3
301 large3
301 large3
302 $ cat sub/normal4
302 $ cat sub/normal4
303 normal4
303 normal4
304 $ cat sub/large4
304 $ cat sub/large4
305 large4
305 large4
306
306
307 One more commit corner case: commit from a subdirectory.
307 One more commit corner case: commit from a subdirectory.
308
308
309 $ cd ../a
309 $ cd ../a
310 $ echo normal33 > normal3
310 $ echo normal33 > normal3
311 $ echo large33 > large3
311 $ echo large33 > large3
312 $ echo normal44 > sub/normal4
312 $ echo normal44 > sub/normal4
313 $ echo large44 > sub/large4
313 $ echo large44 > sub/large4
314 $ cd sub
314 $ cd sub
315 $ hg commit -m "edit files yet again"
315 $ hg commit -m "edit files yet again"
316 Invoking status precommit hook
316 Invoking status precommit hook
317 M large3
317 M large3
318 M normal3
318 M normal3
319 M sub/large4
319 M sub/large4
320 M sub/normal4
320 M sub/normal4
321 $ cat ../normal3
321 $ cat ../normal3
322 normal33
322 normal33
323 $ cat ../large3
323 $ cat ../large3
324 large33
324 large33
325 $ cat normal4
325 $ cat normal4
326 normal44
326 normal44
327 $ cat large4
327 $ cat large4
328 large44
328 large44
329
329
330 Committing standins is not allowed.
330 Committing standins is not allowed.
331
331
332 $ cd ..
332 $ cd ..
333 $ echo large3 > large3
333 $ echo large3 > large3
334 $ hg commit .hglf/large3 -m "try to commit standin"
334 $ hg commit .hglf/large3 -m "try to commit standin"
335 abort: file ".hglf/large3" is a largefile standin
335 abort: file ".hglf/large3" is a largefile standin
336 (commit the largefile itself instead)
336 (commit the largefile itself instead)
337 [255]
337 [255]
338
338
339 Corner cases for adding largefiles.
339 Corner cases for adding largefiles.
340
340
341 $ echo large5 > large5
341 $ echo large5 > large5
342 $ hg add --large large5
342 $ hg add --large large5
343 $ hg add --large large5
343 $ hg add --large large5
344 large5 already a largefile
344 large5 already a largefile
345 $ mkdir sub2
345 $ mkdir sub2
346 $ echo large6 > sub2/large6
346 $ echo large6 > sub2/large6
347 $ echo large7 > sub2/large7
347 $ echo large7 > sub2/large7
348 $ hg add --large sub2
348 $ hg add --large sub2
349 adding sub2/large6 as a largefile
349 adding sub2/large6 as a largefile
350 adding sub2/large7 as a largefile
350 adding sub2/large7 as a largefile
351 $ hg st
351 $ hg st
352 M large3
352 M large3
353 A large5
353 A large5
354 A sub2/large6
354 A sub2/large6
355 A sub2/large7
355 A sub2/large7
356
356
357 Committing directories containing only largefiles.
357 Committing directories containing only largefiles.
358
358
359 $ mkdir -p z/y/x/m
359 $ mkdir -p z/y/x/m
360 $ touch z/y/x/m/large1
360 $ touch z/y/x/m/large1
361 $ touch z/y/x/large2
361 $ touch z/y/x/large2
362 $ hg add --large z/y/x/m/large1 z/y/x/large2
362 $ hg add --large z/y/x/m/large1 z/y/x/large2
363 $ hg commit -m "Subdir with directory only containing largefiles" z
363 $ hg commit -m "Subdir with directory only containing largefiles" z
364 Invoking status precommit hook
364 Invoking status precommit hook
365 M large3
365 M large3
366 A large5
366 A large5
367 A sub2/large6
367 A sub2/large6
368 A sub2/large7
368 A sub2/large7
369 A z/y/x/large2
369 A z/y/x/large2
370 A z/y/x/m/large1
370 A z/y/x/m/large1
371
371
372 (and a bit of log testing)
372 (and a bit of log testing)
373
373
374 $ hg log -T '{rev}\n' z/y/x/m/large1
374 $ hg log -T '{rev}\n' z/y/x/m/large1
375 7
375 7
376 $ hg log -T '{rev}\n' z/y/x/m # with only a largefile
376 $ hg log -T '{rev}\n' z/y/x/m # with only a largefile
377 7
377 7
378
378
379 $ hg rollback --quiet
379 $ hg rollback --quiet
380 $ touch z/y/x/m/normal
380 $ touch z/y/x/m/normal
381 $ hg add z/y/x/m/normal
381 $ hg add z/y/x/m/normal
382 $ hg commit -m "Subdir with mixed contents" z
382 $ hg commit -m "Subdir with mixed contents" z
383 Invoking status precommit hook
383 Invoking status precommit hook
384 M large3
384 M large3
385 A large5
385 A large5
386 A sub2/large6
386 A sub2/large6
387 A sub2/large7
387 A sub2/large7
388 A z/y/x/large2
388 A z/y/x/large2
389 A z/y/x/m/large1
389 A z/y/x/m/large1
390 A z/y/x/m/normal
390 A z/y/x/m/normal
391 $ hg st
391 $ hg st
392 M large3
392 M large3
393 A large5
393 A large5
394 A sub2/large6
394 A sub2/large6
395 A sub2/large7
395 A sub2/large7
396 $ hg rollback --quiet
396 $ hg rollback --quiet
397 $ hg revert z/y/x/large2 z/y/x/m/large1
397 $ hg revert z/y/x/large2 z/y/x/m/large1
398 $ rm z/y/x/large2 z/y/x/m/large1
398 $ rm z/y/x/large2 z/y/x/m/large1
399 $ hg commit -m "Subdir with normal contents" z
399 $ hg commit -m "Subdir with normal contents" z
400 Invoking status precommit hook
400 Invoking status precommit hook
401 M large3
401 M large3
402 A large5
402 A large5
403 A sub2/large6
403 A sub2/large6
404 A sub2/large7
404 A sub2/large7
405 A z/y/x/m/normal
405 A z/y/x/m/normal
406 $ hg st
406 $ hg st
407 M large3
407 M large3
408 A large5
408 A large5
409 A sub2/large6
409 A sub2/large6
410 A sub2/large7
410 A sub2/large7
411 $ hg rollback --quiet
411 $ hg rollback --quiet
412 $ hg revert --quiet z
412 $ hg revert --quiet z
413 $ hg commit -m "Empty subdir" z
413 $ hg commit -m "Empty subdir" z
414 abort: z: no match under directory!
414 abort: z: no match under directory!
415 [255]
415 [10]
416 $ rm -rf z
416 $ rm -rf z
417 $ hg ci -m "standin" .hglf
417 $ hg ci -m "standin" .hglf
418 abort: file ".hglf" is a largefile standin
418 abort: file ".hglf" is a largefile standin
419 (commit the largefile itself instead)
419 (commit the largefile itself instead)
420 [255]
420 [255]
421
421
422 Test "hg status" with combination of 'file pattern' and 'directory
422 Test "hg status" with combination of 'file pattern' and 'directory
423 pattern' for largefiles:
423 pattern' for largefiles:
424
424
425 $ hg status sub2/large6 sub2
425 $ hg status sub2/large6 sub2
426 A sub2/large6
426 A sub2/large6
427 A sub2/large7
427 A sub2/large7
428
428
429 Config settings (pattern **.dat, minsize 2 MB) are respected.
429 Config settings (pattern **.dat, minsize 2 MB) are respected.
430
430
431 $ echo testdata > test.dat
431 $ echo testdata > test.dat
432 $ dd bs=1k count=2k if=/dev/zero of=reallylarge > /dev/null 2> /dev/null
432 $ dd bs=1k count=2k if=/dev/zero of=reallylarge > /dev/null 2> /dev/null
433 $ hg add
433 $ hg add
434 adding reallylarge as a largefile
434 adding reallylarge as a largefile
435 adding test.dat as a largefile
435 adding test.dat as a largefile
436
436
437 Test that minsize and --lfsize handle float values;
437 Test that minsize and --lfsize handle float values;
438 also tests that --lfsize overrides largefiles.minsize.
438 also tests that --lfsize overrides largefiles.minsize.
439 (0.250 MB = 256 kB = 262144 B)
439 (0.250 MB = 256 kB = 262144 B)
440
440
441 $ dd if=/dev/zero of=ratherlarge bs=1024 count=256 > /dev/null 2> /dev/null
441 $ dd if=/dev/zero of=ratherlarge bs=1024 count=256 > /dev/null 2> /dev/null
442 $ dd if=/dev/zero of=medium bs=1024 count=128 > /dev/null 2> /dev/null
442 $ dd if=/dev/zero of=medium bs=1024 count=128 > /dev/null 2> /dev/null
443 $ hg --config largefiles.minsize=.25 add
443 $ hg --config largefiles.minsize=.25 add
444 adding ratherlarge as a largefile
444 adding ratherlarge as a largefile
445 adding medium
445 adding medium
446 $ hg forget medium
446 $ hg forget medium
447 $ hg --config largefiles.minsize=.25 add --lfsize=.125
447 $ hg --config largefiles.minsize=.25 add --lfsize=.125
448 adding medium as a largefile
448 adding medium as a largefile
449 $ dd if=/dev/zero of=notlarge bs=1024 count=127 > /dev/null 2> /dev/null
449 $ dd if=/dev/zero of=notlarge bs=1024 count=127 > /dev/null 2> /dev/null
450 $ hg --config largefiles.minsize=.25 add --lfsize=.125
450 $ hg --config largefiles.minsize=.25 add --lfsize=.125
451 adding notlarge
451 adding notlarge
452 $ hg forget notlarge
452 $ hg forget notlarge
453
453
454 Test forget on largefiles.
454 Test forget on largefiles.
455
455
456 $ hg forget large3 large5 test.dat reallylarge ratherlarge medium
456 $ hg forget large3 large5 test.dat reallylarge ratherlarge medium
457 $ hg commit -m "add/edit more largefiles"
457 $ hg commit -m "add/edit more largefiles"
458 Invoking status precommit hook
458 Invoking status precommit hook
459 A sub2/large6
459 A sub2/large6
460 A sub2/large7
460 A sub2/large7
461 R large3
461 R large3
462 ? large5
462 ? large5
463 ? medium
463 ? medium
464 ? notlarge
464 ? notlarge
465 ? ratherlarge
465 ? ratherlarge
466 ? reallylarge
466 ? reallylarge
467 ? test.dat
467 ? test.dat
468 $ hg st
468 $ hg st
469 ? large3
469 ? large3
470 ? large5
470 ? large5
471 ? medium
471 ? medium
472 ? notlarge
472 ? notlarge
473 ? ratherlarge
473 ? ratherlarge
474 ? reallylarge
474 ? reallylarge
475 ? test.dat
475 ? test.dat
476
476
477 Purge with largefiles: verify that largefiles are still in the working
477 Purge with largefiles: verify that largefiles are still in the working
478 dir after a purge.
478 dir after a purge.
479
479
480 $ hg purge --all
480 $ hg purge --all
481 $ cat sub/large4
481 $ cat sub/large4
482 large44
482 large44
483 $ cat sub2/large6
483 $ cat sub2/large6
484 large6
484 large6
485 $ cat sub2/large7
485 $ cat sub2/large7
486 large7
486 large7
487
487
488 Test addremove: verify that files that should be added as largefiles are added as
488 Test addremove: verify that files that should be added as largefiles are added as
489 such and that already-existing largefiles are not added as normal files by
489 such and that already-existing largefiles are not added as normal files by
490 accident.
490 accident.
491
491
492 $ rm normal3
492 $ rm normal3
493 $ rm sub/large4
493 $ rm sub/large4
494 $ echo "testing addremove with patterns" > testaddremove.dat
494 $ echo "testing addremove with patterns" > testaddremove.dat
495 $ echo "normaladdremove" > normaladdremove
495 $ echo "normaladdremove" > normaladdremove
496 $ hg addremove
496 $ hg addremove
497 removing sub/large4
497 removing sub/large4
498 adding testaddremove.dat as a largefile
498 adding testaddremove.dat as a largefile
499 removing normal3
499 removing normal3
500 adding normaladdremove
500 adding normaladdremove
501
501
502 Test addremove with -R
502 Test addremove with -R
503
503
504 $ hg up -C
504 $ hg up -C
505 getting changed largefiles
505 getting changed largefiles
506 1 largefiles updated, 0 removed
506 1 largefiles updated, 0 removed
507 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
507 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
508 $ rm normal3
508 $ rm normal3
509 $ rm sub/large4
509 $ rm sub/large4
510 $ echo "testing addremove with patterns" > testaddremove.dat
510 $ echo "testing addremove with patterns" > testaddremove.dat
511 $ echo "normaladdremove" > normaladdremove
511 $ echo "normaladdremove" > normaladdremove
512 $ cd ..
512 $ cd ..
513 $ hg -R a -v addremove
513 $ hg -R a -v addremove
514 removing sub/large4
514 removing sub/large4
515 adding testaddremove.dat as a largefile
515 adding testaddremove.dat as a largefile
516 removing normal3
516 removing normal3
517 adding normaladdremove
517 adding normaladdremove
518 $ cd a
518 $ cd a
519
519
520 Test 3364
520 Test 3364
521 $ hg clone . ../addrm
521 $ hg clone . ../addrm
522 updating to branch default
522 updating to branch default
523 getting changed largefiles
523 getting changed largefiles
524 3 largefiles updated, 0 removed
524 3 largefiles updated, 0 removed
525 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
525 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
526 $ cd ../addrm
526 $ cd ../addrm
527 $ cat >> .hg/hgrc <<EOF
527 $ cat >> .hg/hgrc <<EOF
528 > [hooks]
528 > [hooks]
529 > post-commit.stat=sh -c "echo \\"Invoking status postcommit hook\\"; hg status -A"
529 > post-commit.stat=sh -c "echo \\"Invoking status postcommit hook\\"; hg status -A"
530 > EOF
530 > EOF
531 $ touch foo
531 $ touch foo
532 $ hg add --large foo
532 $ hg add --large foo
533 $ hg ci -m "add foo"
533 $ hg ci -m "add foo"
534 Invoking status precommit hook
534 Invoking status precommit hook
535 A foo
535 A foo
536 Invoking status postcommit hook
536 Invoking status postcommit hook
537 C foo
537 C foo
538 C normal3
538 C normal3
539 C sub/large4
539 C sub/large4
540 C sub/normal4
540 C sub/normal4
541 C sub2/large6
541 C sub2/large6
542 C sub2/large7
542 C sub2/large7
543 $ rm foo
543 $ rm foo
544 $ hg st
544 $ hg st
545 ! foo
545 ! foo
546 hmm.. no precommit invoked, but there is a postcommit??
546 hmm.. no precommit invoked, but there is a postcommit??
547 $ hg ci -m "will not checkin"
547 $ hg ci -m "will not checkin"
548 nothing changed (1 missing files, see 'hg status')
548 nothing changed (1 missing files, see 'hg status')
549 Invoking status postcommit hook
549 Invoking status postcommit hook
550 ! foo
550 ! foo
551 C normal3
551 C normal3
552 C sub/large4
552 C sub/large4
553 C sub/normal4
553 C sub/normal4
554 C sub2/large6
554 C sub2/large6
555 C sub2/large7
555 C sub2/large7
556 [1]
556 [1]
557 $ hg addremove
557 $ hg addremove
558 removing foo
558 removing foo
559 $ hg st
559 $ hg st
560 R foo
560 R foo
561 $ hg ci -m "used to say nothing changed"
561 $ hg ci -m "used to say nothing changed"
562 Invoking status precommit hook
562 Invoking status precommit hook
563 R foo
563 R foo
564 Invoking status postcommit hook
564 Invoking status postcommit hook
565 C normal3
565 C normal3
566 C sub/large4
566 C sub/large4
567 C sub/normal4
567 C sub/normal4
568 C sub2/large6
568 C sub2/large6
569 C sub2/large7
569 C sub2/large7
570 $ hg st
570 $ hg st
571
571
572 Test 3507 (both normal files and largefiles were a problem)
572 Test 3507 (both normal files and largefiles were a problem)
573
573
574 $ touch normal
574 $ touch normal
575 $ touch large
575 $ touch large
576 $ hg add normal
576 $ hg add normal
577 $ hg add --large large
577 $ hg add --large large
578 $ hg ci -m "added"
578 $ hg ci -m "added"
579 Invoking status precommit hook
579 Invoking status precommit hook
580 A large
580 A large
581 A normal
581 A normal
582 Invoking status postcommit hook
582 Invoking status postcommit hook
583 C large
583 C large
584 C normal
584 C normal
585 C normal3
585 C normal3
586 C sub/large4
586 C sub/large4
587 C sub/normal4
587 C sub/normal4
588 C sub2/large6
588 C sub2/large6
589 C sub2/large7
589 C sub2/large7
590 $ hg remove normal
590 $ hg remove normal
591 $ hg addremove --traceback
591 $ hg addremove --traceback
592 $ hg ci -m "addremoved normal"
592 $ hg ci -m "addremoved normal"
593 Invoking status precommit hook
593 Invoking status precommit hook
594 R normal
594 R normal
595 Invoking status postcommit hook
595 Invoking status postcommit hook
596 C large
596 C large
597 C normal3
597 C normal3
598 C sub/large4
598 C sub/large4
599 C sub/normal4
599 C sub/normal4
600 C sub2/large6
600 C sub2/large6
601 C sub2/large7
601 C sub2/large7
602 $ hg up -C '.^'
602 $ hg up -C '.^'
603 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
603 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
604 $ hg remove large
604 $ hg remove large
605 $ hg addremove --traceback
605 $ hg addremove --traceback
606 $ hg ci -m "removed large"
606 $ hg ci -m "removed large"
607 Invoking status precommit hook
607 Invoking status precommit hook
608 R large
608 R large
609 created new head
609 created new head
610 Invoking status postcommit hook
610 Invoking status postcommit hook
611 C normal
611 C normal
612 C normal3
612 C normal3
613 C sub/large4
613 C sub/large4
614 C sub/normal4
614 C sub/normal4
615 C sub2/large6
615 C sub2/large6
616 C sub2/large7
616 C sub2/large7
617
617
618 Test commit -A (issue3542)
618 Test commit -A (issue3542)
619 $ echo large8 > large8
619 $ echo large8 > large8
620 $ hg add --large large8
620 $ hg add --large large8
621 $ hg ci -Am 'this used to add large8 as normal and commit both'
621 $ hg ci -Am 'this used to add large8 as normal and commit both'
622 Invoking status precommit hook
622 Invoking status precommit hook
623 A large8
623 A large8
624 Invoking status postcommit hook
624 Invoking status postcommit hook
625 C large8
625 C large8
626 C normal
626 C normal
627 C normal3
627 C normal3
628 C sub/large4
628 C sub/large4
629 C sub/normal4
629 C sub/normal4
630 C sub2/large6
630 C sub2/large6
631 C sub2/large7
631 C sub2/large7
632 $ rm large8
632 $ rm large8
633 $ hg ci -Am 'this used to not notice the rm'
633 $ hg ci -Am 'this used to not notice the rm'
634 removing large8
634 removing large8
635 Invoking status precommit hook
635 Invoking status precommit hook
636 R large8
636 R large8
637 Invoking status postcommit hook
637 Invoking status postcommit hook
638 C normal
638 C normal
639 C normal3
639 C normal3
640 C sub/large4
640 C sub/large4
641 C sub/normal4
641 C sub/normal4
642 C sub2/large6
642 C sub2/large6
643 C sub2/large7
643 C sub2/large7
644
644
645 Test that a standin can't be added as a large file
645 Test that a standin can't be added as a large file
646
646
647 $ touch large
647 $ touch large
648 $ hg add --large large
648 $ hg add --large large
649 $ hg ci -m "add"
649 $ hg ci -m "add"
650 Invoking status precommit hook
650 Invoking status precommit hook
651 A large
651 A large
652 Invoking status postcommit hook
652 Invoking status postcommit hook
653 C large
653 C large
654 C normal
654 C normal
655 C normal3
655 C normal3
656 C sub/large4
656 C sub/large4
657 C sub/normal4
657 C sub/normal4
658 C sub2/large6
658 C sub2/large6
659 C sub2/large7
659 C sub2/large7
660 $ hg remove large
660 $ hg remove large
661 $ touch large
661 $ touch large
662 $ hg addremove --config largefiles.patterns=**large --traceback
662 $ hg addremove --config largefiles.patterns=**large --traceback
663 adding large as a largefile
663 adding large as a largefile
664
664
665 Test that outgoing --large works (with revsets too)
665 Test that outgoing --large works (with revsets too)
666 $ hg outgoing --rev '.^' --large
666 $ hg outgoing --rev '.^' --large
667 comparing with $TESTTMP/a
667 comparing with $TESTTMP/a
668 searching for changes
668 searching for changes
669 changeset: 8:c02fd3b77ec4
669 changeset: 8:c02fd3b77ec4
670 user: test
670 user: test
671 date: Thu Jan 01 00:00:00 1970 +0000
671 date: Thu Jan 01 00:00:00 1970 +0000
672 summary: add foo
672 summary: add foo
673
673
674 changeset: 9:289dd08c9bbb
674 changeset: 9:289dd08c9bbb
675 user: test
675 user: test
676 date: Thu Jan 01 00:00:00 1970 +0000
676 date: Thu Jan 01 00:00:00 1970 +0000
677 summary: used to say nothing changed
677 summary: used to say nothing changed
678
678
679 changeset: 10:34f23ac6ac12
679 changeset: 10:34f23ac6ac12
680 user: test
680 user: test
681 date: Thu Jan 01 00:00:00 1970 +0000
681 date: Thu Jan 01 00:00:00 1970 +0000
682 summary: added
682 summary: added
683
683
684 changeset: 12:710c1b2f523c
684 changeset: 12:710c1b2f523c
685 parent: 10:34f23ac6ac12
685 parent: 10:34f23ac6ac12
686 user: test
686 user: test
687 date: Thu Jan 01 00:00:00 1970 +0000
687 date: Thu Jan 01 00:00:00 1970 +0000
688 summary: removed large
688 summary: removed large
689
689
690 changeset: 13:0a3e75774479
690 changeset: 13:0a3e75774479
691 user: test
691 user: test
692 date: Thu Jan 01 00:00:00 1970 +0000
692 date: Thu Jan 01 00:00:00 1970 +0000
693 summary: this used to add large8 as normal and commit both
693 summary: this used to add large8 as normal and commit both
694
694
695 changeset: 14:84f3d378175c
695 changeset: 14:84f3d378175c
696 user: test
696 user: test
697 date: Thu Jan 01 00:00:00 1970 +0000
697 date: Thu Jan 01 00:00:00 1970 +0000
698 summary: this used to not notice the rm
698 summary: this used to not notice the rm
699
699
700 largefiles to upload (1 entities):
700 largefiles to upload (1 entities):
701 large8
701 large8
702
702
703 $ cd ../a
703 $ cd ../a
704
704
705 Clone a largefiles repo.
705 Clone a largefiles repo.
706
706
707 $ hg clone . ../b
707 $ hg clone . ../b
708 updating to branch default
708 updating to branch default
709 getting changed largefiles
709 getting changed largefiles
710 3 largefiles updated, 0 removed
710 3 largefiles updated, 0 removed
711 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
711 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
712 $ cd ../b
712 $ cd ../b
713 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
713 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
714 7:daea875e9014 add/edit more largefiles
714 7:daea875e9014 add/edit more largefiles
715 6:4355d653f84f edit files yet again
715 6:4355d653f84f edit files yet again
716 5:9d5af5072dbd edit files again
716 5:9d5af5072dbd edit files again
717 4:74c02385b94c move files
717 4:74c02385b94c move files
718 3:9e8fbc4bce62 copy files
718 3:9e8fbc4bce62 copy files
719 2:51a0ae4d5864 remove files
719 2:51a0ae4d5864 remove files
720 1:ce8896473775 edit files
720 1:ce8896473775 edit files
721 0:30d30fe6a5be add files
721 0:30d30fe6a5be add files
722 $ cat normal3
722 $ cat normal3
723 normal33
723 normal33
724
724
725 Test graph log
725 Test graph log
726
726
727 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
727 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
728 @ 7:daea875e9014 add/edit more largefiles
728 @ 7:daea875e9014 add/edit more largefiles
729 |
729 |
730 o 6:4355d653f84f edit files yet again
730 o 6:4355d653f84f edit files yet again
731 |
731 |
732 o 5:9d5af5072dbd edit files again
732 o 5:9d5af5072dbd edit files again
733 |
733 |
734 o 4:74c02385b94c move files
734 o 4:74c02385b94c move files
735 |
735 |
736 o 3:9e8fbc4bce62 copy files
736 o 3:9e8fbc4bce62 copy files
737 |
737 |
738 o 2:51a0ae4d5864 remove files
738 o 2:51a0ae4d5864 remove files
739 |
739 |
740 o 1:ce8896473775 edit files
740 o 1:ce8896473775 edit files
741 |
741 |
742 o 0:30d30fe6a5be add files
742 o 0:30d30fe6a5be add files
743
743
744
744
745 Test log with --patch
745 Test log with --patch
746
746
747 $ hg log --patch -r 6::7
747 $ hg log --patch -r 6::7
748 changeset: 6:4355d653f84f
748 changeset: 6:4355d653f84f
749 user: test
749 user: test
750 date: Thu Jan 01 00:00:00 1970 +0000
750 date: Thu Jan 01 00:00:00 1970 +0000
751 summary: edit files yet again
751 summary: edit files yet again
752
752
753 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
753 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
754 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
754 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
755 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
755 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
756 @@ -1,1 +1,1 @@
756 @@ -1,1 +1,1 @@
757 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
757 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
758 +7838695e10da2bb75ac1156565f40a2595fa2fa0
758 +7838695e10da2bb75ac1156565f40a2595fa2fa0
759 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
759 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
760 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
760 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
761 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
761 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
762 @@ -1,1 +1,1 @@
762 @@ -1,1 +1,1 @@
763 -aeb2210d19f02886dde00dac279729a48471e2f9
763 -aeb2210d19f02886dde00dac279729a48471e2f9
764 +971fb41e78fea4f8e0ba5244784239371cb00591
764 +971fb41e78fea4f8e0ba5244784239371cb00591
765 diff -r 9d5af5072dbd -r 4355d653f84f normal3
765 diff -r 9d5af5072dbd -r 4355d653f84f normal3
766 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
766 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
767 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
767 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
768 @@ -1,1 +1,1 @@
768 @@ -1,1 +1,1 @@
769 -normal3
769 -normal3
770 +normal33
770 +normal33
771 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
771 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
772 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
772 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
773 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
773 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
774 @@ -1,1 +1,1 @@
774 @@ -1,1 +1,1 @@
775 -normal4
775 -normal4
776 +normal44
776 +normal44
777
777
778 changeset: 7:daea875e9014
778 changeset: 7:daea875e9014
779 tag: tip
779 tag: tip
780 user: test
780 user: test
781 date: Thu Jan 01 00:00:00 1970 +0000
781 date: Thu Jan 01 00:00:00 1970 +0000
782 summary: add/edit more largefiles
782 summary: add/edit more largefiles
783
783
784 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
784 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
785 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
785 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
786 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
786 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
787 @@ -1,1 +0,0 @@
787 @@ -1,1 +0,0 @@
788 -7838695e10da2bb75ac1156565f40a2595fa2fa0
788 -7838695e10da2bb75ac1156565f40a2595fa2fa0
789 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
789 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
790 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
790 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
791 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
791 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
792 @@ -0,0 +1,1 @@
792 @@ -0,0 +1,1 @@
793 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
793 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
794 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
794 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
795 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
795 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
796 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
796 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
797 @@ -0,0 +1,1 @@
797 @@ -0,0 +1,1 @@
798 +bb3151689acb10f0c3125c560d5e63df914bc1af
798 +bb3151689acb10f0c3125c560d5e63df914bc1af
799
799
800
800
801 $ hg log --patch -r 6::7 sub/
801 $ hg log --patch -r 6::7 sub/
802 changeset: 6:4355d653f84f
802 changeset: 6:4355d653f84f
803 user: test
803 user: test
804 date: Thu Jan 01 00:00:00 1970 +0000
804 date: Thu Jan 01 00:00:00 1970 +0000
805 summary: edit files yet again
805 summary: edit files yet again
806
806
807 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
807 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
808 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
808 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
809 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
809 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
810 @@ -1,1 +1,1 @@
810 @@ -1,1 +1,1 @@
811 -aeb2210d19f02886dde00dac279729a48471e2f9
811 -aeb2210d19f02886dde00dac279729a48471e2f9
812 +971fb41e78fea4f8e0ba5244784239371cb00591
812 +971fb41e78fea4f8e0ba5244784239371cb00591
813 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
813 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
814 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
814 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
815 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
815 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
816 @@ -1,1 +1,1 @@
816 @@ -1,1 +1,1 @@
817 -normal4
817 -normal4
818 +normal44
818 +normal44
819
819
820
820
821 log with both --follow and --patch
821 log with both --follow and --patch
822
822
823 $ hg log --follow --patch --limit 2
823 $ hg log --follow --patch --limit 2
824 changeset: 7:daea875e9014
824 changeset: 7:daea875e9014
825 tag: tip
825 tag: tip
826 user: test
826 user: test
827 date: Thu Jan 01 00:00:00 1970 +0000
827 date: Thu Jan 01 00:00:00 1970 +0000
828 summary: add/edit more largefiles
828 summary: add/edit more largefiles
829
829
830 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
830 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
831 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
831 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
832 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
832 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
833 @@ -1,1 +0,0 @@
833 @@ -1,1 +0,0 @@
834 -7838695e10da2bb75ac1156565f40a2595fa2fa0
834 -7838695e10da2bb75ac1156565f40a2595fa2fa0
835 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
835 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
836 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
836 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
837 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
837 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
838 @@ -0,0 +1,1 @@
838 @@ -0,0 +1,1 @@
839 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
839 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
840 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
840 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
841 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
841 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
842 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
842 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
843 @@ -0,0 +1,1 @@
843 @@ -0,0 +1,1 @@
844 +bb3151689acb10f0c3125c560d5e63df914bc1af
844 +bb3151689acb10f0c3125c560d5e63df914bc1af
845
845
846 changeset: 6:4355d653f84f
846 changeset: 6:4355d653f84f
847 user: test
847 user: test
848 date: Thu Jan 01 00:00:00 1970 +0000
848 date: Thu Jan 01 00:00:00 1970 +0000
849 summary: edit files yet again
849 summary: edit files yet again
850
850
851 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
851 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
852 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
852 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
853 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
853 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
854 @@ -1,1 +1,1 @@
854 @@ -1,1 +1,1 @@
855 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
855 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
856 +7838695e10da2bb75ac1156565f40a2595fa2fa0
856 +7838695e10da2bb75ac1156565f40a2595fa2fa0
857 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
857 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
858 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
858 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
859 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
859 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
860 @@ -1,1 +1,1 @@
860 @@ -1,1 +1,1 @@
861 -aeb2210d19f02886dde00dac279729a48471e2f9
861 -aeb2210d19f02886dde00dac279729a48471e2f9
862 +971fb41e78fea4f8e0ba5244784239371cb00591
862 +971fb41e78fea4f8e0ba5244784239371cb00591
863 diff -r 9d5af5072dbd -r 4355d653f84f normal3
863 diff -r 9d5af5072dbd -r 4355d653f84f normal3
864 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
864 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
865 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
865 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
866 @@ -1,1 +1,1 @@
866 @@ -1,1 +1,1 @@
867 -normal3
867 -normal3
868 +normal33
868 +normal33
869 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
869 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
870 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
870 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
871 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
871 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
872 @@ -1,1 +1,1 @@
872 @@ -1,1 +1,1 @@
873 -normal4
873 -normal4
874 +normal44
874 +normal44
875
875
876 $ hg log --follow --patch sub/large4
876 $ hg log --follow --patch sub/large4
877 changeset: 6:4355d653f84f
877 changeset: 6:4355d653f84f
878 user: test
878 user: test
879 date: Thu Jan 01 00:00:00 1970 +0000
879 date: Thu Jan 01 00:00:00 1970 +0000
880 summary: edit files yet again
880 summary: edit files yet again
881
881
882 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
882 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
883 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
883 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
884 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
884 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
885 @@ -1,1 +1,1 @@
885 @@ -1,1 +1,1 @@
886 -aeb2210d19f02886dde00dac279729a48471e2f9
886 -aeb2210d19f02886dde00dac279729a48471e2f9
887 +971fb41e78fea4f8e0ba5244784239371cb00591
887 +971fb41e78fea4f8e0ba5244784239371cb00591
888
888
889 changeset: 5:9d5af5072dbd
889 changeset: 5:9d5af5072dbd
890 user: test
890 user: test
891 date: Thu Jan 01 00:00:00 1970 +0000
891 date: Thu Jan 01 00:00:00 1970 +0000
892 summary: edit files again
892 summary: edit files again
893
893
894 diff -r 74c02385b94c -r 9d5af5072dbd .hglf/sub/large4
894 diff -r 74c02385b94c -r 9d5af5072dbd .hglf/sub/large4
895 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
895 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
896 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
896 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
897 @@ -1,1 +1,1 @@
897 @@ -1,1 +1,1 @@
898 -eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
898 -eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
899 +aeb2210d19f02886dde00dac279729a48471e2f9
899 +aeb2210d19f02886dde00dac279729a48471e2f9
900
900
901 changeset: 4:74c02385b94c
901 changeset: 4:74c02385b94c
902 user: test
902 user: test
903 date: Thu Jan 01 00:00:00 1970 +0000
903 date: Thu Jan 01 00:00:00 1970 +0000
904 summary: move files
904 summary: move files
905
905
906 diff -r 9e8fbc4bce62 -r 74c02385b94c .hglf/sub/large4
906 diff -r 9e8fbc4bce62 -r 74c02385b94c .hglf/sub/large4
907 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
907 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
908 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
908 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
909 @@ -0,0 +1,1 @@
909 @@ -0,0 +1,1 @@
910 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
910 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
911
911
912 changeset: 1:ce8896473775
912 changeset: 1:ce8896473775
913 user: test
913 user: test
914 date: Thu Jan 01 00:00:00 1970 +0000
914 date: Thu Jan 01 00:00:00 1970 +0000
915 summary: edit files
915 summary: edit files
916
916
917 diff -r 30d30fe6a5be -r ce8896473775 .hglf/sub/large2
917 diff -r 30d30fe6a5be -r ce8896473775 .hglf/sub/large2
918 --- a/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
918 --- a/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
919 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
919 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
920 @@ -1,1 +1,1 @@
920 @@ -1,1 +1,1 @@
921 -1deebade43c8c498a3c8daddac0244dc55d1331d
921 -1deebade43c8c498a3c8daddac0244dc55d1331d
922 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
922 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
923
923
924 changeset: 0:30d30fe6a5be
924 changeset: 0:30d30fe6a5be
925 user: test
925 user: test
926 date: Thu Jan 01 00:00:00 1970 +0000
926 date: Thu Jan 01 00:00:00 1970 +0000
927 summary: add files
927 summary: add files
928
928
929 diff -r 000000000000 -r 30d30fe6a5be .hglf/sub/large2
929 diff -r 000000000000 -r 30d30fe6a5be .hglf/sub/large2
930 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
930 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
931 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
931 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
932 @@ -0,0 +1,1 @@
932 @@ -0,0 +1,1 @@
933 +1deebade43c8c498a3c8daddac0244dc55d1331d
933 +1deebade43c8c498a3c8daddac0244dc55d1331d
934
934
935 $ cat sub/normal4
935 $ cat sub/normal4
936 normal44
936 normal44
937 $ cat sub/large4
937 $ cat sub/large4
938 large44
938 large44
939 $ cat sub2/large6
939 $ cat sub2/large6
940 large6
940 large6
941 $ cat sub2/large7
941 $ cat sub2/large7
942 large7
942 large7
943 $ hg log -qf sub2/large7
943 $ hg log -qf sub2/large7
944 7:daea875e9014
944 7:daea875e9014
945 $ hg log -Gqf sub2/large7
945 $ hg log -Gqf sub2/large7
946 @ 7:daea875e9014
946 @ 7:daea875e9014
947 |
947 |
948 ~
948 ~
949 $ cd ..
949 $ cd ..
950
950
951 Test log from outside repo
951 Test log from outside repo
952
952
953 $ hg log b/sub -T '{rev}:{node|short} {desc|firstline}\n'
953 $ hg log b/sub -T '{rev}:{node|short} {desc|firstline}\n'
954 6:4355d653f84f edit files yet again
954 6:4355d653f84f edit files yet again
955 5:9d5af5072dbd edit files again
955 5:9d5af5072dbd edit files again
956 4:74c02385b94c move files
956 4:74c02385b94c move files
957 1:ce8896473775 edit files
957 1:ce8896473775 edit files
958 0:30d30fe6a5be add files
958 0:30d30fe6a5be add files
959
959
960 Test clone at revision
960 Test clone at revision
961
961
962 $ hg clone a -r 3 c
962 $ hg clone a -r 3 c
963 adding changesets
963 adding changesets
964 adding manifests
964 adding manifests
965 adding file changes
965 adding file changes
966 added 4 changesets with 10 changes to 4 files
966 added 4 changesets with 10 changes to 4 files
967 new changesets 30d30fe6a5be:9e8fbc4bce62 (4 drafts)
967 new changesets 30d30fe6a5be:9e8fbc4bce62 (4 drafts)
968 updating to branch default
968 updating to branch default
969 getting changed largefiles
969 getting changed largefiles
970 2 largefiles updated, 0 removed
970 2 largefiles updated, 0 removed
971 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
971 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
972 $ cd c
972 $ cd c
973 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
973 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
974 3:9e8fbc4bce62 copy files
974 3:9e8fbc4bce62 copy files
975 2:51a0ae4d5864 remove files
975 2:51a0ae4d5864 remove files
976 1:ce8896473775 edit files
976 1:ce8896473775 edit files
977 0:30d30fe6a5be add files
977 0:30d30fe6a5be add files
978 $ cat normal1
978 $ cat normal1
979 normal22
979 normal22
980 $ cat large1
980 $ cat large1
981 large22
981 large22
982 $ cat sub/normal2
982 $ cat sub/normal2
983 normal22
983 normal22
984 $ cat sub/large2
984 $ cat sub/large2
985 large22
985 large22
986
986
987 Old revisions of a clone have correct largefiles content (this also
987 Old revisions of a clone have correct largefiles content (this also
988 tests update).
988 tests update).
989
989
990 $ hg update -r 1
990 $ hg update -r 1
991 getting changed largefiles
991 getting changed largefiles
992 1 largefiles updated, 0 removed
992 1 largefiles updated, 0 removed
993 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
993 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
994 $ cat large1
994 $ cat large1
995 large11
995 large11
996 $ cat sub/large2
996 $ cat sub/large2
997 large22
997 large22
998 $ cd ..
998 $ cd ..
999
999
1000 Test cloning with --all-largefiles flag
1000 Test cloning with --all-largefiles flag
1001
1001
1002 $ rm "${USERCACHE}"/*
1002 $ rm "${USERCACHE}"/*
1003 $ hg clone --all-largefiles a a-backup
1003 $ hg clone --all-largefiles a a-backup
1004 updating to branch default
1004 updating to branch default
1005 getting changed largefiles
1005 getting changed largefiles
1006 3 largefiles updated, 0 removed
1006 3 largefiles updated, 0 removed
1007 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1007 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1008 7 additional largefiles cached
1008 7 additional largefiles cached
1009
1009
1010 $ rm "${USERCACHE}"/*
1010 $ rm "${USERCACHE}"/*
1011 $ hg clone --all-largefiles -u 0 a a-clone0
1011 $ hg clone --all-largefiles -u 0 a a-clone0
1012 updating to branch default
1012 updating to branch default
1013 getting changed largefiles
1013 getting changed largefiles
1014 2 largefiles updated, 0 removed
1014 2 largefiles updated, 0 removed
1015 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1015 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1016 8 additional largefiles cached
1016 8 additional largefiles cached
1017 $ hg -R a-clone0 sum
1017 $ hg -R a-clone0 sum
1018 parent: 0:30d30fe6a5be
1018 parent: 0:30d30fe6a5be
1019 add files
1019 add files
1020 branch: default
1020 branch: default
1021 commit: (clean)
1021 commit: (clean)
1022 update: 7 new changesets (update)
1022 update: 7 new changesets (update)
1023 phases: 8 draft
1023 phases: 8 draft
1024
1024
1025 $ rm "${USERCACHE}"/*
1025 $ rm "${USERCACHE}"/*
1026 $ hg clone --all-largefiles -u 1 a a-clone1
1026 $ hg clone --all-largefiles -u 1 a a-clone1
1027 updating to branch default
1027 updating to branch default
1028 getting changed largefiles
1028 getting changed largefiles
1029 2 largefiles updated, 0 removed
1029 2 largefiles updated, 0 removed
1030 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1030 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1031 8 additional largefiles cached
1031 8 additional largefiles cached
1032 $ hg -R a-clone1 verify --large --lfa --lfc
1032 $ hg -R a-clone1 verify --large --lfa --lfc
1033 checking changesets
1033 checking changesets
1034 checking manifests
1034 checking manifests
1035 crosschecking files in changesets and manifests
1035 crosschecking files in changesets and manifests
1036 checking files
1036 checking files
1037 checked 8 changesets with 24 changes to 10 files
1037 checked 8 changesets with 24 changes to 10 files
1038 searching 8 changesets for largefiles
1038 searching 8 changesets for largefiles
1039 verified contents of 13 revisions of 6 largefiles
1039 verified contents of 13 revisions of 6 largefiles
1040 $ hg -R a-clone1 sum
1040 $ hg -R a-clone1 sum
1041 parent: 1:ce8896473775
1041 parent: 1:ce8896473775
1042 edit files
1042 edit files
1043 branch: default
1043 branch: default
1044 commit: (clean)
1044 commit: (clean)
1045 update: 6 new changesets (update)
1045 update: 6 new changesets (update)
1046 phases: 8 draft
1046 phases: 8 draft
1047
1047
1048 $ rm "${USERCACHE}"/*
1048 $ rm "${USERCACHE}"/*
1049 $ hg clone --all-largefiles -U a a-clone-u
1049 $ hg clone --all-largefiles -U a a-clone-u
1050 10 additional largefiles cached
1050 10 additional largefiles cached
1051 $ hg -R a-clone-u sum
1051 $ hg -R a-clone-u sum
1052 parent: -1:000000000000 (no revision checked out)
1052 parent: -1:000000000000 (no revision checked out)
1053 branch: default
1053 branch: default
1054 commit: (clean)
1054 commit: (clean)
1055 update: 8 new changesets (update)
1055 update: 8 new changesets (update)
1056 phases: 8 draft
1056 phases: 8 draft
1057
1057
1058 Show computed destination directory:
1058 Show computed destination directory:
1059
1059
1060 $ mkdir xyz
1060 $ mkdir xyz
1061 $ cd xyz
1061 $ cd xyz
1062 $ hg clone ../a
1062 $ hg clone ../a
1063 destination directory: a
1063 destination directory: a
1064 updating to branch default
1064 updating to branch default
1065 getting changed largefiles
1065 getting changed largefiles
1066 3 largefiles updated, 0 removed
1066 3 largefiles updated, 0 removed
1067 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1067 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1068 $ cd ..
1068 $ cd ..
1069
1069
1070 Clone URL without path:
1070 Clone URL without path:
1071
1071
1072 $ hg clone file://
1072 $ hg clone file://
1073 abort: repository / not found!
1073 abort: repository / not found!
1074 [255]
1074 [255]
1075
1075
1076 Ensure base clone command argument validation
1076 Ensure base clone command argument validation
1077
1077
1078 $ hg clone -U -u 0 a a-clone-failure
1078 $ hg clone -U -u 0 a a-clone-failure
1079 abort: cannot specify both --noupdate and --updaterev
1079 abort: cannot specify both --noupdate and --updaterev
1080 [10]
1080 [10]
1081
1081
1082 $ hg clone --all-largefiles a ssh://localhost/a
1082 $ hg clone --all-largefiles a ssh://localhost/a
1083 abort: --all-largefiles is incompatible with non-local destination ssh://localhost/a
1083 abort: --all-largefiles is incompatible with non-local destination ssh://localhost/a
1084 [255]
1084 [255]
1085
1085
1086 Test pulling with --all-largefiles flag. Also test that the largefiles are
1086 Test pulling with --all-largefiles flag. Also test that the largefiles are
1087 downloaded from 'default' instead of 'default-push' when no source is specified
1087 downloaded from 'default' instead of 'default-push' when no source is specified
1088 (issue3584)
1088 (issue3584)
1089
1089
1090 $ rm -Rf a-backup
1090 $ rm -Rf a-backup
1091 $ hg clone -r 1 a a-backup
1091 $ hg clone -r 1 a a-backup
1092 adding changesets
1092 adding changesets
1093 adding manifests
1093 adding manifests
1094 adding file changes
1094 adding file changes
1095 added 2 changesets with 8 changes to 4 files
1095 added 2 changesets with 8 changes to 4 files
1096 new changesets 30d30fe6a5be:ce8896473775 (2 drafts)
1096 new changesets 30d30fe6a5be:ce8896473775 (2 drafts)
1097 updating to branch default
1097 updating to branch default
1098 getting changed largefiles
1098 getting changed largefiles
1099 2 largefiles updated, 0 removed
1099 2 largefiles updated, 0 removed
1100 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1100 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1101 $ rm "${USERCACHE}"/*
1101 $ rm "${USERCACHE}"/*
1102 $ cd a-backup
1102 $ cd a-backup
1103 $ hg pull --all-largefiles --config paths.default-push=bogus/path
1103 $ hg pull --all-largefiles --config paths.default-push=bogus/path
1104 pulling from $TESTTMP/a
1104 pulling from $TESTTMP/a
1105 searching for changes
1105 searching for changes
1106 adding changesets
1106 adding changesets
1107 adding manifests
1107 adding manifests
1108 adding file changes
1108 adding file changes
1109 added 6 changesets with 16 changes to 8 files
1109 added 6 changesets with 16 changes to 8 files
1110 new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
1110 new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
1111 (run 'hg update' to get a working copy)
1111 (run 'hg update' to get a working copy)
1112 6 largefiles cached
1112 6 largefiles cached
1113
1113
1114 redo pull with --lfrev and check it pulls largefiles for the right revs
1114 redo pull with --lfrev and check it pulls largefiles for the right revs
1115
1115
1116 $ hg rollback
1116 $ hg rollback
1117 repository tip rolled back to revision 1 (undo pull)
1117 repository tip rolled back to revision 1 (undo pull)
1118 $ hg pull -v --lfrev 'heads(pulled())+min(pulled())'
1118 $ hg pull -v --lfrev 'heads(pulled())+min(pulled())'
1119 pulling from $TESTTMP/a
1119 pulling from $TESTTMP/a
1120 searching for changes
1120 searching for changes
1121 all local changesets known remotely
1121 all local changesets known remotely
1122 6 changesets found
1122 6 changesets found
1123 uncompressed size of bundle content:
1123 uncompressed size of bundle content:
1124 1389 (changelog)
1124 1389 (changelog)
1125 1599 (manifests)
1125 1599 (manifests)
1126 254 .hglf/large1
1126 254 .hglf/large1
1127 564 .hglf/large3
1127 564 .hglf/large3
1128 572 .hglf/sub/large4
1128 572 .hglf/sub/large4
1129 182 .hglf/sub2/large6
1129 182 .hglf/sub2/large6
1130 182 .hglf/sub2/large7
1130 182 .hglf/sub2/large7
1131 212 normal1
1131 212 normal1
1132 457 normal3
1132 457 normal3
1133 465 sub/normal4
1133 465 sub/normal4
1134 adding changesets
1134 adding changesets
1135 adding manifests
1135 adding manifests
1136 adding file changes
1136 adding file changes
1137 added 6 changesets with 16 changes to 8 files
1137 added 6 changesets with 16 changes to 8 files
1138 new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
1138 new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
1139 calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
1139 calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
1140 (run 'hg update' to get a working copy)
1140 (run 'hg update' to get a working copy)
1141 pulling largefiles for revision 7
1141 pulling largefiles for revision 7
1142 found 971fb41e78fea4f8e0ba5244784239371cb00591 in store
1142 found 971fb41e78fea4f8e0ba5244784239371cb00591 in store
1143 found 0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30 in store
1143 found 0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30 in store
1144 found bb3151689acb10f0c3125c560d5e63df914bc1af in store
1144 found bb3151689acb10f0c3125c560d5e63df914bc1af in store
1145 pulling largefiles for revision 2
1145 pulling largefiles for revision 2
1146 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1146 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1147 0 largefiles cached
1147 0 largefiles cached
1148
1148
1149 lfpull
1149 lfpull
1150
1150
1151 $ hg lfpull -r : --config largefiles.usercache=usercache-lfpull
1151 $ hg lfpull -r : --config largefiles.usercache=usercache-lfpull
1152 2 largefiles cached
1152 2 largefiles cached
1153 $ hg lfpull -v -r 4+2 --config largefiles.usercache=usercache-lfpull
1153 $ hg lfpull -v -r 4+2 --config largefiles.usercache=usercache-lfpull
1154 pulling largefiles for revision 4
1154 pulling largefiles for revision 4
1155 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1155 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1156 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1156 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1157 pulling largefiles for revision 2
1157 pulling largefiles for revision 2
1158 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1158 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1159 0 largefiles cached
1159 0 largefiles cached
1160
1160
1161 $ ls usercache-lfpull/* | sort
1161 $ ls usercache-lfpull/* | sort
1162 usercache-lfpull/1deebade43c8c498a3c8daddac0244dc55d1331d
1162 usercache-lfpull/1deebade43c8c498a3c8daddac0244dc55d1331d
1163 usercache-lfpull/4669e532d5b2c093a78eca010077e708a071bb64
1163 usercache-lfpull/4669e532d5b2c093a78eca010077e708a071bb64
1164
1164
1165 $ cd ..
1165 $ cd ..
1166
1166
1167 Rebasing between two repositories does not revert largefiles to old
1167 Rebasing between two repositories does not revert largefiles to old
1168 revisions (this was a very bad bug that took a lot of work to fix).
1168 revisions (this was a very bad bug that took a lot of work to fix).
1169
1169
1170 $ hg clone a d
1170 $ hg clone a d
1171 updating to branch default
1171 updating to branch default
1172 getting changed largefiles
1172 getting changed largefiles
1173 3 largefiles updated, 0 removed
1173 3 largefiles updated, 0 removed
1174 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1174 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1175 $ cd b
1175 $ cd b
1176 $ echo large4-modified > sub/large4
1176 $ echo large4-modified > sub/large4
1177 $ echo normal3-modified > normal3
1177 $ echo normal3-modified > normal3
1178 $ hg commit -m "modify normal file and largefile in repo b"
1178 $ hg commit -m "modify normal file and largefile in repo b"
1179 Invoking status precommit hook
1179 Invoking status precommit hook
1180 M normal3
1180 M normal3
1181 M sub/large4
1181 M sub/large4
1182 $ cd ../d
1182 $ cd ../d
1183 $ echo large6-modified > sub2/large6
1183 $ echo large6-modified > sub2/large6
1184 $ echo normal4-modified > sub/normal4
1184 $ echo normal4-modified > sub/normal4
1185 $ hg commit -m "modify normal file largefile in repo d"
1185 $ hg commit -m "modify normal file largefile in repo d"
1186 Invoking status precommit hook
1186 Invoking status precommit hook
1187 M sub/normal4
1187 M sub/normal4
1188 M sub2/large6
1188 M sub2/large6
1189 $ cd ..
1189 $ cd ..
1190 $ hg clone d e
1190 $ hg clone d e
1191 updating to branch default
1191 updating to branch default
1192 getting changed largefiles
1192 getting changed largefiles
1193 3 largefiles updated, 0 removed
1193 3 largefiles updated, 0 removed
1194 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1194 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1195 $ cd d
1195 $ cd d
1196
1196
1197 More rebase testing, but also test that the largefiles are downloaded from
1197 More rebase testing, but also test that the largefiles are downloaded from
1198 'default-push' when no source is specified (issue3584). (The largefile from the
1198 'default-push' when no source is specified (issue3584). (The largefile from the
1199 pulled revision is however not downloaded but found in the local cache.)
1199 pulled revision is however not downloaded but found in the local cache.)
1200 Largefiles are fetched for the new pulled revision, not for existing revisions,
1200 Largefiles are fetched for the new pulled revision, not for existing revisions,
1201 rebased or not.
1201 rebased or not.
1202
1202
1203 $ [ ! -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1203 $ [ ! -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1204 $ hg pull --rebase --all-largefiles --config paths.default-push=bogus/path --config paths.default=../b
1204 $ hg pull --rebase --all-largefiles --config paths.default-push=bogus/path --config paths.default=../b
1205 pulling from $TESTTMP/b
1205 pulling from $TESTTMP/b
1206 searching for changes
1206 searching for changes
1207 adding changesets
1207 adding changesets
1208 adding manifests
1208 adding manifests
1209 adding file changes
1209 adding file changes
1210 added 1 changesets with 2 changes to 2 files (+1 heads)
1210 added 1 changesets with 2 changes to 2 files (+1 heads)
1211 new changesets a381d2c8c80e (1 drafts)
1211 new changesets a381d2c8c80e (1 drafts)
1212 0 largefiles cached
1212 0 largefiles cached
1213 rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
1213 rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
1214 Invoking status precommit hook
1214 Invoking status precommit hook
1215 M sub/normal4
1215 M sub/normal4
1216 M sub2/large6
1216 M sub2/large6
1217 saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-dd1d9f80-rebase.hg
1217 saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-dd1d9f80-rebase.hg
1218 $ [ -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1218 $ [ -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1219 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1219 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1220 9:598410d3eb9a modify normal file largefile in repo d
1220 9:598410d3eb9a modify normal file largefile in repo d
1221 8:a381d2c8c80e modify normal file and largefile in repo b
1221 8:a381d2c8c80e modify normal file and largefile in repo b
1222 7:daea875e9014 add/edit more largefiles
1222 7:daea875e9014 add/edit more largefiles
1223 6:4355d653f84f edit files yet again
1223 6:4355d653f84f edit files yet again
1224 5:9d5af5072dbd edit files again
1224 5:9d5af5072dbd edit files again
1225 4:74c02385b94c move files
1225 4:74c02385b94c move files
1226 3:9e8fbc4bce62 copy files
1226 3:9e8fbc4bce62 copy files
1227 2:51a0ae4d5864 remove files
1227 2:51a0ae4d5864 remove files
1228 1:ce8896473775 edit files
1228 1:ce8896473775 edit files
1229 0:30d30fe6a5be add files
1229 0:30d30fe6a5be add files
1230 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
1230 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
1231 @ 9:598410d3eb9a modify normal file largefile in repo d
1231 @ 9:598410d3eb9a modify normal file largefile in repo d
1232 |
1232 |
1233 o 8:a381d2c8c80e modify normal file and largefile in repo b
1233 o 8:a381d2c8c80e modify normal file and largefile in repo b
1234 |
1234 |
1235 o 7:daea875e9014 add/edit more largefiles
1235 o 7:daea875e9014 add/edit more largefiles
1236 |
1236 |
1237 o 6:4355d653f84f edit files yet again
1237 o 6:4355d653f84f edit files yet again
1238 |
1238 |
1239 o 5:9d5af5072dbd edit files again
1239 o 5:9d5af5072dbd edit files again
1240 |
1240 |
1241 o 4:74c02385b94c move files
1241 o 4:74c02385b94c move files
1242 |
1242 |
1243 o 3:9e8fbc4bce62 copy files
1243 o 3:9e8fbc4bce62 copy files
1244 |
1244 |
1245 o 2:51a0ae4d5864 remove files
1245 o 2:51a0ae4d5864 remove files
1246 |
1246 |
1247 o 1:ce8896473775 edit files
1247 o 1:ce8896473775 edit files
1248 |
1248 |
1249 o 0:30d30fe6a5be add files
1249 o 0:30d30fe6a5be add files
1250
1250
1251 $ cat normal3
1251 $ cat normal3
1252 normal3-modified
1252 normal3-modified
1253 $ cat sub/normal4
1253 $ cat sub/normal4
1254 normal4-modified
1254 normal4-modified
1255 $ cat sub/large4
1255 $ cat sub/large4
1256 large4-modified
1256 large4-modified
1257 $ cat sub2/large6
1257 $ cat sub2/large6
1258 large6-modified
1258 large6-modified
1259 $ cat sub2/large7
1259 $ cat sub2/large7
1260 large7
1260 large7
1261 $ cd ../e
1261 $ cd ../e
1262 $ hg pull ../b
1262 $ hg pull ../b
1263 pulling from ../b
1263 pulling from ../b
1264 searching for changes
1264 searching for changes
1265 adding changesets
1265 adding changesets
1266 adding manifests
1266 adding manifests
1267 adding file changes
1267 adding file changes
1268 added 1 changesets with 2 changes to 2 files (+1 heads)
1268 added 1 changesets with 2 changes to 2 files (+1 heads)
1269 new changesets a381d2c8c80e (1 drafts)
1269 new changesets a381d2c8c80e (1 drafts)
1270 (run 'hg heads' to see heads, 'hg merge' to merge)
1270 (run 'hg heads' to see heads, 'hg merge' to merge)
1271 $ hg rebase
1271 $ hg rebase
1272 rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
1272 rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
1273 Invoking status precommit hook
1273 Invoking status precommit hook
1274 M sub/normal4
1274 M sub/normal4
1275 M sub2/large6
1275 M sub2/large6
1276 saved backup bundle to $TESTTMP/e/.hg/strip-backup/f574fb32bb45-dd1d9f80-rebase.hg
1276 saved backup bundle to $TESTTMP/e/.hg/strip-backup/f574fb32bb45-dd1d9f80-rebase.hg
1277 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1277 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1278 9:598410d3eb9a modify normal file largefile in repo d
1278 9:598410d3eb9a modify normal file largefile in repo d
1279 8:a381d2c8c80e modify normal file and largefile in repo b
1279 8:a381d2c8c80e modify normal file and largefile in repo b
1280 7:daea875e9014 add/edit more largefiles
1280 7:daea875e9014 add/edit more largefiles
1281 6:4355d653f84f edit files yet again
1281 6:4355d653f84f edit files yet again
1282 5:9d5af5072dbd edit files again
1282 5:9d5af5072dbd edit files again
1283 4:74c02385b94c move files
1283 4:74c02385b94c move files
1284 3:9e8fbc4bce62 copy files
1284 3:9e8fbc4bce62 copy files
1285 2:51a0ae4d5864 remove files
1285 2:51a0ae4d5864 remove files
1286 1:ce8896473775 edit files
1286 1:ce8896473775 edit files
1287 0:30d30fe6a5be add files
1287 0:30d30fe6a5be add files
1288 $ cat normal3
1288 $ cat normal3
1289 normal3-modified
1289 normal3-modified
1290 $ cat sub/normal4
1290 $ cat sub/normal4
1291 normal4-modified
1291 normal4-modified
1292 $ cat sub/large4
1292 $ cat sub/large4
1293 large4-modified
1293 large4-modified
1294 $ cat sub2/large6
1294 $ cat sub2/large6
1295 large6-modified
1295 large6-modified
1296 $ cat sub2/large7
1296 $ cat sub2/large7
1297 large7
1297 large7
1298
1298
1299 Log on largefiles
1299 Log on largefiles
1300
1300
1301 - same output
1301 - same output
1302 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1302 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1303 8:a381d2c8c80e modify normal file and largefile in repo b
1303 8:a381d2c8c80e modify normal file and largefile in repo b
1304 6:4355d653f84f edit files yet again
1304 6:4355d653f84f edit files yet again
1305 5:9d5af5072dbd edit files again
1305 5:9d5af5072dbd edit files again
1306 4:74c02385b94c move files
1306 4:74c02385b94c move files
1307 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1307 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1308 o 8:a381d2c8c80e modify normal file and largefile in repo b
1308 o 8:a381d2c8c80e modify normal file and largefile in repo b
1309 :
1309 :
1310 o 6:4355d653f84f edit files yet again
1310 o 6:4355d653f84f edit files yet again
1311 |
1311 |
1312 o 5:9d5af5072dbd edit files again
1312 o 5:9d5af5072dbd edit files again
1313 |
1313 |
1314 o 4:74c02385b94c move files
1314 o 4:74c02385b94c move files
1315 |
1315 |
1316 ~
1316 ~
1317 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub/large4
1317 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub/large4
1318 8:a381d2c8c80e modify normal file and largefile in repo b
1318 8:a381d2c8c80e modify normal file and largefile in repo b
1319 6:4355d653f84f edit files yet again
1319 6:4355d653f84f edit files yet again
1320 5:9d5af5072dbd edit files again
1320 5:9d5af5072dbd edit files again
1321 4:74c02385b94c move files
1321 4:74c02385b94c move files
1322 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1322 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1323 o 8:a381d2c8c80e modify normal file and largefile in repo b
1323 o 8:a381d2c8c80e modify normal file and largefile in repo b
1324 :
1324 :
1325 o 6:4355d653f84f edit files yet again
1325 o 6:4355d653f84f edit files yet again
1326 |
1326 |
1327 o 5:9d5af5072dbd edit files again
1327 o 5:9d5af5072dbd edit files again
1328 |
1328 |
1329 o 4:74c02385b94c move files
1329 o 4:74c02385b94c move files
1330 |
1330 |
1331 ~
1331 ~
1332
1332
1333 - .hglf only matches largefiles, without .hglf it matches 9 bco sub/normal
1333 - .hglf only matches largefiles, without .hglf it matches 9 bco sub/normal
1334 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1334 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1335 8:a381d2c8c80e modify normal file and largefile in repo b
1335 8:a381d2c8c80e modify normal file and largefile in repo b
1336 6:4355d653f84f edit files yet again
1336 6:4355d653f84f edit files yet again
1337 5:9d5af5072dbd edit files again
1337 5:9d5af5072dbd edit files again
1338 4:74c02385b94c move files
1338 4:74c02385b94c move files
1339 1:ce8896473775 edit files
1339 1:ce8896473775 edit files
1340 0:30d30fe6a5be add files
1340 0:30d30fe6a5be add files
1341 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1341 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1342 o 8:a381d2c8c80e modify normal file and largefile in repo b
1342 o 8:a381d2c8c80e modify normal file and largefile in repo b
1343 :
1343 :
1344 o 6:4355d653f84f edit files yet again
1344 o 6:4355d653f84f edit files yet again
1345 |
1345 |
1346 o 5:9d5af5072dbd edit files again
1346 o 5:9d5af5072dbd edit files again
1347 |
1347 |
1348 o 4:74c02385b94c move files
1348 o 4:74c02385b94c move files
1349 :
1349 :
1350 o 1:ce8896473775 edit files
1350 o 1:ce8896473775 edit files
1351 |
1351 |
1352 o 0:30d30fe6a5be add files
1352 o 0:30d30fe6a5be add files
1353
1353
1354 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub
1354 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub
1355 9:598410d3eb9a modify normal file largefile in repo d
1355 9:598410d3eb9a modify normal file largefile in repo d
1356 8:a381d2c8c80e modify normal file and largefile in repo b
1356 8:a381d2c8c80e modify normal file and largefile in repo b
1357 6:4355d653f84f edit files yet again
1357 6:4355d653f84f edit files yet again
1358 5:9d5af5072dbd edit files again
1358 5:9d5af5072dbd edit files again
1359 4:74c02385b94c move files
1359 4:74c02385b94c move files
1360 1:ce8896473775 edit files
1360 1:ce8896473775 edit files
1361 0:30d30fe6a5be add files
1361 0:30d30fe6a5be add files
1362 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' sub
1362 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' sub
1363 @ 9:598410d3eb9a modify normal file largefile in repo d
1363 @ 9:598410d3eb9a modify normal file largefile in repo d
1364 |
1364 |
1365 o 8:a381d2c8c80e modify normal file and largefile in repo b
1365 o 8:a381d2c8c80e modify normal file and largefile in repo b
1366 :
1366 :
1367 o 6:4355d653f84f edit files yet again
1367 o 6:4355d653f84f edit files yet again
1368 |
1368 |
1369 o 5:9d5af5072dbd edit files again
1369 o 5:9d5af5072dbd edit files again
1370 |
1370 |
1371 o 4:74c02385b94c move files
1371 o 4:74c02385b94c move files
1372 :
1372 :
1373 o 1:ce8896473775 edit files
1373 o 1:ce8896473775 edit files
1374 |
1374 |
1375 o 0:30d30fe6a5be add files
1375 o 0:30d30fe6a5be add files
1376
1376
1377 - globbing gives same result
1377 - globbing gives same result
1378 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1378 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1379 9:598410d3eb9a modify normal file largefile in repo d
1379 9:598410d3eb9a modify normal file largefile in repo d
1380 8:a381d2c8c80e modify normal file and largefile in repo b
1380 8:a381d2c8c80e modify normal file and largefile in repo b
1381 6:4355d653f84f edit files yet again
1381 6:4355d653f84f edit files yet again
1382 5:9d5af5072dbd edit files again
1382 5:9d5af5072dbd edit files again
1383 4:74c02385b94c move files
1383 4:74c02385b94c move files
1384 1:ce8896473775 edit files
1384 1:ce8896473775 edit files
1385 0:30d30fe6a5be add files
1385 0:30d30fe6a5be add files
1386 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1386 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1387 @ 9:598410d3eb9a modify normal file largefile in repo d
1387 @ 9:598410d3eb9a modify normal file largefile in repo d
1388 |
1388 |
1389 o 8:a381d2c8c80e modify normal file and largefile in repo b
1389 o 8:a381d2c8c80e modify normal file and largefile in repo b
1390 :
1390 :
1391 o 6:4355d653f84f edit files yet again
1391 o 6:4355d653f84f edit files yet again
1392 |
1392 |
1393 o 5:9d5af5072dbd edit files again
1393 o 5:9d5af5072dbd edit files again
1394 |
1394 |
1395 o 4:74c02385b94c move files
1395 o 4:74c02385b94c move files
1396 :
1396 :
1397 o 1:ce8896473775 edit files
1397 o 1:ce8896473775 edit files
1398 |
1398 |
1399 o 0:30d30fe6a5be add files
1399 o 0:30d30fe6a5be add files
1400
1400
1401 Rollback on largefiles.
1401 Rollback on largefiles.
1402
1402
1403 $ echo large4-modified-again > sub/large4
1403 $ echo large4-modified-again > sub/large4
1404 $ hg commit -m "Modify large4 again"
1404 $ hg commit -m "Modify large4 again"
1405 Invoking status precommit hook
1405 Invoking status precommit hook
1406 M sub/large4
1406 M sub/large4
1407 $ hg rollback
1407 $ hg rollback
1408 repository tip rolled back to revision 9 (undo commit)
1408 repository tip rolled back to revision 9 (undo commit)
1409 working directory now based on revision 9
1409 working directory now based on revision 9
1410 $ hg st
1410 $ hg st
1411 M sub/large4
1411 M sub/large4
1412 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1412 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1413 9:598410d3eb9a modify normal file largefile in repo d
1413 9:598410d3eb9a modify normal file largefile in repo d
1414 8:a381d2c8c80e modify normal file and largefile in repo b
1414 8:a381d2c8c80e modify normal file and largefile in repo b
1415 7:daea875e9014 add/edit more largefiles
1415 7:daea875e9014 add/edit more largefiles
1416 6:4355d653f84f edit files yet again
1416 6:4355d653f84f edit files yet again
1417 5:9d5af5072dbd edit files again
1417 5:9d5af5072dbd edit files again
1418 4:74c02385b94c move files
1418 4:74c02385b94c move files
1419 3:9e8fbc4bce62 copy files
1419 3:9e8fbc4bce62 copy files
1420 2:51a0ae4d5864 remove files
1420 2:51a0ae4d5864 remove files
1421 1:ce8896473775 edit files
1421 1:ce8896473775 edit files
1422 0:30d30fe6a5be add files
1422 0:30d30fe6a5be add files
1423 $ cat sub/large4
1423 $ cat sub/large4
1424 large4-modified-again
1424 large4-modified-again
1425
1425
1426 "update --check" refuses to update with uncommitted changes.
1426 "update --check" refuses to update with uncommitted changes.
1427 $ hg update --check 8
1427 $ hg update --check 8
1428 abort: uncommitted changes
1428 abort: uncommitted changes
1429 [255]
1429 [255]
1430
1430
1431 "update --clean" leaves correct largefiles in working copy, even when there is
1431 "update --clean" leaves correct largefiles in working copy, even when there is
1432 .orig files from revert in .hglf.
1432 .orig files from revert in .hglf.
1433
1433
1434 $ echo mistake > sub2/large7
1434 $ echo mistake > sub2/large7
1435 $ hg revert sub2/large7
1435 $ hg revert sub2/large7
1436 $ cat sub2/large7
1436 $ cat sub2/large7
1437 large7
1437 large7
1438 $ cat sub2/large7.orig
1438 $ cat sub2/large7.orig
1439 mistake
1439 mistake
1440 $ test ! -f .hglf/sub2/large7.orig
1440 $ test ! -f .hglf/sub2/large7.orig
1441
1441
1442 $ hg -q update --clean -r null
1442 $ hg -q update --clean -r null
1443 $ hg update --clean
1443 $ hg update --clean
1444 getting changed largefiles
1444 getting changed largefiles
1445 3 largefiles updated, 0 removed
1445 3 largefiles updated, 0 removed
1446 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1446 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1447 $ cat normal3
1447 $ cat normal3
1448 normal3-modified
1448 normal3-modified
1449 $ cat sub/normal4
1449 $ cat sub/normal4
1450 normal4-modified
1450 normal4-modified
1451 $ cat sub/large4
1451 $ cat sub/large4
1452 large4-modified
1452 large4-modified
1453 $ cat sub2/large6
1453 $ cat sub2/large6
1454 large6-modified
1454 large6-modified
1455 $ cat sub2/large7
1455 $ cat sub2/large7
1456 large7
1456 large7
1457 $ cat sub2/large7.orig
1457 $ cat sub2/large7.orig
1458 mistake
1458 mistake
1459 $ test ! -f .hglf/sub2/large7.orig
1459 $ test ! -f .hglf/sub2/large7.orig
1460
1460
1461 verify that largefile .orig file no longer is overwritten on every update -C:
1461 verify that largefile .orig file no longer is overwritten on every update -C:
1462 $ hg update --clean
1462 $ hg update --clean
1463 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1463 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1464 $ cat sub2/large7.orig
1464 $ cat sub2/large7.orig
1465 mistake
1465 mistake
1466 $ rm sub2/large7.orig
1466 $ rm sub2/large7.orig
1467
1467
1468 Now "update check" is happy.
1468 Now "update check" is happy.
1469 $ hg update --check 8
1469 $ hg update --check 8
1470 getting changed largefiles
1470 getting changed largefiles
1471 1 largefiles updated, 0 removed
1471 1 largefiles updated, 0 removed
1472 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1472 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1473 $ hg update --check
1473 $ hg update --check
1474 getting changed largefiles
1474 getting changed largefiles
1475 1 largefiles updated, 0 removed
1475 1 largefiles updated, 0 removed
1476 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1476 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1477
1477
1478 Test removing empty largefiles directories on update
1478 Test removing empty largefiles directories on update
1479 $ test -d sub2 && echo "sub2 exists"
1479 $ test -d sub2 && echo "sub2 exists"
1480 sub2 exists
1480 sub2 exists
1481 $ hg update -q null
1481 $ hg update -q null
1482 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1482 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1483 [1]
1483 [1]
1484 $ hg update -q
1484 $ hg update -q
1485
1485
1486 Test hg remove removes empty largefiles directories
1486 Test hg remove removes empty largefiles directories
1487 $ test -d sub2 && echo "sub2 exists"
1487 $ test -d sub2 && echo "sub2 exists"
1488 sub2 exists
1488 sub2 exists
1489 $ hg remove sub2/*
1489 $ hg remove sub2/*
1490 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1490 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1491 [1]
1491 [1]
1492 $ hg revert sub2/large6 sub2/large7
1492 $ hg revert sub2/large6 sub2/large7
1493
1493
1494 "revert" works on largefiles (and normal files too).
1494 "revert" works on largefiles (and normal files too).
1495 $ echo hack3 >> normal3
1495 $ echo hack3 >> normal3
1496 $ echo hack4 >> sub/normal4
1496 $ echo hack4 >> sub/normal4
1497 $ echo hack4 >> sub/large4
1497 $ echo hack4 >> sub/large4
1498 $ rm sub2/large6
1498 $ rm sub2/large6
1499 $ hg revert sub2/large6
1499 $ hg revert sub2/large6
1500 $ hg rm sub2/large6
1500 $ hg rm sub2/large6
1501 $ echo new >> sub2/large8
1501 $ echo new >> sub2/large8
1502 $ hg add --large sub2/large8
1502 $ hg add --large sub2/large8
1503 # XXX we don't really want to report that we're reverting the standin;
1503 # XXX we don't really want to report that we're reverting the standin;
1504 # that's just an implementation detail. But I don't see an obvious fix. ;-(
1504 # that's just an implementation detail. But I don't see an obvious fix. ;-(
1505 $ hg revert sub
1505 $ hg revert sub
1506 reverting .hglf/sub/large4
1506 reverting .hglf/sub/large4
1507 reverting sub/normal4
1507 reverting sub/normal4
1508 $ hg status
1508 $ hg status
1509 M normal3
1509 M normal3
1510 A sub2/large8
1510 A sub2/large8
1511 R sub2/large6
1511 R sub2/large6
1512 ? sub/large4.orig
1512 ? sub/large4.orig
1513 ? sub/normal4.orig
1513 ? sub/normal4.orig
1514 $ cat sub/normal4
1514 $ cat sub/normal4
1515 normal4-modified
1515 normal4-modified
1516 $ cat sub/large4
1516 $ cat sub/large4
1517 large4-modified
1517 large4-modified
1518 $ hg revert -a --no-backup
1518 $ hg revert -a --no-backup
1519 forgetting .hglf/sub2/large8
1519 forgetting .hglf/sub2/large8
1520 reverting normal3
1520 reverting normal3
1521 undeleting .hglf/sub2/large6
1521 undeleting .hglf/sub2/large6
1522 $ hg status
1522 $ hg status
1523 ? sub/large4.orig
1523 ? sub/large4.orig
1524 ? sub/normal4.orig
1524 ? sub/normal4.orig
1525 ? sub2/large8
1525 ? sub2/large8
1526 $ cat normal3
1526 $ cat normal3
1527 normal3-modified
1527 normal3-modified
1528 $ cat sub2/large6
1528 $ cat sub2/large6
1529 large6-modified
1529 large6-modified
1530 $ rm sub/*.orig sub2/large8
1530 $ rm sub/*.orig sub2/large8
1531
1531
1532 revert some files to an older revision
1532 revert some files to an older revision
1533 $ hg revert --no-backup -r 8 sub2
1533 $ hg revert --no-backup -r 8 sub2
1534 reverting .hglf/sub2/large6
1534 reverting .hglf/sub2/large6
1535 $ cat sub2/large6
1535 $ cat sub2/large6
1536 large6
1536 large6
1537 $ hg revert --no-backup -C -r '.^' sub2
1537 $ hg revert --no-backup -C -r '.^' sub2
1538 $ hg revert --no-backup sub2
1538 $ hg revert --no-backup sub2
1539 reverting .hglf/sub2/large6
1539 reverting .hglf/sub2/large6
1540 $ hg status
1540 $ hg status
1541
1541
1542 "verify --large" actually verifies largefiles
1542 "verify --large" actually verifies largefiles
1543
1543
1544 - Where Do We Come From? What Are We? Where Are We Going?
1544 - Where Do We Come From? What Are We? Where Are We Going?
1545 $ pwd
1545 $ pwd
1546 $TESTTMP/e
1546 $TESTTMP/e
1547 $ hg paths
1547 $ hg paths
1548 default = $TESTTMP/d
1548 default = $TESTTMP/d
1549
1549
1550 $ hg verify --large
1550 $ hg verify --large
1551 checking changesets
1551 checking changesets
1552 checking manifests
1552 checking manifests
1553 crosschecking files in changesets and manifests
1553 crosschecking files in changesets and manifests
1554 checking files
1554 checking files
1555 checked 10 changesets with 28 changes to 10 files
1555 checked 10 changesets with 28 changes to 10 files
1556 searching 1 changesets for largefiles
1556 searching 1 changesets for largefiles
1557 verified existence of 3 revisions of 3 largefiles
1557 verified existence of 3 revisions of 3 largefiles
1558
1558
1559 - introduce missing blob in local store repo and remote store
1559 - introduce missing blob in local store repo and remote store
1560 and make sure that this is caught:
1560 and make sure that this is caught:
1561
1561
1562 $ mv $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 .
1562 $ mv $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 .
1563 $ rm .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1563 $ rm .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1564 $ hg verify --large
1564 $ hg verify --large
1565 checking changesets
1565 checking changesets
1566 checking manifests
1566 checking manifests
1567 crosschecking files in changesets and manifests
1567 crosschecking files in changesets and manifests
1568 checking files
1568 checking files
1569 checked 10 changesets with 28 changes to 10 files
1569 checked 10 changesets with 28 changes to 10 files
1570 searching 1 changesets for largefiles
1570 searching 1 changesets for largefiles
1571 changeset 9:598410d3eb9a: sub/large4 references missing $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1571 changeset 9:598410d3eb9a: sub/large4 references missing $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1572 verified existence of 3 revisions of 3 largefiles
1572 verified existence of 3 revisions of 3 largefiles
1573 [1]
1573 [1]
1574
1574
1575 - introduce corruption and make sure that it is caught when checking content:
1575 - introduce corruption and make sure that it is caught when checking content:
1576 $ echo '5 cents' > $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1576 $ echo '5 cents' > $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1577 $ hg verify -q --large --lfc
1577 $ hg verify -q --large --lfc
1578 changeset 9:598410d3eb9a: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1578 changeset 9:598410d3eb9a: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1579 [1]
1579 [1]
1580
1580
1581 - cleanup
1581 - cleanup
1582 $ cp e166e74c7303192238d60af5a9c4ce9bef0b7928 $TESTTMP/d/.hg/largefiles/
1582 $ cp e166e74c7303192238d60af5a9c4ce9bef0b7928 $TESTTMP/d/.hg/largefiles/
1583 $ mv e166e74c7303192238d60af5a9c4ce9bef0b7928 .hg/largefiles/
1583 $ mv e166e74c7303192238d60af5a9c4ce9bef0b7928 .hg/largefiles/
1584
1584
1585 - verifying all revisions will fail because we didn't clone all largefiles to d:
1585 - verifying all revisions will fail because we didn't clone all largefiles to d:
1586 $ echo 'T-shirt' > $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1586 $ echo 'T-shirt' > $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1587 $ hg verify -q --lfa --lfc
1587 $ hg verify -q --lfa --lfc
1588 changeset 0:30d30fe6a5be: large1 references missing $TESTTMP/d/.hg/largefiles/4669e532d5b2c093a78eca010077e708a071bb64
1588 changeset 0:30d30fe6a5be: large1 references missing $TESTTMP/d/.hg/largefiles/4669e532d5b2c093a78eca010077e708a071bb64
1589 changeset 0:30d30fe6a5be: sub/large2 references missing $TESTTMP/d/.hg/largefiles/1deebade43c8c498a3c8daddac0244dc55d1331d
1589 changeset 0:30d30fe6a5be: sub/large2 references missing $TESTTMP/d/.hg/largefiles/1deebade43c8c498a3c8daddac0244dc55d1331d
1590 changeset 1:ce8896473775: large1 references missing $TESTTMP/d/.hg/largefiles/5f78770c0e77ba4287ad6ef3071c9bf9c379742f
1590 changeset 1:ce8896473775: large1 references missing $TESTTMP/d/.hg/largefiles/5f78770c0e77ba4287ad6ef3071c9bf9c379742f
1591 changeset 1:ce8896473775: sub/large2 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1591 changeset 1:ce8896473775: sub/large2 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1592 changeset 3:9e8fbc4bce62: large1 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1592 changeset 3:9e8fbc4bce62: large1 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1593 changeset 4:74c02385b94c: large3 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1593 changeset 4:74c02385b94c: large3 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1594 changeset 4:74c02385b94c: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1594 changeset 4:74c02385b94c: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1595 changeset 5:9d5af5072dbd: large3 references missing $TESTTMP/d/.hg/largefiles/baaf12afde9d8d67f25dab6dced0d2bf77dba47c
1595 changeset 5:9d5af5072dbd: large3 references missing $TESTTMP/d/.hg/largefiles/baaf12afde9d8d67f25dab6dced0d2bf77dba47c
1596 changeset 5:9d5af5072dbd: sub/large4 references missing $TESTTMP/d/.hg/largefiles/aeb2210d19f02886dde00dac279729a48471e2f9
1596 changeset 5:9d5af5072dbd: sub/large4 references missing $TESTTMP/d/.hg/largefiles/aeb2210d19f02886dde00dac279729a48471e2f9
1597 changeset 6:4355d653f84f: large3 references missing $TESTTMP/d/.hg/largefiles/7838695e10da2bb75ac1156565f40a2595fa2fa0
1597 changeset 6:4355d653f84f: large3 references missing $TESTTMP/d/.hg/largefiles/7838695e10da2bb75ac1156565f40a2595fa2fa0
1598 [1]
1598 [1]
1599
1599
1600 - cleanup
1600 - cleanup
1601 $ rm $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1601 $ rm $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1602 $ rm -f .hglf/sub/*.orig
1602 $ rm -f .hglf/sub/*.orig
1603
1603
1604 Update to revision with missing largefile - and make sure it really is missing
1604 Update to revision with missing largefile - and make sure it really is missing
1605
1605
1606 $ rm ${USERCACHE}/7838695e10da2bb75ac1156565f40a2595fa2fa0
1606 $ rm ${USERCACHE}/7838695e10da2bb75ac1156565f40a2595fa2fa0
1607 $ hg up -r 6
1607 $ hg up -r 6
1608 getting changed largefiles
1608 getting changed largefiles
1609 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1609 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1610 1 largefiles updated, 2 removed
1610 1 largefiles updated, 2 removed
1611 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
1611 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
1612 $ rm normal3
1612 $ rm normal3
1613 $ echo >> sub/normal4
1613 $ echo >> sub/normal4
1614 $ hg ci -m 'commit with missing files'
1614 $ hg ci -m 'commit with missing files'
1615 Invoking status precommit hook
1615 Invoking status precommit hook
1616 M sub/normal4
1616 M sub/normal4
1617 ! large3
1617 ! large3
1618 ! normal3
1618 ! normal3
1619 created new head
1619 created new head
1620 $ hg st
1620 $ hg st
1621 ! large3
1621 ! large3
1622 ! normal3
1622 ! normal3
1623 $ hg up -r.
1623 $ hg up -r.
1624 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1624 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1625 $ hg st
1625 $ hg st
1626 ! large3
1626 ! large3
1627 ! normal3
1627 ! normal3
1628 $ hg up -Cr.
1628 $ hg up -Cr.
1629 getting changed largefiles
1629 getting changed largefiles
1630 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1630 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1631 0 largefiles updated, 0 removed
1631 0 largefiles updated, 0 removed
1632 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1632 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1633 $ hg st
1633 $ hg st
1634 ! large3
1634 ! large3
1635 $ hg rollback
1635 $ hg rollback
1636 repository tip rolled back to revision 9 (undo commit)
1636 repository tip rolled back to revision 9 (undo commit)
1637 working directory now based on revision 6
1637 working directory now based on revision 6
1638
1638
1639 Merge with revision with missing largefile - and make sure it tries to fetch it.
1639 Merge with revision with missing largefile - and make sure it tries to fetch it.
1640
1640
1641 $ hg up -Cqr null
1641 $ hg up -Cqr null
1642 $ echo f > f
1642 $ echo f > f
1643 $ hg ci -Am branch
1643 $ hg ci -Am branch
1644 adding f
1644 adding f
1645 Invoking status precommit hook
1645 Invoking status precommit hook
1646 A f
1646 A f
1647 created new head
1647 created new head
1648 $ hg merge -r 6
1648 $ hg merge -r 6
1649 getting changed largefiles
1649 getting changed largefiles
1650 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1650 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1651 1 largefiles updated, 0 removed
1651 1 largefiles updated, 0 removed
1652 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1652 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1653 (branch merge, don't forget to commit)
1653 (branch merge, don't forget to commit)
1654
1654
1655 $ hg rollback -q
1655 $ hg rollback -q
1656 $ hg up -Cq
1656 $ hg up -Cq
1657
1657
1658 Pulling 0 revisions with --all-largefiles should not fetch for all revisions
1658 Pulling 0 revisions with --all-largefiles should not fetch for all revisions
1659
1659
1660 $ hg pull --all-largefiles
1660 $ hg pull --all-largefiles
1661 pulling from $TESTTMP/d
1661 pulling from $TESTTMP/d
1662 searching for changes
1662 searching for changes
1663 no changes found
1663 no changes found
1664
1664
1665 Merging does not revert to old versions of largefiles and also check
1665 Merging does not revert to old versions of largefiles and also check
1666 that merging after having pulled from a non-default remote works
1666 that merging after having pulled from a non-default remote works
1667 correctly.
1667 correctly.
1668
1668
1669 $ cd ..
1669 $ cd ..
1670 $ hg clone -r 7 e temp
1670 $ hg clone -r 7 e temp
1671 adding changesets
1671 adding changesets
1672 adding manifests
1672 adding manifests
1673 adding file changes
1673 adding file changes
1674 added 8 changesets with 24 changes to 10 files
1674 added 8 changesets with 24 changes to 10 files
1675 new changesets 30d30fe6a5be:daea875e9014 (8 drafts)
1675 new changesets 30d30fe6a5be:daea875e9014 (8 drafts)
1676 updating to branch default
1676 updating to branch default
1677 getting changed largefiles
1677 getting changed largefiles
1678 3 largefiles updated, 0 removed
1678 3 largefiles updated, 0 removed
1679 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1679 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1680 $ hg clone temp f
1680 $ hg clone temp f
1681 updating to branch default
1681 updating to branch default
1682 getting changed largefiles
1682 getting changed largefiles
1683 3 largefiles updated, 0 removed
1683 3 largefiles updated, 0 removed
1684 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1684 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1685 # Delete the largefiles in the largefiles system cache so that we have an
1685 # Delete the largefiles in the largefiles system cache so that we have an
1686 # opportunity to test that caching after a pull works.
1686 # opportunity to test that caching after a pull works.
1687 $ rm "${USERCACHE}"/*
1687 $ rm "${USERCACHE}"/*
1688 $ cd f
1688 $ cd f
1689 $ echo "large4-merge-test" > sub/large4
1689 $ echo "large4-merge-test" > sub/large4
1690 $ hg commit -m "Modify large4 to test merge"
1690 $ hg commit -m "Modify large4 to test merge"
1691 Invoking status precommit hook
1691 Invoking status precommit hook
1692 M sub/large4
1692 M sub/large4
1693 # Test --cache-largefiles flag
1693 # Test --cache-largefiles flag
1694 $ hg pull --lfrev 'heads(pulled())' ../e
1694 $ hg pull --lfrev 'heads(pulled())' ../e
1695 pulling from ../e
1695 pulling from ../e
1696 searching for changes
1696 searching for changes
1697 adding changesets
1697 adding changesets
1698 adding manifests
1698 adding manifests
1699 adding file changes
1699 adding file changes
1700 added 2 changesets with 4 changes to 4 files (+1 heads)
1700 added 2 changesets with 4 changes to 4 files (+1 heads)
1701 new changesets a381d2c8c80e:598410d3eb9a (2 drafts)
1701 new changesets a381d2c8c80e:598410d3eb9a (2 drafts)
1702 (run 'hg heads' to see heads, 'hg merge' to merge)
1702 (run 'hg heads' to see heads, 'hg merge' to merge)
1703 2 largefiles cached
1703 2 largefiles cached
1704 $ hg merge
1704 $ hg merge
1705 largefile sub/large4 has a merge conflict
1705 largefile sub/large4 has a merge conflict
1706 ancestor was 971fb41e78fea4f8e0ba5244784239371cb00591
1706 ancestor was 971fb41e78fea4f8e0ba5244784239371cb00591
1707 you can keep (l)ocal d846f26643bfa8ec210be40cc93cc6b7ff1128ea or take (o)ther e166e74c7303192238d60af5a9c4ce9bef0b7928.
1707 you can keep (l)ocal d846f26643bfa8ec210be40cc93cc6b7ff1128ea or take (o)ther e166e74c7303192238d60af5a9c4ce9bef0b7928.
1708 what do you want to do? l
1708 what do you want to do? l
1709 getting changed largefiles
1709 getting changed largefiles
1710 1 largefiles updated, 0 removed
1710 1 largefiles updated, 0 removed
1711 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
1711 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
1712 (branch merge, don't forget to commit)
1712 (branch merge, don't forget to commit)
1713 $ hg commit -m "Merge repos e and f"
1713 $ hg commit -m "Merge repos e and f"
1714 Invoking status precommit hook
1714 Invoking status precommit hook
1715 M normal3
1715 M normal3
1716 M sub/normal4
1716 M sub/normal4
1717 M sub2/large6
1717 M sub2/large6
1718 $ cat normal3
1718 $ cat normal3
1719 normal3-modified
1719 normal3-modified
1720 $ cat sub/normal4
1720 $ cat sub/normal4
1721 normal4-modified
1721 normal4-modified
1722 $ cat sub/large4
1722 $ cat sub/large4
1723 large4-merge-test
1723 large4-merge-test
1724 $ cat sub2/large6
1724 $ cat sub2/large6
1725 large6-modified
1725 large6-modified
1726 $ cat sub2/large7
1726 $ cat sub2/large7
1727 large7
1727 large7
1728
1728
1729 Test status after merging with a branch that introduces a new largefile:
1729 Test status after merging with a branch that introduces a new largefile:
1730
1730
1731 $ echo large > large
1731 $ echo large > large
1732 $ hg add --large large
1732 $ hg add --large large
1733 $ hg commit -m 'add largefile'
1733 $ hg commit -m 'add largefile'
1734 Invoking status precommit hook
1734 Invoking status precommit hook
1735 A large
1735 A large
1736 $ hg update -q ".^"
1736 $ hg update -q ".^"
1737 $ echo change >> normal3
1737 $ echo change >> normal3
1738 $ hg commit -m 'some change'
1738 $ hg commit -m 'some change'
1739 Invoking status precommit hook
1739 Invoking status precommit hook
1740 M normal3
1740 M normal3
1741 created new head
1741 created new head
1742 $ hg merge
1742 $ hg merge
1743 getting changed largefiles
1743 getting changed largefiles
1744 1 largefiles updated, 0 removed
1744 1 largefiles updated, 0 removed
1745 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1745 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1746 (branch merge, don't forget to commit)
1746 (branch merge, don't forget to commit)
1747 $ hg status
1747 $ hg status
1748 M large
1748 M large
1749
1749
1750 - make sure update of merge with removed largefiles fails as expected
1750 - make sure update of merge with removed largefiles fails as expected
1751 $ hg rm sub2/large6
1751 $ hg rm sub2/large6
1752 $ hg up -r.
1752 $ hg up -r.
1753 abort: outstanding uncommitted merge
1753 abort: outstanding uncommitted merge
1754 [255]
1754 [255]
1755
1755
1756 - revert should be able to revert files introduced in a pending merge
1756 - revert should be able to revert files introduced in a pending merge
1757 $ hg revert --all -r .
1757 $ hg revert --all -r .
1758 removing .hglf/large
1758 removing .hglf/large
1759 undeleting .hglf/sub2/large6
1759 undeleting .hglf/sub2/large6
1760
1760
1761 Test that a normal file and a largefile with the same name and path cannot
1761 Test that a normal file and a largefile with the same name and path cannot
1762 coexist.
1762 coexist.
1763
1763
1764 $ rm sub2/large7
1764 $ rm sub2/large7
1765 $ echo "largeasnormal" > sub2/large7
1765 $ echo "largeasnormal" > sub2/large7
1766 $ hg add sub2/large7
1766 $ hg add sub2/large7
1767 sub2/large7 already a largefile
1767 sub2/large7 already a largefile
1768
1768
1769 Test that transplanting a largefile change works correctly.
1769 Test that transplanting a largefile change works correctly.
1770
1770
1771 $ cd ..
1771 $ cd ..
1772 $ hg clone -r 8 d g
1772 $ hg clone -r 8 d g
1773 adding changesets
1773 adding changesets
1774 adding manifests
1774 adding manifests
1775 adding file changes
1775 adding file changes
1776 added 9 changesets with 26 changes to 10 files
1776 added 9 changesets with 26 changes to 10 files
1777 new changesets 30d30fe6a5be:a381d2c8c80e (9 drafts)
1777 new changesets 30d30fe6a5be:a381d2c8c80e (9 drafts)
1778 updating to branch default
1778 updating to branch default
1779 getting changed largefiles
1779 getting changed largefiles
1780 3 largefiles updated, 0 removed
1780 3 largefiles updated, 0 removed
1781 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1781 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1782 $ cd g
1782 $ cd g
1783 $ hg transplant -s ../d 598410d3eb9a
1783 $ hg transplant -s ../d 598410d3eb9a
1784 searching for changes
1784 searching for changes
1785 searching for changes
1785 searching for changes
1786 adding changesets
1786 adding changesets
1787 adding manifests
1787 adding manifests
1788 adding file changes
1788 adding file changes
1789 added 1 changesets with 2 changes to 2 files
1789 added 1 changesets with 2 changes to 2 files
1790 new changesets 598410d3eb9a (1 drafts)
1790 new changesets 598410d3eb9a (1 drafts)
1791 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1791 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1792 9:598410d3eb9a modify normal file largefile in repo d
1792 9:598410d3eb9a modify normal file largefile in repo d
1793 8:a381d2c8c80e modify normal file and largefile in repo b
1793 8:a381d2c8c80e modify normal file and largefile in repo b
1794 7:daea875e9014 add/edit more largefiles
1794 7:daea875e9014 add/edit more largefiles
1795 6:4355d653f84f edit files yet again
1795 6:4355d653f84f edit files yet again
1796 5:9d5af5072dbd edit files again
1796 5:9d5af5072dbd edit files again
1797 4:74c02385b94c move files
1797 4:74c02385b94c move files
1798 3:9e8fbc4bce62 copy files
1798 3:9e8fbc4bce62 copy files
1799 2:51a0ae4d5864 remove files
1799 2:51a0ae4d5864 remove files
1800 1:ce8896473775 edit files
1800 1:ce8896473775 edit files
1801 0:30d30fe6a5be add files
1801 0:30d30fe6a5be add files
1802 $ cat normal3
1802 $ cat normal3
1803 normal3-modified
1803 normal3-modified
1804 $ cat sub/normal4
1804 $ cat sub/normal4
1805 normal4-modified
1805 normal4-modified
1806 $ cat sub/large4
1806 $ cat sub/large4
1807 large4-modified
1807 large4-modified
1808 $ cat sub2/large6
1808 $ cat sub2/large6
1809 large6-modified
1809 large6-modified
1810 $ cat sub2/large7
1810 $ cat sub2/large7
1811 large7
1811 large7
1812
1812
1813 Cat a largefile
1813 Cat a largefile
1814 $ hg cat normal3
1814 $ hg cat normal3
1815 normal3-modified
1815 normal3-modified
1816 $ hg cat sub/large4
1816 $ hg cat sub/large4
1817 large4-modified
1817 large4-modified
1818 $ rm "${USERCACHE}"/*
1818 $ rm "${USERCACHE}"/*
1819 $ hg cat -r a381d2c8c80e -o cat.out sub/large4
1819 $ hg cat -r a381d2c8c80e -o cat.out sub/large4
1820 $ cat cat.out
1820 $ cat cat.out
1821 large4-modified
1821 large4-modified
1822 $ rm cat.out
1822 $ rm cat.out
1823 $ hg cat -r a381d2c8c80e normal3
1823 $ hg cat -r a381d2c8c80e normal3
1824 normal3-modified
1824 normal3-modified
1825 $ hg cat -r '.^' normal3
1825 $ hg cat -r '.^' normal3
1826 normal3-modified
1826 normal3-modified
1827 $ hg cat -r '.^' sub/large4 doesntexist
1827 $ hg cat -r '.^' sub/large4 doesntexist
1828 large4-modified
1828 large4-modified
1829 doesntexist: no such file in rev a381d2c8c80e
1829 doesntexist: no such file in rev a381d2c8c80e
1830 $ hg --cwd sub cat -r '.^' large4
1830 $ hg --cwd sub cat -r '.^' large4
1831 large4-modified
1831 large4-modified
1832 $ hg --cwd sub cat -r '.^' ../normal3
1832 $ hg --cwd sub cat -r '.^' ../normal3
1833 normal3-modified
1833 normal3-modified
1834 Cat a standin
1834 Cat a standin
1835 $ hg cat .hglf/sub/large4
1835 $ hg cat .hglf/sub/large4
1836 e166e74c7303192238d60af5a9c4ce9bef0b7928
1836 e166e74c7303192238d60af5a9c4ce9bef0b7928
1837 $ hg cat .hglf/normal3
1837 $ hg cat .hglf/normal3
1838 .hglf/normal3: no such file in rev 598410d3eb9a
1838 .hglf/normal3: no such file in rev 598410d3eb9a
1839 [1]
1839 [1]
1840
1840
1841 Test that renaming a largefile results in correct output for status
1841 Test that renaming a largefile results in correct output for status
1842
1842
1843 $ hg rename sub/large4 large4-renamed
1843 $ hg rename sub/large4 large4-renamed
1844 $ hg commit -m "test rename output"
1844 $ hg commit -m "test rename output"
1845 Invoking status precommit hook
1845 Invoking status precommit hook
1846 A large4-renamed
1846 A large4-renamed
1847 R sub/large4
1847 R sub/large4
1848 $ cat large4-renamed
1848 $ cat large4-renamed
1849 large4-modified
1849 large4-modified
1850 $ cd sub2
1850 $ cd sub2
1851 $ hg rename large6 large6-renamed
1851 $ hg rename large6 large6-renamed
1852 $ hg st
1852 $ hg st
1853 A sub2/large6-renamed
1853 A sub2/large6-renamed
1854 R sub2/large6
1854 R sub2/large6
1855 $ cd ..
1855 $ cd ..
1856
1856
1857 Test --normal flag
1857 Test --normal flag
1858
1858
1859 $ dd if=/dev/zero bs=2k count=11k > new-largefile 2> /dev/null
1859 $ dd if=/dev/zero bs=2k count=11k > new-largefile 2> /dev/null
1860 $ hg add --normal --large new-largefile
1860 $ hg add --normal --large new-largefile
1861 abort: --normal cannot be used with --large
1861 abort: --normal cannot be used with --large
1862 [255]
1862 [255]
1863 $ hg add --normal new-largefile
1863 $ hg add --normal new-largefile
1864 new-largefile: up to 69 MB of RAM may be required to manage this file
1864 new-largefile: up to 69 MB of RAM may be required to manage this file
1865 (use 'hg revert new-largefile' to cancel the pending addition)
1865 (use 'hg revert new-largefile' to cancel the pending addition)
1866 $ hg revert new-largefile
1866 $ hg revert new-largefile
1867 $ hg --config ui.large-file-limit=22M add --normal new-largefile
1867 $ hg --config ui.large-file-limit=22M add --normal new-largefile
1868
1868
1869 Test explicit commit of switch between normal and largefile - make sure both
1869 Test explicit commit of switch between normal and largefile - make sure both
1870 the add and the remove is committed.
1870 the add and the remove is committed.
1871
1871
1872 $ hg up -qC
1872 $ hg up -qC
1873 $ hg forget normal3 large4-renamed
1873 $ hg forget normal3 large4-renamed
1874 $ hg add --large normal3
1874 $ hg add --large normal3
1875 $ hg add large4-renamed
1875 $ hg add large4-renamed
1876 $ hg commit -m 'swap' normal3 large4-renamed
1876 $ hg commit -m 'swap' normal3 large4-renamed
1877 Invoking status precommit hook
1877 Invoking status precommit hook
1878 A large4-renamed
1878 A large4-renamed
1879 A normal3
1879 A normal3
1880 ? new-largefile
1880 ? new-largefile
1881 ? sub2/large6-renamed
1881 ? sub2/large6-renamed
1882 $ hg mani
1882 $ hg mani
1883 .hglf/normal3
1883 .hglf/normal3
1884 .hglf/sub2/large6
1884 .hglf/sub2/large6
1885 .hglf/sub2/large7
1885 .hglf/sub2/large7
1886 large4-renamed
1886 large4-renamed
1887 sub/normal4
1887 sub/normal4
1888
1888
1889 $ cd ..
1889 $ cd ..
1890
1890
1891
1891
1892
1892
@@ -1,275 +1,275 b''
1 #require symlink
1 #require symlink
2
2
3 == tests added in 0.7 ==
3 == tests added in 0.7 ==
4
4
5 $ hg init test-symlinks-0.7; cd test-symlinks-0.7;
5 $ hg init test-symlinks-0.7; cd test-symlinks-0.7;
6 $ touch foo; ln -s foo bar; ln -s nonexistent baz
6 $ touch foo; ln -s foo bar; ln -s nonexistent baz
7
7
8 import with add and addremove -- symlink walking should _not_ screwup.
8 import with add and addremove -- symlink walking should _not_ screwup.
9
9
10 $ hg add
10 $ hg add
11 adding bar
11 adding bar
12 adding baz
12 adding baz
13 adding foo
13 adding foo
14 $ hg forget bar baz foo
14 $ hg forget bar baz foo
15 $ hg addremove
15 $ hg addremove
16 adding bar
16 adding bar
17 adding baz
17 adding baz
18 adding foo
18 adding foo
19
19
20 commit -- the symlink should _not_ appear added to dir state
20 commit -- the symlink should _not_ appear added to dir state
21
21
22 $ hg commit -m 'initial'
22 $ hg commit -m 'initial'
23
23
24 $ touch bomb
24 $ touch bomb
25
25
26 again, symlink should _not_ show up on dir state
26 again, symlink should _not_ show up on dir state
27
27
28 $ hg addremove
28 $ hg addremove
29 adding bomb
29 adding bomb
30
30
31 Assert screamed here before, should go by without consequence
31 Assert screamed here before, should go by without consequence
32
32
33 $ hg commit -m 'is there a bug?'
33 $ hg commit -m 'is there a bug?'
34 $ cd ..
34 $ cd ..
35
35
36
36
37 == fifo & ignore ==
37 == fifo & ignore ==
38
38
39 $ hg init test; cd test;
39 $ hg init test; cd test;
40
40
41 $ mkdir dir
41 $ mkdir dir
42 $ touch a.c dir/a.o dir/b.o
42 $ touch a.c dir/a.o dir/b.o
43
43
44 test what happens if we want to trick hg
44 test what happens if we want to trick hg
45
45
46 $ hg commit -A -m 0
46 $ hg commit -A -m 0
47 adding a.c
47 adding a.c
48 adding dir/a.o
48 adding dir/a.o
49 adding dir/b.o
49 adding dir/b.o
50 $ echo "relglob:*.o" > .hgignore
50 $ echo "relglob:*.o" > .hgignore
51 $ rm a.c
51 $ rm a.c
52 $ rm dir/a.o
52 $ rm dir/a.o
53 $ rm dir/b.o
53 $ rm dir/b.o
54 $ mkdir dir/a.o
54 $ mkdir dir/a.o
55 $ ln -s nonexistent dir/b.o
55 $ ln -s nonexistent dir/b.o
56 $ mkfifo a.c
56 $ mkfifo a.c
57
57
58 it should show a.c, dir/a.o and dir/b.o deleted
58 it should show a.c, dir/a.o and dir/b.o deleted
59
59
60 $ hg status
60 $ hg status
61 M dir/b.o
61 M dir/b.o
62 ! a.c
62 ! a.c
63 ! dir/a.o
63 ! dir/a.o
64 ? .hgignore
64 ? .hgignore
65 $ hg status a.c
65 $ hg status a.c
66 a.c: unsupported file type (type is fifo)
66 a.c: unsupported file type (type is fifo)
67 ! a.c
67 ! a.c
68 $ cd ..
68 $ cd ..
69
69
70
70
71 == symlinks from outside the tree ==
71 == symlinks from outside the tree ==
72
72
73 test absolute path through symlink outside repo
73 test absolute path through symlink outside repo
74
74
75 $ p=`pwd`
75 $ p=`pwd`
76 $ hg init x
76 $ hg init x
77 $ ln -s x y
77 $ ln -s x y
78 $ cd x
78 $ cd x
79 $ touch f
79 $ touch f
80 $ hg add f
80 $ hg add f
81 $ hg status "$p"/y/f
81 $ hg status "$p"/y/f
82 A f
82 A f
83
83
84 try symlink outside repo to file inside
84 try symlink outside repo to file inside
85
85
86 $ ln -s x/f ../z
86 $ ln -s x/f ../z
87
87
88 this should fail
88 this should fail
89
89
90 $ hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || :
90 $ hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || :
91 abort: ../z not under root '$TESTTMP/x'
91 abort: ../z not under root '$TESTTMP/x'
92 $ cd ..
92 $ cd ..
93
93
94
94
95 == cloning symlinks ==
95 == cloning symlinks ==
96 $ hg init clone; cd clone;
96 $ hg init clone; cd clone;
97
97
98 try cloning symlink in a subdir
98 try cloning symlink in a subdir
99 1. commit a symlink
99 1. commit a symlink
100
100
101 $ mkdir -p a/b/c
101 $ mkdir -p a/b/c
102 $ cd a/b/c
102 $ cd a/b/c
103 $ ln -s /path/to/symlink/source demo
103 $ ln -s /path/to/symlink/source demo
104 $ cd ../../..
104 $ cd ../../..
105 $ hg stat
105 $ hg stat
106 ? a/b/c/demo
106 ? a/b/c/demo
107 $ hg commit -A -m 'add symlink in a/b/c subdir'
107 $ hg commit -A -m 'add symlink in a/b/c subdir'
108 adding a/b/c/demo
108 adding a/b/c/demo
109
109
110 2. clone it
110 2. clone it
111
111
112 $ cd ..
112 $ cd ..
113 $ hg clone clone clonedest
113 $ hg clone clone clonedest
114 updating to branch default
114 updating to branch default
115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116
116
117
117
118 == symlink and git diffs ==
118 == symlink and git diffs ==
119
119
120 git symlink diff
120 git symlink diff
121
121
122 $ cd clonedest
122 $ cd clonedest
123 $ hg diff --git -r null:tip
123 $ hg diff --git -r null:tip
124 diff --git a/a/b/c/demo b/a/b/c/demo
124 diff --git a/a/b/c/demo b/a/b/c/demo
125 new file mode 120000
125 new file mode 120000
126 --- /dev/null
126 --- /dev/null
127 +++ b/a/b/c/demo
127 +++ b/a/b/c/demo
128 @@ -0,0 +1,1 @@
128 @@ -0,0 +1,1 @@
129 +/path/to/symlink/source
129 +/path/to/symlink/source
130 \ No newline at end of file
130 \ No newline at end of file
131 $ hg export --git tip > ../sl.diff
131 $ hg export --git tip > ../sl.diff
132
132
133 import git symlink diff
133 import git symlink diff
134
134
135 $ hg rm a/b/c/demo
135 $ hg rm a/b/c/demo
136 $ hg commit -m'remove link'
136 $ hg commit -m'remove link'
137 $ hg import ../sl.diff
137 $ hg import ../sl.diff
138 applying ../sl.diff
138 applying ../sl.diff
139 $ hg diff --git -r 1:tip
139 $ hg diff --git -r 1:tip
140 diff --git a/a/b/c/demo b/a/b/c/demo
140 diff --git a/a/b/c/demo b/a/b/c/demo
141 new file mode 120000
141 new file mode 120000
142 --- /dev/null
142 --- /dev/null
143 +++ b/a/b/c/demo
143 +++ b/a/b/c/demo
144 @@ -0,0 +1,1 @@
144 @@ -0,0 +1,1 @@
145 +/path/to/symlink/source
145 +/path/to/symlink/source
146 \ No newline at end of file
146 \ No newline at end of file
147
147
148 == symlinks and addremove ==
148 == symlinks and addremove ==
149
149
150 directory moved and symlinked
150 directory moved and symlinked
151
151
152 $ mkdir foo
152 $ mkdir foo
153 $ touch foo/a
153 $ touch foo/a
154 $ hg ci -Ama
154 $ hg ci -Ama
155 adding foo/a
155 adding foo/a
156 $ mv foo bar
156 $ mv foo bar
157 $ ln -s bar foo
157 $ ln -s bar foo
158 $ hg status
158 $ hg status
159 ! foo/a
159 ! foo/a
160 ? bar/a
160 ? bar/a
161 ? foo
161 ? foo
162
162
163 now addremove should remove old files
163 now addremove should remove old files
164
164
165 $ hg addremove
165 $ hg addremove
166 adding bar/a
166 adding bar/a
167 adding foo
167 adding foo
168 removing foo/a
168 removing foo/a
169
169
170 commit and update back
170 commit and update back
171
171
172 $ hg ci -mb
172 $ hg ci -mb
173 $ hg up '.^'
173 $ hg up '.^'
174 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
174 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
175 $ hg up tip
175 $ hg up tip
176 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
176 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
177
177
178 $ cd ..
178 $ cd ..
179
179
180 == root of repository is symlinked ==
180 == root of repository is symlinked ==
181
181
182 $ hg init root
182 $ hg init root
183 $ ln -s root link
183 $ ln -s root link
184 $ cd root
184 $ cd root
185 $ echo foo > foo
185 $ echo foo > foo
186 $ hg status
186 $ hg status
187 ? foo
187 ? foo
188 $ hg status ../link
188 $ hg status ../link
189 ? foo
189 ? foo
190 $ hg add foo
190 $ hg add foo
191 $ hg cp foo "$TESTTMP/link/bar"
191 $ hg cp foo "$TESTTMP/link/bar"
192 foo has not been committed yet, so no copy data will be stored for bar.
192 foo has not been committed yet, so no copy data will be stored for bar.
193 $ cd ..
193 $ cd ..
194
194
195
195
196 $ hg init b
196 $ hg init b
197 $ cd b
197 $ cd b
198 $ ln -s nothing dangling
198 $ ln -s nothing dangling
199 $ hg commit -m 'commit symlink without adding' dangling
199 $ hg commit -m 'commit symlink without adding' dangling
200 abort: dangling: file not tracked!
200 abort: dangling: file not tracked!
201 [255]
201 [10]
202 $ hg add dangling
202 $ hg add dangling
203 $ hg commit -m 'add symlink'
203 $ hg commit -m 'add symlink'
204
204
205 $ hg tip -v
205 $ hg tip -v
206 changeset: 0:cabd88b706fc
206 changeset: 0:cabd88b706fc
207 tag: tip
207 tag: tip
208 user: test
208 user: test
209 date: Thu Jan 01 00:00:00 1970 +0000
209 date: Thu Jan 01 00:00:00 1970 +0000
210 files: dangling
210 files: dangling
211 description:
211 description:
212 add symlink
212 add symlink
213
213
214
214
215 $ hg manifest --debug
215 $ hg manifest --debug
216 2564acbe54bbbedfbf608479340b359f04597f80 644 @ dangling
216 2564acbe54bbbedfbf608479340b359f04597f80 644 @ dangling
217 $ readlink.py dangling
217 $ readlink.py dangling
218 dangling -> nothing
218 dangling -> nothing
219
219
220 $ rm dangling
220 $ rm dangling
221 $ ln -s void dangling
221 $ ln -s void dangling
222 $ hg commit -m 'change symlink'
222 $ hg commit -m 'change symlink'
223 $ readlink.py dangling
223 $ readlink.py dangling
224 dangling -> void
224 dangling -> void
225
225
226
226
227 modifying link
227 modifying link
228
228
229 $ rm dangling
229 $ rm dangling
230 $ ln -s empty dangling
230 $ ln -s empty dangling
231 $ readlink.py dangling
231 $ readlink.py dangling
232 dangling -> empty
232 dangling -> empty
233
233
234
234
235 reverting to rev 0:
235 reverting to rev 0:
236
236
237 $ hg revert -r 0 -a
237 $ hg revert -r 0 -a
238 reverting dangling
238 reverting dangling
239 $ readlink.py dangling
239 $ readlink.py dangling
240 dangling -> nothing
240 dangling -> nothing
241
241
242
242
243 backups:
243 backups:
244
244
245 $ readlink.py *.orig
245 $ readlink.py *.orig
246 dangling.orig -> empty
246 dangling.orig -> empty
247 $ rm *.orig
247 $ rm *.orig
248 $ hg up -C
248 $ hg up -C
249 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
249 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
250
250
251 copies
251 copies
252
252
253 $ hg cp -v dangling dangling2
253 $ hg cp -v dangling dangling2
254 copying dangling to dangling2
254 copying dangling to dangling2
255 $ hg st -Cmard
255 $ hg st -Cmard
256 A dangling2
256 A dangling2
257 dangling
257 dangling
258 $ readlink.py dangling dangling2
258 $ readlink.py dangling dangling2
259 dangling -> void
259 dangling -> void
260 dangling2 -> void
260 dangling2 -> void
261
261
262
262
263 Issue995: hg copy -A incorrectly handles symbolic links
263 Issue995: hg copy -A incorrectly handles symbolic links
264
264
265 $ hg up -C
265 $ hg up -C
266 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 $ mkdir dir
267 $ mkdir dir
268 $ ln -s dir dirlink
268 $ ln -s dir dirlink
269 $ hg ci -qAm 'add dirlink'
269 $ hg ci -qAm 'add dirlink'
270 $ mkdir newdir
270 $ mkdir newdir
271 $ mv dir newdir/dir
271 $ mv dir newdir/dir
272 $ mv dirlink newdir/dirlink
272 $ mv dirlink newdir/dirlink
273 $ hg mv -A dirlink newdir/dirlink
273 $ hg mv -A dirlink newdir/dirlink
274
274
275 $ cd ..
275 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now