##// END OF EJS Templates
mergestate: store about files resolved in favour of other...
Pulkit Goyal -
r45178:1b8fd4af default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,7833 +1,7838 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing 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 difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from .pycompat import open
25 from .pycompat import open
26 from . import (
26 from . import (
27 archival,
27 archival,
28 bookmarks,
28 bookmarks,
29 bundle2,
29 bundle2,
30 changegroup,
30 changegroup,
31 cmdutil,
31 cmdutil,
32 copies,
32 copies,
33 debugcommands as debugcommandsmod,
33 debugcommands as debugcommandsmod,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filemerge,
41 filemerge,
42 formatter,
42 formatter,
43 graphmod,
43 graphmod,
44 hbisect,
44 hbisect,
45 help,
45 help,
46 hg,
46 hg,
47 logcmdutil,
47 logcmdutil,
48 merge as mergemod,
48 merge as mergemod,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 obsutil,
51 obsutil,
52 patch,
52 patch,
53 phases,
53 phases,
54 pycompat,
54 pycompat,
55 rcutil,
55 rcutil,
56 registrar,
56 registrar,
57 revsetlang,
57 revsetlang,
58 rewriteutil,
58 rewriteutil,
59 scmutil,
59 scmutil,
60 server,
60 server,
61 shelve as shelvemod,
61 shelve as shelvemod,
62 state as statemod,
62 state as statemod,
63 streamclone,
63 streamclone,
64 tags as tagsmod,
64 tags as tagsmod,
65 ui as uimod,
65 ui as uimod,
66 util,
66 util,
67 verify as verifymod,
67 verify as verifymod,
68 wireprotoserver,
68 wireprotoserver,
69 )
69 )
70 from .utils import (
70 from .utils import (
71 dateutil,
71 dateutil,
72 stringutil,
72 stringutil,
73 )
73 )
74
74
75 table = {}
75 table = {}
76 table.update(debugcommandsmod.command._table)
76 table.update(debugcommandsmod.command._table)
77
77
78 command = registrar.command(table)
78 command = registrar.command(table)
79 INTENT_READONLY = registrar.INTENT_READONLY
79 INTENT_READONLY = registrar.INTENT_READONLY
80
80
81 # common command options
81 # common command options
82
82
83 globalopts = [
83 globalopts = [
84 (
84 (
85 b'R',
85 b'R',
86 b'repository',
86 b'repository',
87 b'',
87 b'',
88 _(b'repository root directory or name of overlay bundle file'),
88 _(b'repository root directory or name of overlay bundle file'),
89 _(b'REPO'),
89 _(b'REPO'),
90 ),
90 ),
91 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
91 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
92 (
92 (
93 b'y',
93 b'y',
94 b'noninteractive',
94 b'noninteractive',
95 None,
95 None,
96 _(
96 _(
97 b'do not prompt, automatically pick the first choice for all prompts'
97 b'do not prompt, automatically pick the first choice for all prompts'
98 ),
98 ),
99 ),
99 ),
100 (b'q', b'quiet', None, _(b'suppress output')),
100 (b'q', b'quiet', None, _(b'suppress output')),
101 (b'v', b'verbose', None, _(b'enable additional output')),
101 (b'v', b'verbose', None, _(b'enable additional output')),
102 (
102 (
103 b'',
103 b'',
104 b'color',
104 b'color',
105 b'',
105 b'',
106 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
106 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
107 # and should not be translated
107 # and should not be translated
108 _(b"when to colorize (boolean, always, auto, never, or debug)"),
108 _(b"when to colorize (boolean, always, auto, never, or debug)"),
109 _(b'TYPE'),
109 _(b'TYPE'),
110 ),
110 ),
111 (
111 (
112 b'',
112 b'',
113 b'config',
113 b'config',
114 [],
114 [],
115 _(b'set/override config option (use \'section.name=value\')'),
115 _(b'set/override config option (use \'section.name=value\')'),
116 _(b'CONFIG'),
116 _(b'CONFIG'),
117 ),
117 ),
118 (b'', b'debug', None, _(b'enable debugging output')),
118 (b'', b'debug', None, _(b'enable debugging output')),
119 (b'', b'debugger', None, _(b'start debugger')),
119 (b'', b'debugger', None, _(b'start debugger')),
120 (
120 (
121 b'',
121 b'',
122 b'encoding',
122 b'encoding',
123 encoding.encoding,
123 encoding.encoding,
124 _(b'set the charset encoding'),
124 _(b'set the charset encoding'),
125 _(b'ENCODE'),
125 _(b'ENCODE'),
126 ),
126 ),
127 (
127 (
128 b'',
128 b'',
129 b'encodingmode',
129 b'encodingmode',
130 encoding.encodingmode,
130 encoding.encodingmode,
131 _(b'set the charset encoding mode'),
131 _(b'set the charset encoding mode'),
132 _(b'MODE'),
132 _(b'MODE'),
133 ),
133 ),
134 (b'', b'traceback', None, _(b'always print a traceback on exception')),
134 (b'', b'traceback', None, _(b'always print a traceback on exception')),
135 (b'', b'time', None, _(b'time how long the command takes')),
135 (b'', b'time', None, _(b'time how long the command takes')),
136 (b'', b'profile', None, _(b'print command execution profile')),
136 (b'', b'profile', None, _(b'print command execution profile')),
137 (b'', b'version', None, _(b'output version information and exit')),
137 (b'', b'version', None, _(b'output version information and exit')),
138 (b'h', b'help', None, _(b'display help and exit')),
138 (b'h', b'help', None, _(b'display help and exit')),
139 (b'', b'hidden', False, _(b'consider hidden changesets')),
139 (b'', b'hidden', False, _(b'consider hidden changesets')),
140 (
140 (
141 b'',
141 b'',
142 b'pager',
142 b'pager',
143 b'auto',
143 b'auto',
144 _(b"when to paginate (boolean, always, auto, or never)"),
144 _(b"when to paginate (boolean, always, auto, or never)"),
145 _(b'TYPE'),
145 _(b'TYPE'),
146 ),
146 ),
147 ]
147 ]
148
148
149 dryrunopts = cmdutil.dryrunopts
149 dryrunopts = cmdutil.dryrunopts
150 remoteopts = cmdutil.remoteopts
150 remoteopts = cmdutil.remoteopts
151 walkopts = cmdutil.walkopts
151 walkopts = cmdutil.walkopts
152 commitopts = cmdutil.commitopts
152 commitopts = cmdutil.commitopts
153 commitopts2 = cmdutil.commitopts2
153 commitopts2 = cmdutil.commitopts2
154 commitopts3 = cmdutil.commitopts3
154 commitopts3 = cmdutil.commitopts3
155 formatteropts = cmdutil.formatteropts
155 formatteropts = cmdutil.formatteropts
156 templateopts = cmdutil.templateopts
156 templateopts = cmdutil.templateopts
157 logopts = cmdutil.logopts
157 logopts = cmdutil.logopts
158 diffopts = cmdutil.diffopts
158 diffopts = cmdutil.diffopts
159 diffwsopts = cmdutil.diffwsopts
159 diffwsopts = cmdutil.diffwsopts
160 diffopts2 = cmdutil.diffopts2
160 diffopts2 = cmdutil.diffopts2
161 mergetoolopts = cmdutil.mergetoolopts
161 mergetoolopts = cmdutil.mergetoolopts
162 similarityopts = cmdutil.similarityopts
162 similarityopts = cmdutil.similarityopts
163 subrepoopts = cmdutil.subrepoopts
163 subrepoopts = cmdutil.subrepoopts
164 debugrevlogopts = cmdutil.debugrevlogopts
164 debugrevlogopts = cmdutil.debugrevlogopts
165
165
166 # Commands start here, listed alphabetically
166 # Commands start here, listed alphabetically
167
167
168
168
169 @command(
169 @command(
170 b'abort',
170 b'abort',
171 dryrunopts,
171 dryrunopts,
172 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
172 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
173 helpbasic=True,
173 helpbasic=True,
174 )
174 )
175 def abort(ui, repo, **opts):
175 def abort(ui, repo, **opts):
176 """abort an unfinished operation (EXPERIMENTAL)
176 """abort an unfinished operation (EXPERIMENTAL)
177
177
178 Aborts a multistep operation like graft, histedit, rebase, merge,
178 Aborts a multistep operation like graft, histedit, rebase, merge,
179 and unshelve if they are in an unfinished state.
179 and unshelve if they are in an unfinished state.
180
180
181 use --dry-run/-n to dry run the command.
181 use --dry-run/-n to dry run the command.
182 """
182 """
183 dryrun = opts.get('dry_run')
183 dryrun = opts.get('dry_run')
184 abortstate = cmdutil.getunfinishedstate(repo)
184 abortstate = cmdutil.getunfinishedstate(repo)
185 if not abortstate:
185 if not abortstate:
186 raise error.Abort(_(b'no operation in progress'))
186 raise error.Abort(_(b'no operation in progress'))
187 if not abortstate.abortfunc:
187 if not abortstate.abortfunc:
188 raise error.Abort(
188 raise error.Abort(
189 (
189 (
190 _(b"%s in progress but does not support 'hg abort'")
190 _(b"%s in progress but does not support 'hg abort'")
191 % (abortstate._opname)
191 % (abortstate._opname)
192 ),
192 ),
193 hint=abortstate.hint(),
193 hint=abortstate.hint(),
194 )
194 )
195 if dryrun:
195 if dryrun:
196 ui.status(
196 ui.status(
197 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
197 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
198 )
198 )
199 return
199 return
200 return abortstate.abortfunc(ui, repo)
200 return abortstate.abortfunc(ui, repo)
201
201
202
202
203 @command(
203 @command(
204 b'add',
204 b'add',
205 walkopts + subrepoopts + dryrunopts,
205 walkopts + subrepoopts + dryrunopts,
206 _(b'[OPTION]... [FILE]...'),
206 _(b'[OPTION]... [FILE]...'),
207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
208 helpbasic=True,
208 helpbasic=True,
209 inferrepo=True,
209 inferrepo=True,
210 )
210 )
211 def add(ui, repo, *pats, **opts):
211 def add(ui, repo, *pats, **opts):
212 """add the specified files on the next commit
212 """add the specified files on the next commit
213
213
214 Schedule files to be version controlled and added to the
214 Schedule files to be version controlled and added to the
215 repository.
215 repository.
216
216
217 The files will be added to the repository at the next commit. To
217 The files will be added to the repository at the next commit. To
218 undo an add before that, see :hg:`forget`.
218 undo an add before that, see :hg:`forget`.
219
219
220 If no names are given, add all files to the repository (except
220 If no names are given, add all files to the repository (except
221 files matching ``.hgignore``).
221 files matching ``.hgignore``).
222
222
223 .. container:: verbose
223 .. container:: verbose
224
224
225 Examples:
225 Examples:
226
226
227 - New (unknown) files are added
227 - New (unknown) files are added
228 automatically by :hg:`add`::
228 automatically by :hg:`add`::
229
229
230 $ ls
230 $ ls
231 foo.c
231 foo.c
232 $ hg status
232 $ hg status
233 ? foo.c
233 ? foo.c
234 $ hg add
234 $ hg add
235 adding foo.c
235 adding foo.c
236 $ hg status
236 $ hg status
237 A foo.c
237 A foo.c
238
238
239 - Specific files to be added can be specified::
239 - Specific files to be added can be specified::
240
240
241 $ ls
241 $ ls
242 bar.c foo.c
242 bar.c foo.c
243 $ hg status
243 $ hg status
244 ? bar.c
244 ? bar.c
245 ? foo.c
245 ? foo.c
246 $ hg add bar.c
246 $ hg add bar.c
247 $ hg status
247 $ hg status
248 A bar.c
248 A bar.c
249 ? foo.c
249 ? foo.c
250
250
251 Returns 0 if all files are successfully added.
251 Returns 0 if all files are successfully added.
252 """
252 """
253
253
254 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
254 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
255 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
255 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
256 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
256 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
257 return rejected and 1 or 0
257 return rejected and 1 or 0
258
258
259
259
260 @command(
260 @command(
261 b'addremove',
261 b'addremove',
262 similarityopts + subrepoopts + walkopts + dryrunopts,
262 similarityopts + subrepoopts + walkopts + dryrunopts,
263 _(b'[OPTION]... [FILE]...'),
263 _(b'[OPTION]... [FILE]...'),
264 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
264 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
265 inferrepo=True,
265 inferrepo=True,
266 )
266 )
267 def addremove(ui, repo, *pats, **opts):
267 def addremove(ui, repo, *pats, **opts):
268 """add all new files, delete all missing files
268 """add all new files, delete all missing files
269
269
270 Add all new files and remove all missing files from the
270 Add all new files and remove all missing files from the
271 repository.
271 repository.
272
272
273 Unless names are given, new files are ignored if they match any of
273 Unless names are given, new files are ignored if they match any of
274 the patterns in ``.hgignore``. As with add, these changes take
274 the patterns in ``.hgignore``. As with add, these changes take
275 effect at the next commit.
275 effect at the next commit.
276
276
277 Use the -s/--similarity option to detect renamed files. This
277 Use the -s/--similarity option to detect renamed files. This
278 option takes a percentage between 0 (disabled) and 100 (files must
278 option takes a percentage between 0 (disabled) and 100 (files must
279 be identical) as its parameter. With a parameter greater than 0,
279 be identical) as its parameter. With a parameter greater than 0,
280 this compares every removed file with every added file and records
280 this compares every removed file with every added file and records
281 those similar enough as renames. Detecting renamed files this way
281 those similar enough as renames. Detecting renamed files this way
282 can be expensive. After using this option, :hg:`status -C` can be
282 can be expensive. After using this option, :hg:`status -C` can be
283 used to check which files were identified as moved or renamed. If
283 used to check which files were identified as moved or renamed. If
284 not specified, -s/--similarity defaults to 100 and only renames of
284 not specified, -s/--similarity defaults to 100 and only renames of
285 identical files are detected.
285 identical files are detected.
286
286
287 .. container:: verbose
287 .. container:: verbose
288
288
289 Examples:
289 Examples:
290
290
291 - A number of files (bar.c and foo.c) are new,
291 - A number of files (bar.c and foo.c) are new,
292 while foobar.c has been removed (without using :hg:`remove`)
292 while foobar.c has been removed (without using :hg:`remove`)
293 from the repository::
293 from the repository::
294
294
295 $ ls
295 $ ls
296 bar.c foo.c
296 bar.c foo.c
297 $ hg status
297 $ hg status
298 ! foobar.c
298 ! foobar.c
299 ? bar.c
299 ? bar.c
300 ? foo.c
300 ? foo.c
301 $ hg addremove
301 $ hg addremove
302 adding bar.c
302 adding bar.c
303 adding foo.c
303 adding foo.c
304 removing foobar.c
304 removing foobar.c
305 $ hg status
305 $ hg status
306 A bar.c
306 A bar.c
307 A foo.c
307 A foo.c
308 R foobar.c
308 R foobar.c
309
309
310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
311 Afterwards, it was edited slightly::
311 Afterwards, it was edited slightly::
312
312
313 $ ls
313 $ ls
314 foo.c
314 foo.c
315 $ hg status
315 $ hg status
316 ! foobar.c
316 ! foobar.c
317 ? foo.c
317 ? foo.c
318 $ hg addremove --similarity 90
318 $ hg addremove --similarity 90
319 removing foobar.c
319 removing foobar.c
320 adding foo.c
320 adding foo.c
321 recording removal of foobar.c as rename to foo.c (94% similar)
321 recording removal of foobar.c as rename to foo.c (94% similar)
322 $ hg status -C
322 $ hg status -C
323 A foo.c
323 A foo.c
324 foobar.c
324 foobar.c
325 R foobar.c
325 R foobar.c
326
326
327 Returns 0 if all files are successfully added.
327 Returns 0 if all files are successfully added.
328 """
328 """
329 opts = pycompat.byteskwargs(opts)
329 opts = pycompat.byteskwargs(opts)
330 if not opts.get(b'similarity'):
330 if not opts.get(b'similarity'):
331 opts[b'similarity'] = b'100'
331 opts[b'similarity'] = b'100'
332 matcher = scmutil.match(repo[None], pats, opts)
332 matcher = scmutil.match(repo[None], pats, opts)
333 relative = scmutil.anypats(pats, opts)
333 relative = scmutil.anypats(pats, opts)
334 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
334 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
335 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
335 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
336
336
337
337
338 @command(
338 @command(
339 b'annotate|blame',
339 b'annotate|blame',
340 [
340 [
341 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
341 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
342 (
342 (
343 b'',
343 b'',
344 b'follow',
344 b'follow',
345 None,
345 None,
346 _(b'follow copies/renames and list the filename (DEPRECATED)'),
346 _(b'follow copies/renames and list the filename (DEPRECATED)'),
347 ),
347 ),
348 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
348 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
349 (b'a', b'text', None, _(b'treat all files as text')),
349 (b'a', b'text', None, _(b'treat all files as text')),
350 (b'u', b'user', None, _(b'list the author (long with -v)')),
350 (b'u', b'user', None, _(b'list the author (long with -v)')),
351 (b'f', b'file', None, _(b'list the filename')),
351 (b'f', b'file', None, _(b'list the filename')),
352 (b'd', b'date', None, _(b'list the date (short with -q)')),
352 (b'd', b'date', None, _(b'list the date (short with -q)')),
353 (b'n', b'number', None, _(b'list the revision number (default)')),
353 (b'n', b'number', None, _(b'list the revision number (default)')),
354 (b'c', b'changeset', None, _(b'list the changeset')),
354 (b'c', b'changeset', None, _(b'list the changeset')),
355 (
355 (
356 b'l',
356 b'l',
357 b'line-number',
357 b'line-number',
358 None,
358 None,
359 _(b'show line number at the first appearance'),
359 _(b'show line number at the first appearance'),
360 ),
360 ),
361 (
361 (
362 b'',
362 b'',
363 b'skip',
363 b'skip',
364 [],
364 [],
365 _(b'revset to not display (EXPERIMENTAL)'),
365 _(b'revset to not display (EXPERIMENTAL)'),
366 _(b'REV'),
366 _(b'REV'),
367 ),
367 ),
368 ]
368 ]
369 + diffwsopts
369 + diffwsopts
370 + walkopts
370 + walkopts
371 + formatteropts,
371 + formatteropts,
372 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
372 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
373 helpcategory=command.CATEGORY_FILE_CONTENTS,
373 helpcategory=command.CATEGORY_FILE_CONTENTS,
374 helpbasic=True,
374 helpbasic=True,
375 inferrepo=True,
375 inferrepo=True,
376 )
376 )
377 def annotate(ui, repo, *pats, **opts):
377 def annotate(ui, repo, *pats, **opts):
378 """show changeset information by line for each file
378 """show changeset information by line for each file
379
379
380 List changes in files, showing the revision id responsible for
380 List changes in files, showing the revision id responsible for
381 each line.
381 each line.
382
382
383 This command is useful for discovering when a change was made and
383 This command is useful for discovering when a change was made and
384 by whom.
384 by whom.
385
385
386 If you include --file, --user, or --date, the revision number is
386 If you include --file, --user, or --date, the revision number is
387 suppressed unless you also include --number.
387 suppressed unless you also include --number.
388
388
389 Without the -a/--text option, annotate will avoid processing files
389 Without the -a/--text option, annotate will avoid processing files
390 it detects as binary. With -a, annotate will annotate the file
390 it detects as binary. With -a, annotate will annotate the file
391 anyway, although the results will probably be neither useful
391 anyway, although the results will probably be neither useful
392 nor desirable.
392 nor desirable.
393
393
394 .. container:: verbose
394 .. container:: verbose
395
395
396 Template:
396 Template:
397
397
398 The following keywords are supported in addition to the common template
398 The following keywords are supported in addition to the common template
399 keywords and functions. See also :hg:`help templates`.
399 keywords and functions. See also :hg:`help templates`.
400
400
401 :lines: List of lines with annotation data.
401 :lines: List of lines with annotation data.
402 :path: String. Repository-absolute path of the specified file.
402 :path: String. Repository-absolute path of the specified file.
403
403
404 And each entry of ``{lines}`` provides the following sub-keywords in
404 And each entry of ``{lines}`` provides the following sub-keywords in
405 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
405 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
406
406
407 :line: String. Line content.
407 :line: String. Line content.
408 :lineno: Integer. Line number at that revision.
408 :lineno: Integer. Line number at that revision.
409 :path: String. Repository-absolute path of the file at that revision.
409 :path: String. Repository-absolute path of the file at that revision.
410
410
411 See :hg:`help templates.operators` for the list expansion syntax.
411 See :hg:`help templates.operators` for the list expansion syntax.
412
412
413 Returns 0 on success.
413 Returns 0 on success.
414 """
414 """
415 opts = pycompat.byteskwargs(opts)
415 opts = pycompat.byteskwargs(opts)
416 if not pats:
416 if not pats:
417 raise error.Abort(_(b'at least one filename or pattern is required'))
417 raise error.Abort(_(b'at least one filename or pattern is required'))
418
418
419 if opts.get(b'follow'):
419 if opts.get(b'follow'):
420 # --follow is deprecated and now just an alias for -f/--file
420 # --follow is deprecated and now just an alias for -f/--file
421 # to mimic the behavior of Mercurial before version 1.5
421 # to mimic the behavior of Mercurial before version 1.5
422 opts[b'file'] = True
422 opts[b'file'] = True
423
423
424 if (
424 if (
425 not opts.get(b'user')
425 not opts.get(b'user')
426 and not opts.get(b'changeset')
426 and not opts.get(b'changeset')
427 and not opts.get(b'date')
427 and not opts.get(b'date')
428 and not opts.get(b'file')
428 and not opts.get(b'file')
429 ):
429 ):
430 opts[b'number'] = True
430 opts[b'number'] = True
431
431
432 linenumber = opts.get(b'line_number') is not None
432 linenumber = opts.get(b'line_number') is not None
433 if (
433 if (
434 linenumber
434 linenumber
435 and (not opts.get(b'changeset'))
435 and (not opts.get(b'changeset'))
436 and (not opts.get(b'number'))
436 and (not opts.get(b'number'))
437 ):
437 ):
438 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
438 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
439
439
440 rev = opts.get(b'rev')
440 rev = opts.get(b'rev')
441 if rev:
441 if rev:
442 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
442 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
443 ctx = scmutil.revsingle(repo, rev)
443 ctx = scmutil.revsingle(repo, rev)
444
444
445 ui.pager(b'annotate')
445 ui.pager(b'annotate')
446 rootfm = ui.formatter(b'annotate', opts)
446 rootfm = ui.formatter(b'annotate', opts)
447 if ui.debugflag:
447 if ui.debugflag:
448 shorthex = pycompat.identity
448 shorthex = pycompat.identity
449 else:
449 else:
450
450
451 def shorthex(h):
451 def shorthex(h):
452 return h[:12]
452 return h[:12]
453
453
454 if ui.quiet:
454 if ui.quiet:
455 datefunc = dateutil.shortdate
455 datefunc = dateutil.shortdate
456 else:
456 else:
457 datefunc = dateutil.datestr
457 datefunc = dateutil.datestr
458 if ctx.rev() is None:
458 if ctx.rev() is None:
459 if opts.get(b'changeset'):
459 if opts.get(b'changeset'):
460 # omit "+" suffix which is appended to node hex
460 # omit "+" suffix which is appended to node hex
461 def formatrev(rev):
461 def formatrev(rev):
462 if rev == wdirrev:
462 if rev == wdirrev:
463 return b'%d' % ctx.p1().rev()
463 return b'%d' % ctx.p1().rev()
464 else:
464 else:
465 return b'%d' % rev
465 return b'%d' % rev
466
466
467 else:
467 else:
468
468
469 def formatrev(rev):
469 def formatrev(rev):
470 if rev == wdirrev:
470 if rev == wdirrev:
471 return b'%d+' % ctx.p1().rev()
471 return b'%d+' % ctx.p1().rev()
472 else:
472 else:
473 return b'%d ' % rev
473 return b'%d ' % rev
474
474
475 def formathex(h):
475 def formathex(h):
476 if h == wdirhex:
476 if h == wdirhex:
477 return b'%s+' % shorthex(hex(ctx.p1().node()))
477 return b'%s+' % shorthex(hex(ctx.p1().node()))
478 else:
478 else:
479 return b'%s ' % shorthex(h)
479 return b'%s ' % shorthex(h)
480
480
481 else:
481 else:
482 formatrev = b'%d'.__mod__
482 formatrev = b'%d'.__mod__
483 formathex = shorthex
483 formathex = shorthex
484
484
485 opmap = [
485 opmap = [
486 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
486 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
487 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
487 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
488 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
488 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
489 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
489 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
490 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
490 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
491 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
491 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
492 ]
492 ]
493 opnamemap = {
493 opnamemap = {
494 b'rev': b'number',
494 b'rev': b'number',
495 b'node': b'changeset',
495 b'node': b'changeset',
496 b'path': b'file',
496 b'path': b'file',
497 b'lineno': b'line_number',
497 b'lineno': b'line_number',
498 }
498 }
499
499
500 if rootfm.isplain():
500 if rootfm.isplain():
501
501
502 def makefunc(get, fmt):
502 def makefunc(get, fmt):
503 return lambda x: fmt(get(x))
503 return lambda x: fmt(get(x))
504
504
505 else:
505 else:
506
506
507 def makefunc(get, fmt):
507 def makefunc(get, fmt):
508 return get
508 return get
509
509
510 datahint = rootfm.datahint()
510 datahint = rootfm.datahint()
511 funcmap = [
511 funcmap = [
512 (makefunc(get, fmt), sep)
512 (makefunc(get, fmt), sep)
513 for fn, sep, get, fmt in opmap
513 for fn, sep, get, fmt in opmap
514 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
514 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
515 ]
515 ]
516 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
516 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
517 fields = b' '.join(
517 fields = b' '.join(
518 fn
518 fn
519 for fn, sep, get, fmt in opmap
519 for fn, sep, get, fmt in opmap
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
521 )
521 )
522
522
523 def bad(x, y):
523 def bad(x, y):
524 raise error.Abort(b"%s: %s" % (x, y))
524 raise error.Abort(b"%s: %s" % (x, y))
525
525
526 m = scmutil.match(ctx, pats, opts, badfn=bad)
526 m = scmutil.match(ctx, pats, opts, badfn=bad)
527
527
528 follow = not opts.get(b'no_follow')
528 follow = not opts.get(b'no_follow')
529 diffopts = patch.difffeatureopts(
529 diffopts = patch.difffeatureopts(
530 ui, opts, section=b'annotate', whitespace=True
530 ui, opts, section=b'annotate', whitespace=True
531 )
531 )
532 skiprevs = opts.get(b'skip')
532 skiprevs = opts.get(b'skip')
533 if skiprevs:
533 if skiprevs:
534 skiprevs = scmutil.revrange(repo, skiprevs)
534 skiprevs = scmutil.revrange(repo, skiprevs)
535
535
536 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
536 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
537 for abs in ctx.walk(m):
537 for abs in ctx.walk(m):
538 fctx = ctx[abs]
538 fctx = ctx[abs]
539 rootfm.startitem()
539 rootfm.startitem()
540 rootfm.data(path=abs)
540 rootfm.data(path=abs)
541 if not opts.get(b'text') and fctx.isbinary():
541 if not opts.get(b'text') and fctx.isbinary():
542 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
542 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
543 continue
543 continue
544
544
545 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
545 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
546 lines = fctx.annotate(
546 lines = fctx.annotate(
547 follow=follow, skiprevs=skiprevs, diffopts=diffopts
547 follow=follow, skiprevs=skiprevs, diffopts=diffopts
548 )
548 )
549 if not lines:
549 if not lines:
550 fm.end()
550 fm.end()
551 continue
551 continue
552 formats = []
552 formats = []
553 pieces = []
553 pieces = []
554
554
555 for f, sep in funcmap:
555 for f, sep in funcmap:
556 l = [f(n) for n in lines]
556 l = [f(n) for n in lines]
557 if fm.isplain():
557 if fm.isplain():
558 sizes = [encoding.colwidth(x) for x in l]
558 sizes = [encoding.colwidth(x) for x in l]
559 ml = max(sizes)
559 ml = max(sizes)
560 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
560 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
561 else:
561 else:
562 formats.append([b'%s'] * len(l))
562 formats.append([b'%s'] * len(l))
563 pieces.append(l)
563 pieces.append(l)
564
564
565 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
565 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
566 fm.startitem()
566 fm.startitem()
567 fm.context(fctx=n.fctx)
567 fm.context(fctx=n.fctx)
568 fm.write(fields, b"".join(f), *p)
568 fm.write(fields, b"".join(f), *p)
569 if n.skip:
569 if n.skip:
570 fmt = b"* %s"
570 fmt = b"* %s"
571 else:
571 else:
572 fmt = b": %s"
572 fmt = b": %s"
573 fm.write(b'line', fmt, n.text)
573 fm.write(b'line', fmt, n.text)
574
574
575 if not lines[-1].text.endswith(b'\n'):
575 if not lines[-1].text.endswith(b'\n'):
576 fm.plain(b'\n')
576 fm.plain(b'\n')
577 fm.end()
577 fm.end()
578
578
579 rootfm.end()
579 rootfm.end()
580
580
581
581
582 @command(
582 @command(
583 b'archive',
583 b'archive',
584 [
584 [
585 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
585 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
586 (
586 (
587 b'p',
587 b'p',
588 b'prefix',
588 b'prefix',
589 b'',
589 b'',
590 _(b'directory prefix for files in archive'),
590 _(b'directory prefix for files in archive'),
591 _(b'PREFIX'),
591 _(b'PREFIX'),
592 ),
592 ),
593 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
593 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
594 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
594 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
595 ]
595 ]
596 + subrepoopts
596 + subrepoopts
597 + walkopts,
597 + walkopts,
598 _(b'[OPTION]... DEST'),
598 _(b'[OPTION]... DEST'),
599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
600 )
600 )
601 def archive(ui, repo, dest, **opts):
601 def archive(ui, repo, dest, **opts):
602 '''create an unversioned archive of a repository revision
602 '''create an unversioned archive of a repository revision
603
603
604 By default, the revision used is the parent of the working
604 By default, the revision used is the parent of the working
605 directory; use -r/--rev to specify a different revision.
605 directory; use -r/--rev to specify a different revision.
606
606
607 The archive type is automatically detected based on file
607 The archive type is automatically detected based on file
608 extension (to override, use -t/--type).
608 extension (to override, use -t/--type).
609
609
610 .. container:: verbose
610 .. container:: verbose
611
611
612 Examples:
612 Examples:
613
613
614 - create a zip file containing the 1.0 release::
614 - create a zip file containing the 1.0 release::
615
615
616 hg archive -r 1.0 project-1.0.zip
616 hg archive -r 1.0 project-1.0.zip
617
617
618 - create a tarball excluding .hg files::
618 - create a tarball excluding .hg files::
619
619
620 hg archive project.tar.gz -X ".hg*"
620 hg archive project.tar.gz -X ".hg*"
621
621
622 Valid types are:
622 Valid types are:
623
623
624 :``files``: a directory full of files (default)
624 :``files``: a directory full of files (default)
625 :``tar``: tar archive, uncompressed
625 :``tar``: tar archive, uncompressed
626 :``tbz2``: tar archive, compressed using bzip2
626 :``tbz2``: tar archive, compressed using bzip2
627 :``tgz``: tar archive, compressed using gzip
627 :``tgz``: tar archive, compressed using gzip
628 :``txz``: tar archive, compressed using lzma (only in Python 3)
628 :``txz``: tar archive, compressed using lzma (only in Python 3)
629 :``uzip``: zip archive, uncompressed
629 :``uzip``: zip archive, uncompressed
630 :``zip``: zip archive, compressed using deflate
630 :``zip``: zip archive, compressed using deflate
631
631
632 The exact name of the destination archive or directory is given
632 The exact name of the destination archive or directory is given
633 using a format string; see :hg:`help export` for details.
633 using a format string; see :hg:`help export` for details.
634
634
635 Each member added to an archive file has a directory prefix
635 Each member added to an archive file has a directory prefix
636 prepended. Use -p/--prefix to specify a format string for the
636 prepended. Use -p/--prefix to specify a format string for the
637 prefix. The default is the basename of the archive, with suffixes
637 prefix. The default is the basename of the archive, with suffixes
638 removed.
638 removed.
639
639
640 Returns 0 on success.
640 Returns 0 on success.
641 '''
641 '''
642
642
643 opts = pycompat.byteskwargs(opts)
643 opts = pycompat.byteskwargs(opts)
644 rev = opts.get(b'rev')
644 rev = opts.get(b'rev')
645 if rev:
645 if rev:
646 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
646 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
647 ctx = scmutil.revsingle(repo, rev)
647 ctx = scmutil.revsingle(repo, rev)
648 if not ctx:
648 if not ctx:
649 raise error.Abort(_(b'no working directory: please specify a revision'))
649 raise error.Abort(_(b'no working directory: please specify a revision'))
650 node = ctx.node()
650 node = ctx.node()
651 dest = cmdutil.makefilename(ctx, dest)
651 dest = cmdutil.makefilename(ctx, dest)
652 if os.path.realpath(dest) == repo.root:
652 if os.path.realpath(dest) == repo.root:
653 raise error.Abort(_(b'repository root cannot be destination'))
653 raise error.Abort(_(b'repository root cannot be destination'))
654
654
655 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
655 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
656 prefix = opts.get(b'prefix')
656 prefix = opts.get(b'prefix')
657
657
658 if dest == b'-':
658 if dest == b'-':
659 if kind == b'files':
659 if kind == b'files':
660 raise error.Abort(_(b'cannot archive plain files to stdout'))
660 raise error.Abort(_(b'cannot archive plain files to stdout'))
661 dest = cmdutil.makefileobj(ctx, dest)
661 dest = cmdutil.makefileobj(ctx, dest)
662 if not prefix:
662 if not prefix:
663 prefix = os.path.basename(repo.root) + b'-%h'
663 prefix = os.path.basename(repo.root) + b'-%h'
664
664
665 prefix = cmdutil.makefilename(ctx, prefix)
665 prefix = cmdutil.makefilename(ctx, prefix)
666 match = scmutil.match(ctx, [], opts)
666 match = scmutil.match(ctx, [], opts)
667 archival.archive(
667 archival.archive(
668 repo,
668 repo,
669 dest,
669 dest,
670 node,
670 node,
671 kind,
671 kind,
672 not opts.get(b'no_decode'),
672 not opts.get(b'no_decode'),
673 match,
673 match,
674 prefix,
674 prefix,
675 subrepos=opts.get(b'subrepos'),
675 subrepos=opts.get(b'subrepos'),
676 )
676 )
677
677
678
678
679 @command(
679 @command(
680 b'backout',
680 b'backout',
681 [
681 [
682 (
682 (
683 b'',
683 b'',
684 b'merge',
684 b'merge',
685 None,
685 None,
686 _(b'merge with old dirstate parent after backout'),
686 _(b'merge with old dirstate parent after backout'),
687 ),
687 ),
688 (
688 (
689 b'',
689 b'',
690 b'commit',
690 b'commit',
691 None,
691 None,
692 _(b'commit if no conflicts were encountered (DEPRECATED)'),
692 _(b'commit if no conflicts were encountered (DEPRECATED)'),
693 ),
693 ),
694 (b'', b'no-commit', None, _(b'do not commit')),
694 (b'', b'no-commit', None, _(b'do not commit')),
695 (
695 (
696 b'',
696 b'',
697 b'parent',
697 b'parent',
698 b'',
698 b'',
699 _(b'parent to choose when backing out merge (DEPRECATED)'),
699 _(b'parent to choose when backing out merge (DEPRECATED)'),
700 _(b'REV'),
700 _(b'REV'),
701 ),
701 ),
702 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
702 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
703 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
703 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
704 ]
704 ]
705 + mergetoolopts
705 + mergetoolopts
706 + walkopts
706 + walkopts
707 + commitopts
707 + commitopts
708 + commitopts2,
708 + commitopts2,
709 _(b'[OPTION]... [-r] REV'),
709 _(b'[OPTION]... [-r] REV'),
710 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
710 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
711 )
711 )
712 def backout(ui, repo, node=None, rev=None, **opts):
712 def backout(ui, repo, node=None, rev=None, **opts):
713 '''reverse effect of earlier changeset
713 '''reverse effect of earlier changeset
714
714
715 Prepare a new changeset with the effect of REV undone in the
715 Prepare a new changeset with the effect of REV undone in the
716 current working directory. If no conflicts were encountered,
716 current working directory. If no conflicts were encountered,
717 it will be committed immediately.
717 it will be committed immediately.
718
718
719 If REV is the parent of the working directory, then this new changeset
719 If REV is the parent of the working directory, then this new changeset
720 is committed automatically (unless --no-commit is specified).
720 is committed automatically (unless --no-commit is specified).
721
721
722 .. note::
722 .. note::
723
723
724 :hg:`backout` cannot be used to fix either an unwanted or
724 :hg:`backout` cannot be used to fix either an unwanted or
725 incorrect merge.
725 incorrect merge.
726
726
727 .. container:: verbose
727 .. container:: verbose
728
728
729 Examples:
729 Examples:
730
730
731 - Reverse the effect of the parent of the working directory.
731 - Reverse the effect of the parent of the working directory.
732 This backout will be committed immediately::
732 This backout will be committed immediately::
733
733
734 hg backout -r .
734 hg backout -r .
735
735
736 - Reverse the effect of previous bad revision 23::
736 - Reverse the effect of previous bad revision 23::
737
737
738 hg backout -r 23
738 hg backout -r 23
739
739
740 - Reverse the effect of previous bad revision 23 and
740 - Reverse the effect of previous bad revision 23 and
741 leave changes uncommitted::
741 leave changes uncommitted::
742
742
743 hg backout -r 23 --no-commit
743 hg backout -r 23 --no-commit
744 hg commit -m "Backout revision 23"
744 hg commit -m "Backout revision 23"
745
745
746 By default, the pending changeset will have one parent,
746 By default, the pending changeset will have one parent,
747 maintaining a linear history. With --merge, the pending
747 maintaining a linear history. With --merge, the pending
748 changeset will instead have two parents: the old parent of the
748 changeset will instead have two parents: the old parent of the
749 working directory and a new child of REV that simply undoes REV.
749 working directory and a new child of REV that simply undoes REV.
750
750
751 Before version 1.7, the behavior without --merge was equivalent
751 Before version 1.7, the behavior without --merge was equivalent
752 to specifying --merge followed by :hg:`update --clean .` to
752 to specifying --merge followed by :hg:`update --clean .` to
753 cancel the merge and leave the child of REV as a head to be
753 cancel the merge and leave the child of REV as a head to be
754 merged separately.
754 merged separately.
755
755
756 See :hg:`help dates` for a list of formats valid for -d/--date.
756 See :hg:`help dates` for a list of formats valid for -d/--date.
757
757
758 See :hg:`help revert` for a way to restore files to the state
758 See :hg:`help revert` for a way to restore files to the state
759 of another revision.
759 of another revision.
760
760
761 Returns 0 on success, 1 if nothing to backout or there are unresolved
761 Returns 0 on success, 1 if nothing to backout or there are unresolved
762 files.
762 files.
763 '''
763 '''
764 with repo.wlock(), repo.lock():
764 with repo.wlock(), repo.lock():
765 return _dobackout(ui, repo, node, rev, **opts)
765 return _dobackout(ui, repo, node, rev, **opts)
766
766
767
767
768 def _dobackout(ui, repo, node=None, rev=None, **opts):
768 def _dobackout(ui, repo, node=None, rev=None, **opts):
769 opts = pycompat.byteskwargs(opts)
769 opts = pycompat.byteskwargs(opts)
770 if opts.get(b'commit') and opts.get(b'no_commit'):
770 if opts.get(b'commit') and opts.get(b'no_commit'):
771 raise error.Abort(_(b"cannot use --commit with --no-commit"))
771 raise error.Abort(_(b"cannot use --commit with --no-commit"))
772 if opts.get(b'merge') and opts.get(b'no_commit'):
772 if opts.get(b'merge') and opts.get(b'no_commit'):
773 raise error.Abort(_(b"cannot use --merge with --no-commit"))
773 raise error.Abort(_(b"cannot use --merge with --no-commit"))
774
774
775 if rev and node:
775 if rev and node:
776 raise error.Abort(_(b"please specify just one revision"))
776 raise error.Abort(_(b"please specify just one revision"))
777
777
778 if not rev:
778 if not rev:
779 rev = node
779 rev = node
780
780
781 if not rev:
781 if not rev:
782 raise error.Abort(_(b"please specify a revision to backout"))
782 raise error.Abort(_(b"please specify a revision to backout"))
783
783
784 date = opts.get(b'date')
784 date = opts.get(b'date')
785 if date:
785 if date:
786 opts[b'date'] = dateutil.parsedate(date)
786 opts[b'date'] = dateutil.parsedate(date)
787
787
788 cmdutil.checkunfinished(repo)
788 cmdutil.checkunfinished(repo)
789 cmdutil.bailifchanged(repo)
789 cmdutil.bailifchanged(repo)
790 node = scmutil.revsingle(repo, rev).node()
790 node = scmutil.revsingle(repo, rev).node()
791
791
792 op1, op2 = repo.dirstate.parents()
792 op1, op2 = repo.dirstate.parents()
793 if not repo.changelog.isancestor(node, op1):
793 if not repo.changelog.isancestor(node, op1):
794 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
794 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
795
795
796 p1, p2 = repo.changelog.parents(node)
796 p1, p2 = repo.changelog.parents(node)
797 if p1 == nullid:
797 if p1 == nullid:
798 raise error.Abort(_(b'cannot backout a change with no parents'))
798 raise error.Abort(_(b'cannot backout a change with no parents'))
799 if p2 != nullid:
799 if p2 != nullid:
800 if not opts.get(b'parent'):
800 if not opts.get(b'parent'):
801 raise error.Abort(_(b'cannot backout a merge changeset'))
801 raise error.Abort(_(b'cannot backout a merge changeset'))
802 p = repo.lookup(opts[b'parent'])
802 p = repo.lookup(opts[b'parent'])
803 if p not in (p1, p2):
803 if p not in (p1, p2):
804 raise error.Abort(
804 raise error.Abort(
805 _(b'%s is not a parent of %s') % (short(p), short(node))
805 _(b'%s is not a parent of %s') % (short(p), short(node))
806 )
806 )
807 parent = p
807 parent = p
808 else:
808 else:
809 if opts.get(b'parent'):
809 if opts.get(b'parent'):
810 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
810 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
811 parent = p1
811 parent = p1
812
812
813 # the backout should appear on the same branch
813 # the backout should appear on the same branch
814 branch = repo.dirstate.branch()
814 branch = repo.dirstate.branch()
815 bheads = repo.branchheads(branch)
815 bheads = repo.branchheads(branch)
816 rctx = scmutil.revsingle(repo, hex(parent))
816 rctx = scmutil.revsingle(repo, hex(parent))
817 if not opts.get(b'merge') and op1 != node:
817 if not opts.get(b'merge') and op1 != node:
818 with dirstateguard.dirstateguard(repo, b'backout'):
818 with dirstateguard.dirstateguard(repo, b'backout'):
819 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
819 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
820 with ui.configoverride(overrides, b'backout'):
820 with ui.configoverride(overrides, b'backout'):
821 stats = mergemod.update(
821 stats = mergemod.update(
822 repo,
822 repo,
823 parent,
823 parent,
824 branchmerge=True,
824 branchmerge=True,
825 force=True,
825 force=True,
826 ancestor=node,
826 ancestor=node,
827 mergeancestor=False,
827 mergeancestor=False,
828 )
828 )
829 repo.setparents(op1, op2)
829 repo.setparents(op1, op2)
830 hg._showstats(repo, stats)
830 hg._showstats(repo, stats)
831 if stats.unresolvedcount:
831 if stats.unresolvedcount:
832 repo.ui.status(
832 repo.ui.status(
833 _(b"use 'hg resolve' to retry unresolved file merges\n")
833 _(b"use 'hg resolve' to retry unresolved file merges\n")
834 )
834 )
835 return 1
835 return 1
836 else:
836 else:
837 hg.clean(repo, node, show_stats=False)
837 hg.clean(repo, node, show_stats=False)
838 repo.dirstate.setbranch(branch)
838 repo.dirstate.setbranch(branch)
839 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
839 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
840
840
841 if opts.get(b'no_commit'):
841 if opts.get(b'no_commit'):
842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
843 ui.status(msg % short(node))
843 ui.status(msg % short(node))
844 return 0
844 return 0
845
845
846 def commitfunc(ui, repo, message, match, opts):
846 def commitfunc(ui, repo, message, match, opts):
847 editform = b'backout'
847 editform = b'backout'
848 e = cmdutil.getcommiteditor(
848 e = cmdutil.getcommiteditor(
849 editform=editform, **pycompat.strkwargs(opts)
849 editform=editform, **pycompat.strkwargs(opts)
850 )
850 )
851 if not message:
851 if not message:
852 # we don't translate commit messages
852 # we don't translate commit messages
853 message = b"Backed out changeset %s" % short(node)
853 message = b"Backed out changeset %s" % short(node)
854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
855 return repo.commit(
855 return repo.commit(
856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857 )
857 )
858
858
859 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
859 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
860 if not newnode:
860 if not newnode:
861 ui.status(_(b"nothing changed\n"))
861 ui.status(_(b"nothing changed\n"))
862 return 1
862 return 1
863 cmdutil.commitstatus(repo, newnode, branch, bheads)
863 cmdutil.commitstatus(repo, newnode, branch, bheads)
864
864
865 def nice(node):
865 def nice(node):
866 return b'%d:%s' % (repo.changelog.rev(node), short(node))
866 return b'%d:%s' % (repo.changelog.rev(node), short(node))
867
867
868 ui.status(
868 ui.status(
869 _(b'changeset %s backs out changeset %s\n')
869 _(b'changeset %s backs out changeset %s\n')
870 % (nice(repo.changelog.tip()), nice(node))
870 % (nice(repo.changelog.tip()), nice(node))
871 )
871 )
872 if opts.get(b'merge') and op1 != node:
872 if opts.get(b'merge') and op1 != node:
873 hg.clean(repo, op1, show_stats=False)
873 hg.clean(repo, op1, show_stats=False)
874 ui.status(
874 ui.status(
875 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
875 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
876 )
876 )
877 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
877 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
878 with ui.configoverride(overrides, b'backout'):
878 with ui.configoverride(overrides, b'backout'):
879 return hg.merge(repo[b'tip'])
879 return hg.merge(repo[b'tip'])
880 return 0
880 return 0
881
881
882
882
883 @command(
883 @command(
884 b'bisect',
884 b'bisect',
885 [
885 [
886 (b'r', b'reset', False, _(b'reset bisect state')),
886 (b'r', b'reset', False, _(b'reset bisect state')),
887 (b'g', b'good', False, _(b'mark changeset good')),
887 (b'g', b'good', False, _(b'mark changeset good')),
888 (b'b', b'bad', False, _(b'mark changeset bad')),
888 (b'b', b'bad', False, _(b'mark changeset bad')),
889 (b's', b'skip', False, _(b'skip testing changeset')),
889 (b's', b'skip', False, _(b'skip testing changeset')),
890 (b'e', b'extend', False, _(b'extend the bisect range')),
890 (b'e', b'extend', False, _(b'extend the bisect range')),
891 (
891 (
892 b'c',
892 b'c',
893 b'command',
893 b'command',
894 b'',
894 b'',
895 _(b'use command to check changeset state'),
895 _(b'use command to check changeset state'),
896 _(b'CMD'),
896 _(b'CMD'),
897 ),
897 ),
898 (b'U', b'noupdate', False, _(b'do not update to target')),
898 (b'U', b'noupdate', False, _(b'do not update to target')),
899 ],
899 ],
900 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
900 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
901 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
901 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
902 )
902 )
903 def bisect(
903 def bisect(
904 ui,
904 ui,
905 repo,
905 repo,
906 rev=None,
906 rev=None,
907 extra=None,
907 extra=None,
908 command=None,
908 command=None,
909 reset=None,
909 reset=None,
910 good=None,
910 good=None,
911 bad=None,
911 bad=None,
912 skip=None,
912 skip=None,
913 extend=None,
913 extend=None,
914 noupdate=None,
914 noupdate=None,
915 ):
915 ):
916 """subdivision search of changesets
916 """subdivision search of changesets
917
917
918 This command helps to find changesets which introduce problems. To
918 This command helps to find changesets which introduce problems. To
919 use, mark the earliest changeset you know exhibits the problem as
919 use, mark the earliest changeset you know exhibits the problem as
920 bad, then mark the latest changeset which is free from the problem
920 bad, then mark the latest changeset which is free from the problem
921 as good. Bisect will update your working directory to a revision
921 as good. Bisect will update your working directory to a revision
922 for testing (unless the -U/--noupdate option is specified). Once
922 for testing (unless the -U/--noupdate option is specified). Once
923 you have performed tests, mark the working directory as good or
923 you have performed tests, mark the working directory as good or
924 bad, and bisect will either update to another candidate changeset
924 bad, and bisect will either update to another candidate changeset
925 or announce that it has found the bad revision.
925 or announce that it has found the bad revision.
926
926
927 As a shortcut, you can also use the revision argument to mark a
927 As a shortcut, you can also use the revision argument to mark a
928 revision as good or bad without checking it out first.
928 revision as good or bad without checking it out first.
929
929
930 If you supply a command, it will be used for automatic bisection.
930 If you supply a command, it will be used for automatic bisection.
931 The environment variable HG_NODE will contain the ID of the
931 The environment variable HG_NODE will contain the ID of the
932 changeset being tested. The exit status of the command will be
932 changeset being tested. The exit status of the command will be
933 used to mark revisions as good or bad: status 0 means good, 125
933 used to mark revisions as good or bad: status 0 means good, 125
934 means to skip the revision, 127 (command not found) will abort the
934 means to skip the revision, 127 (command not found) will abort the
935 bisection, and any other non-zero exit status means the revision
935 bisection, and any other non-zero exit status means the revision
936 is bad.
936 is bad.
937
937
938 .. container:: verbose
938 .. container:: verbose
939
939
940 Some examples:
940 Some examples:
941
941
942 - start a bisection with known bad revision 34, and good revision 12::
942 - start a bisection with known bad revision 34, and good revision 12::
943
943
944 hg bisect --bad 34
944 hg bisect --bad 34
945 hg bisect --good 12
945 hg bisect --good 12
946
946
947 - advance the current bisection by marking current revision as good or
947 - advance the current bisection by marking current revision as good or
948 bad::
948 bad::
949
949
950 hg bisect --good
950 hg bisect --good
951 hg bisect --bad
951 hg bisect --bad
952
952
953 - mark the current revision, or a known revision, to be skipped (e.g. if
953 - mark the current revision, or a known revision, to be skipped (e.g. if
954 that revision is not usable because of another issue)::
954 that revision is not usable because of another issue)::
955
955
956 hg bisect --skip
956 hg bisect --skip
957 hg bisect --skip 23
957 hg bisect --skip 23
958
958
959 - skip all revisions that do not touch directories ``foo`` or ``bar``::
959 - skip all revisions that do not touch directories ``foo`` or ``bar``::
960
960
961 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
961 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
962
962
963 - forget the current bisection::
963 - forget the current bisection::
964
964
965 hg bisect --reset
965 hg bisect --reset
966
966
967 - use 'make && make tests' to automatically find the first broken
967 - use 'make && make tests' to automatically find the first broken
968 revision::
968 revision::
969
969
970 hg bisect --reset
970 hg bisect --reset
971 hg bisect --bad 34
971 hg bisect --bad 34
972 hg bisect --good 12
972 hg bisect --good 12
973 hg bisect --command "make && make tests"
973 hg bisect --command "make && make tests"
974
974
975 - see all changesets whose states are already known in the current
975 - see all changesets whose states are already known in the current
976 bisection::
976 bisection::
977
977
978 hg log -r "bisect(pruned)"
978 hg log -r "bisect(pruned)"
979
979
980 - see the changeset currently being bisected (especially useful
980 - see the changeset currently being bisected (especially useful
981 if running with -U/--noupdate)::
981 if running with -U/--noupdate)::
982
982
983 hg log -r "bisect(current)"
983 hg log -r "bisect(current)"
984
984
985 - see all changesets that took part in the current bisection::
985 - see all changesets that took part in the current bisection::
986
986
987 hg log -r "bisect(range)"
987 hg log -r "bisect(range)"
988
988
989 - you can even get a nice graph::
989 - you can even get a nice graph::
990
990
991 hg log --graph -r "bisect(range)"
991 hg log --graph -r "bisect(range)"
992
992
993 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
993 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
994
994
995 Returns 0 on success.
995 Returns 0 on success.
996 """
996 """
997 # backward compatibility
997 # backward compatibility
998 if rev in b"good bad reset init".split():
998 if rev in b"good bad reset init".split():
999 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
999 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1000 cmd, rev, extra = rev, extra, None
1000 cmd, rev, extra = rev, extra, None
1001 if cmd == b"good":
1001 if cmd == b"good":
1002 good = True
1002 good = True
1003 elif cmd == b"bad":
1003 elif cmd == b"bad":
1004 bad = True
1004 bad = True
1005 else:
1005 else:
1006 reset = True
1006 reset = True
1007 elif extra:
1007 elif extra:
1008 raise error.Abort(_(b'incompatible arguments'))
1008 raise error.Abort(_(b'incompatible arguments'))
1009
1009
1010 incompatibles = {
1010 incompatibles = {
1011 b'--bad': bad,
1011 b'--bad': bad,
1012 b'--command': bool(command),
1012 b'--command': bool(command),
1013 b'--extend': extend,
1013 b'--extend': extend,
1014 b'--good': good,
1014 b'--good': good,
1015 b'--reset': reset,
1015 b'--reset': reset,
1016 b'--skip': skip,
1016 b'--skip': skip,
1017 }
1017 }
1018
1018
1019 enabled = [x for x in incompatibles if incompatibles[x]]
1019 enabled = [x for x in incompatibles if incompatibles[x]]
1020
1020
1021 if len(enabled) > 1:
1021 if len(enabled) > 1:
1022 raise error.Abort(
1022 raise error.Abort(
1023 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1023 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1024 )
1024 )
1025
1025
1026 if reset:
1026 if reset:
1027 hbisect.resetstate(repo)
1027 hbisect.resetstate(repo)
1028 return
1028 return
1029
1029
1030 state = hbisect.load_state(repo)
1030 state = hbisect.load_state(repo)
1031
1031
1032 # update state
1032 # update state
1033 if good or bad or skip:
1033 if good or bad or skip:
1034 if rev:
1034 if rev:
1035 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1035 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1036 else:
1036 else:
1037 nodes = [repo.lookup(b'.')]
1037 nodes = [repo.lookup(b'.')]
1038 if good:
1038 if good:
1039 state[b'good'] += nodes
1039 state[b'good'] += nodes
1040 elif bad:
1040 elif bad:
1041 state[b'bad'] += nodes
1041 state[b'bad'] += nodes
1042 elif skip:
1042 elif skip:
1043 state[b'skip'] += nodes
1043 state[b'skip'] += nodes
1044 hbisect.save_state(repo, state)
1044 hbisect.save_state(repo, state)
1045 if not (state[b'good'] and state[b'bad']):
1045 if not (state[b'good'] and state[b'bad']):
1046 return
1046 return
1047
1047
1048 def mayupdate(repo, node, show_stats=True):
1048 def mayupdate(repo, node, show_stats=True):
1049 """common used update sequence"""
1049 """common used update sequence"""
1050 if noupdate:
1050 if noupdate:
1051 return
1051 return
1052 cmdutil.checkunfinished(repo)
1052 cmdutil.checkunfinished(repo)
1053 cmdutil.bailifchanged(repo)
1053 cmdutil.bailifchanged(repo)
1054 return hg.clean(repo, node, show_stats=show_stats)
1054 return hg.clean(repo, node, show_stats=show_stats)
1055
1055
1056 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1056 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1057
1057
1058 if command:
1058 if command:
1059 changesets = 1
1059 changesets = 1
1060 if noupdate:
1060 if noupdate:
1061 try:
1061 try:
1062 node = state[b'current'][0]
1062 node = state[b'current'][0]
1063 except LookupError:
1063 except LookupError:
1064 raise error.Abort(
1064 raise error.Abort(
1065 _(
1065 _(
1066 b'current bisect revision is unknown - '
1066 b'current bisect revision is unknown - '
1067 b'start a new bisect to fix'
1067 b'start a new bisect to fix'
1068 )
1068 )
1069 )
1069 )
1070 else:
1070 else:
1071 node, p2 = repo.dirstate.parents()
1071 node, p2 = repo.dirstate.parents()
1072 if p2 != nullid:
1072 if p2 != nullid:
1073 raise error.Abort(_(b'current bisect revision is a merge'))
1073 raise error.Abort(_(b'current bisect revision is a merge'))
1074 if rev:
1074 if rev:
1075 node = repo[scmutil.revsingle(repo, rev, node)].node()
1075 node = repo[scmutil.revsingle(repo, rev, node)].node()
1076 with hbisect.restore_state(repo, state, node):
1076 with hbisect.restore_state(repo, state, node):
1077 while changesets:
1077 while changesets:
1078 # update state
1078 # update state
1079 state[b'current'] = [node]
1079 state[b'current'] = [node]
1080 hbisect.save_state(repo, state)
1080 hbisect.save_state(repo, state)
1081 status = ui.system(
1081 status = ui.system(
1082 command,
1082 command,
1083 environ={b'HG_NODE': hex(node)},
1083 environ={b'HG_NODE': hex(node)},
1084 blockedtag=b'bisect_check',
1084 blockedtag=b'bisect_check',
1085 )
1085 )
1086 if status == 125:
1086 if status == 125:
1087 transition = b"skip"
1087 transition = b"skip"
1088 elif status == 0:
1088 elif status == 0:
1089 transition = b"good"
1089 transition = b"good"
1090 # status < 0 means process was killed
1090 # status < 0 means process was killed
1091 elif status == 127:
1091 elif status == 127:
1092 raise error.Abort(_(b"failed to execute %s") % command)
1092 raise error.Abort(_(b"failed to execute %s") % command)
1093 elif status < 0:
1093 elif status < 0:
1094 raise error.Abort(_(b"%s killed") % command)
1094 raise error.Abort(_(b"%s killed") % command)
1095 else:
1095 else:
1096 transition = b"bad"
1096 transition = b"bad"
1097 state[transition].append(node)
1097 state[transition].append(node)
1098 ctx = repo[node]
1098 ctx = repo[node]
1099 ui.status(
1099 ui.status(
1100 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1100 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1101 )
1101 )
1102 hbisect.checkstate(state)
1102 hbisect.checkstate(state)
1103 # bisect
1103 # bisect
1104 nodes, changesets, bgood = hbisect.bisect(repo, state)
1104 nodes, changesets, bgood = hbisect.bisect(repo, state)
1105 # update to next check
1105 # update to next check
1106 node = nodes[0]
1106 node = nodes[0]
1107 mayupdate(repo, node, show_stats=False)
1107 mayupdate(repo, node, show_stats=False)
1108 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1108 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1109 return
1109 return
1110
1110
1111 hbisect.checkstate(state)
1111 hbisect.checkstate(state)
1112
1112
1113 # actually bisect
1113 # actually bisect
1114 nodes, changesets, good = hbisect.bisect(repo, state)
1114 nodes, changesets, good = hbisect.bisect(repo, state)
1115 if extend:
1115 if extend:
1116 if not changesets:
1116 if not changesets:
1117 extendnode = hbisect.extendrange(repo, state, nodes, good)
1117 extendnode = hbisect.extendrange(repo, state, nodes, good)
1118 if extendnode is not None:
1118 if extendnode is not None:
1119 ui.write(
1119 ui.write(
1120 _(b"Extending search to changeset %d:%s\n")
1120 _(b"Extending search to changeset %d:%s\n")
1121 % (extendnode.rev(), extendnode)
1121 % (extendnode.rev(), extendnode)
1122 )
1122 )
1123 state[b'current'] = [extendnode.node()]
1123 state[b'current'] = [extendnode.node()]
1124 hbisect.save_state(repo, state)
1124 hbisect.save_state(repo, state)
1125 return mayupdate(repo, extendnode.node())
1125 return mayupdate(repo, extendnode.node())
1126 raise error.Abort(_(b"nothing to extend"))
1126 raise error.Abort(_(b"nothing to extend"))
1127
1127
1128 if changesets == 0:
1128 if changesets == 0:
1129 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1129 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1130 else:
1130 else:
1131 assert len(nodes) == 1 # only a single node can be tested next
1131 assert len(nodes) == 1 # only a single node can be tested next
1132 node = nodes[0]
1132 node = nodes[0]
1133 # compute the approximate number of remaining tests
1133 # compute the approximate number of remaining tests
1134 tests, size = 0, 2
1134 tests, size = 0, 2
1135 while size <= changesets:
1135 while size <= changesets:
1136 tests, size = tests + 1, size * 2
1136 tests, size = tests + 1, size * 2
1137 rev = repo.changelog.rev(node)
1137 rev = repo.changelog.rev(node)
1138 ui.write(
1138 ui.write(
1139 _(
1139 _(
1140 b"Testing changeset %d:%s "
1140 b"Testing changeset %d:%s "
1141 b"(%d changesets remaining, ~%d tests)\n"
1141 b"(%d changesets remaining, ~%d tests)\n"
1142 )
1142 )
1143 % (rev, short(node), changesets, tests)
1143 % (rev, short(node), changesets, tests)
1144 )
1144 )
1145 state[b'current'] = [node]
1145 state[b'current'] = [node]
1146 hbisect.save_state(repo, state)
1146 hbisect.save_state(repo, state)
1147 return mayupdate(repo, node)
1147 return mayupdate(repo, node)
1148
1148
1149
1149
1150 @command(
1150 @command(
1151 b'bookmarks|bookmark',
1151 b'bookmarks|bookmark',
1152 [
1152 [
1153 (b'f', b'force', False, _(b'force')),
1153 (b'f', b'force', False, _(b'force')),
1154 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1154 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1155 (b'd', b'delete', False, _(b'delete a given bookmark')),
1155 (b'd', b'delete', False, _(b'delete a given bookmark')),
1156 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1156 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1157 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1157 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1158 (b'l', b'list', False, _(b'list existing bookmarks')),
1158 (b'l', b'list', False, _(b'list existing bookmarks')),
1159 ]
1159 ]
1160 + formatteropts,
1160 + formatteropts,
1161 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1161 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1162 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1162 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1163 )
1163 )
1164 def bookmark(ui, repo, *names, **opts):
1164 def bookmark(ui, repo, *names, **opts):
1165 '''create a new bookmark or list existing bookmarks
1165 '''create a new bookmark or list existing bookmarks
1166
1166
1167 Bookmarks are labels on changesets to help track lines of development.
1167 Bookmarks are labels on changesets to help track lines of development.
1168 Bookmarks are unversioned and can be moved, renamed and deleted.
1168 Bookmarks are unversioned and can be moved, renamed and deleted.
1169 Deleting or moving a bookmark has no effect on the associated changesets.
1169 Deleting or moving a bookmark has no effect on the associated changesets.
1170
1170
1171 Creating or updating to a bookmark causes it to be marked as 'active'.
1171 Creating or updating to a bookmark causes it to be marked as 'active'.
1172 The active bookmark is indicated with a '*'.
1172 The active bookmark is indicated with a '*'.
1173 When a commit is made, the active bookmark will advance to the new commit.
1173 When a commit is made, the active bookmark will advance to the new commit.
1174 A plain :hg:`update` will also advance an active bookmark, if possible.
1174 A plain :hg:`update` will also advance an active bookmark, if possible.
1175 Updating away from a bookmark will cause it to be deactivated.
1175 Updating away from a bookmark will cause it to be deactivated.
1176
1176
1177 Bookmarks can be pushed and pulled between repositories (see
1177 Bookmarks can be pushed and pulled between repositories (see
1178 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1178 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1179 diverged, a new 'divergent bookmark' of the form 'name@path' will
1179 diverged, a new 'divergent bookmark' of the form 'name@path' will
1180 be created. Using :hg:`merge` will resolve the divergence.
1180 be created. Using :hg:`merge` will resolve the divergence.
1181
1181
1182 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1182 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1183 the active bookmark's name.
1183 the active bookmark's name.
1184
1184
1185 A bookmark named '@' has the special property that :hg:`clone` will
1185 A bookmark named '@' has the special property that :hg:`clone` will
1186 check it out by default if it exists.
1186 check it out by default if it exists.
1187
1187
1188 .. container:: verbose
1188 .. container:: verbose
1189
1189
1190 Template:
1190 Template:
1191
1191
1192 The following keywords are supported in addition to the common template
1192 The following keywords are supported in addition to the common template
1193 keywords and functions such as ``{bookmark}``. See also
1193 keywords and functions such as ``{bookmark}``. See also
1194 :hg:`help templates`.
1194 :hg:`help templates`.
1195
1195
1196 :active: Boolean. True if the bookmark is active.
1196 :active: Boolean. True if the bookmark is active.
1197
1197
1198 Examples:
1198 Examples:
1199
1199
1200 - create an active bookmark for a new line of development::
1200 - create an active bookmark for a new line of development::
1201
1201
1202 hg book new-feature
1202 hg book new-feature
1203
1203
1204 - create an inactive bookmark as a place marker::
1204 - create an inactive bookmark as a place marker::
1205
1205
1206 hg book -i reviewed
1206 hg book -i reviewed
1207
1207
1208 - create an inactive bookmark on another changeset::
1208 - create an inactive bookmark on another changeset::
1209
1209
1210 hg book -r .^ tested
1210 hg book -r .^ tested
1211
1211
1212 - rename bookmark turkey to dinner::
1212 - rename bookmark turkey to dinner::
1213
1213
1214 hg book -m turkey dinner
1214 hg book -m turkey dinner
1215
1215
1216 - move the '@' bookmark from another branch::
1216 - move the '@' bookmark from another branch::
1217
1217
1218 hg book -f @
1218 hg book -f @
1219
1219
1220 - print only the active bookmark name::
1220 - print only the active bookmark name::
1221
1221
1222 hg book -ql .
1222 hg book -ql .
1223 '''
1223 '''
1224 opts = pycompat.byteskwargs(opts)
1224 opts = pycompat.byteskwargs(opts)
1225 force = opts.get(b'force')
1225 force = opts.get(b'force')
1226 rev = opts.get(b'rev')
1226 rev = opts.get(b'rev')
1227 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1227 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1228
1228
1229 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1229 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1230 if action:
1230 if action:
1231 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1231 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1232 elif names or rev:
1232 elif names or rev:
1233 action = b'add'
1233 action = b'add'
1234 elif inactive:
1234 elif inactive:
1235 action = b'inactive' # meaning deactivate
1235 action = b'inactive' # meaning deactivate
1236 else:
1236 else:
1237 action = b'list'
1237 action = b'list'
1238
1238
1239 cmdutil.check_incompatible_arguments(
1239 cmdutil.check_incompatible_arguments(
1240 opts, b'inactive', [b'delete', b'list']
1240 opts, b'inactive', [b'delete', b'list']
1241 )
1241 )
1242 if not names and action in {b'add', b'delete'}:
1242 if not names and action in {b'add', b'delete'}:
1243 raise error.Abort(_(b"bookmark name required"))
1243 raise error.Abort(_(b"bookmark name required"))
1244
1244
1245 if action in {b'add', b'delete', b'rename', b'inactive'}:
1245 if action in {b'add', b'delete', b'rename', b'inactive'}:
1246 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1246 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1247 if action == b'delete':
1247 if action == b'delete':
1248 names = pycompat.maplist(repo._bookmarks.expandname, names)
1248 names = pycompat.maplist(repo._bookmarks.expandname, names)
1249 bookmarks.delete(repo, tr, names)
1249 bookmarks.delete(repo, tr, names)
1250 elif action == b'rename':
1250 elif action == b'rename':
1251 if not names:
1251 if not names:
1252 raise error.Abort(_(b"new bookmark name required"))
1252 raise error.Abort(_(b"new bookmark name required"))
1253 elif len(names) > 1:
1253 elif len(names) > 1:
1254 raise error.Abort(_(b"only one new bookmark name allowed"))
1254 raise error.Abort(_(b"only one new bookmark name allowed"))
1255 oldname = repo._bookmarks.expandname(opts[b'rename'])
1255 oldname = repo._bookmarks.expandname(opts[b'rename'])
1256 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1256 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1257 elif action == b'add':
1257 elif action == b'add':
1258 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1258 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1259 elif action == b'inactive':
1259 elif action == b'inactive':
1260 if len(repo._bookmarks) == 0:
1260 if len(repo._bookmarks) == 0:
1261 ui.status(_(b"no bookmarks set\n"))
1261 ui.status(_(b"no bookmarks set\n"))
1262 elif not repo._activebookmark:
1262 elif not repo._activebookmark:
1263 ui.status(_(b"no active bookmark\n"))
1263 ui.status(_(b"no active bookmark\n"))
1264 else:
1264 else:
1265 bookmarks.deactivate(repo)
1265 bookmarks.deactivate(repo)
1266 elif action == b'list':
1266 elif action == b'list':
1267 names = pycompat.maplist(repo._bookmarks.expandname, names)
1267 names = pycompat.maplist(repo._bookmarks.expandname, names)
1268 with ui.formatter(b'bookmarks', opts) as fm:
1268 with ui.formatter(b'bookmarks', opts) as fm:
1269 bookmarks.printbookmarks(ui, repo, fm, names)
1269 bookmarks.printbookmarks(ui, repo, fm, names)
1270 else:
1270 else:
1271 raise error.ProgrammingError(b'invalid action: %s' % action)
1271 raise error.ProgrammingError(b'invalid action: %s' % action)
1272
1272
1273
1273
1274 @command(
1274 @command(
1275 b'branch',
1275 b'branch',
1276 [
1276 [
1277 (
1277 (
1278 b'f',
1278 b'f',
1279 b'force',
1279 b'force',
1280 None,
1280 None,
1281 _(b'set branch name even if it shadows an existing branch'),
1281 _(b'set branch name even if it shadows an existing branch'),
1282 ),
1282 ),
1283 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1283 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1284 (
1284 (
1285 b'r',
1285 b'r',
1286 b'rev',
1286 b'rev',
1287 [],
1287 [],
1288 _(b'change branches of the given revs (EXPERIMENTAL)'),
1288 _(b'change branches of the given revs (EXPERIMENTAL)'),
1289 ),
1289 ),
1290 ],
1290 ],
1291 _(b'[-fC] [NAME]'),
1291 _(b'[-fC] [NAME]'),
1292 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1292 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1293 )
1293 )
1294 def branch(ui, repo, label=None, **opts):
1294 def branch(ui, repo, label=None, **opts):
1295 """set or show the current branch name
1295 """set or show the current branch name
1296
1296
1297 .. note::
1297 .. note::
1298
1298
1299 Branch names are permanent and global. Use :hg:`bookmark` to create a
1299 Branch names are permanent and global. Use :hg:`bookmark` to create a
1300 light-weight bookmark instead. See :hg:`help glossary` for more
1300 light-weight bookmark instead. See :hg:`help glossary` for more
1301 information about named branches and bookmarks.
1301 information about named branches and bookmarks.
1302
1302
1303 With no argument, show the current branch name. With one argument,
1303 With no argument, show the current branch name. With one argument,
1304 set the working directory branch name (the branch will not exist
1304 set the working directory branch name (the branch will not exist
1305 in the repository until the next commit). Standard practice
1305 in the repository until the next commit). Standard practice
1306 recommends that primary development take place on the 'default'
1306 recommends that primary development take place on the 'default'
1307 branch.
1307 branch.
1308
1308
1309 Unless -f/--force is specified, branch will not let you set a
1309 Unless -f/--force is specified, branch will not let you set a
1310 branch name that already exists.
1310 branch name that already exists.
1311
1311
1312 Use -C/--clean to reset the working directory branch to that of
1312 Use -C/--clean to reset the working directory branch to that of
1313 the parent of the working directory, negating a previous branch
1313 the parent of the working directory, negating a previous branch
1314 change.
1314 change.
1315
1315
1316 Use the command :hg:`update` to switch to an existing branch. Use
1316 Use the command :hg:`update` to switch to an existing branch. Use
1317 :hg:`commit --close-branch` to mark this branch head as closed.
1317 :hg:`commit --close-branch` to mark this branch head as closed.
1318 When all heads of a branch are closed, the branch will be
1318 When all heads of a branch are closed, the branch will be
1319 considered closed.
1319 considered closed.
1320
1320
1321 Returns 0 on success.
1321 Returns 0 on success.
1322 """
1322 """
1323 opts = pycompat.byteskwargs(opts)
1323 opts = pycompat.byteskwargs(opts)
1324 revs = opts.get(b'rev')
1324 revs = opts.get(b'rev')
1325 if label:
1325 if label:
1326 label = label.strip()
1326 label = label.strip()
1327
1327
1328 if not opts.get(b'clean') and not label:
1328 if not opts.get(b'clean') and not label:
1329 if revs:
1329 if revs:
1330 raise error.Abort(_(b"no branch name specified for the revisions"))
1330 raise error.Abort(_(b"no branch name specified for the revisions"))
1331 ui.write(b"%s\n" % repo.dirstate.branch())
1331 ui.write(b"%s\n" % repo.dirstate.branch())
1332 return
1332 return
1333
1333
1334 with repo.wlock():
1334 with repo.wlock():
1335 if opts.get(b'clean'):
1335 if opts.get(b'clean'):
1336 label = repo[b'.'].branch()
1336 label = repo[b'.'].branch()
1337 repo.dirstate.setbranch(label)
1337 repo.dirstate.setbranch(label)
1338 ui.status(_(b'reset working directory to branch %s\n') % label)
1338 ui.status(_(b'reset working directory to branch %s\n') % label)
1339 elif label:
1339 elif label:
1340
1340
1341 scmutil.checknewlabel(repo, label, b'branch')
1341 scmutil.checknewlabel(repo, label, b'branch')
1342 if revs:
1342 if revs:
1343 return cmdutil.changebranch(ui, repo, revs, label, opts)
1343 return cmdutil.changebranch(ui, repo, revs, label, opts)
1344
1344
1345 if not opts.get(b'force') and label in repo.branchmap():
1345 if not opts.get(b'force') and label in repo.branchmap():
1346 if label not in [p.branch() for p in repo[None].parents()]:
1346 if label not in [p.branch() for p in repo[None].parents()]:
1347 raise error.Abort(
1347 raise error.Abort(
1348 _(b'a branch of the same name already exists'),
1348 _(b'a branch of the same name already exists'),
1349 # i18n: "it" refers to an existing branch
1349 # i18n: "it" refers to an existing branch
1350 hint=_(b"use 'hg update' to switch to it"),
1350 hint=_(b"use 'hg update' to switch to it"),
1351 )
1351 )
1352
1352
1353 repo.dirstate.setbranch(label)
1353 repo.dirstate.setbranch(label)
1354 ui.status(_(b'marked working directory as branch %s\n') % label)
1354 ui.status(_(b'marked working directory as branch %s\n') % label)
1355
1355
1356 # find any open named branches aside from default
1356 # find any open named branches aside from default
1357 for n, h, t, c in repo.branchmap().iterbranches():
1357 for n, h, t, c in repo.branchmap().iterbranches():
1358 if n != b"default" and not c:
1358 if n != b"default" and not c:
1359 return 0
1359 return 0
1360 ui.status(
1360 ui.status(
1361 _(
1361 _(
1362 b'(branches are permanent and global, '
1362 b'(branches are permanent and global, '
1363 b'did you want a bookmark?)\n'
1363 b'did you want a bookmark?)\n'
1364 )
1364 )
1365 )
1365 )
1366
1366
1367
1367
1368 @command(
1368 @command(
1369 b'branches',
1369 b'branches',
1370 [
1370 [
1371 (
1371 (
1372 b'a',
1372 b'a',
1373 b'active',
1373 b'active',
1374 False,
1374 False,
1375 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1375 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1376 ),
1376 ),
1377 (b'c', b'closed', False, _(b'show normal and closed branches')),
1377 (b'c', b'closed', False, _(b'show normal and closed branches')),
1378 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1378 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1379 ]
1379 ]
1380 + formatteropts,
1380 + formatteropts,
1381 _(b'[-c]'),
1381 _(b'[-c]'),
1382 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1382 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1383 intents={INTENT_READONLY},
1383 intents={INTENT_READONLY},
1384 )
1384 )
1385 def branches(ui, repo, active=False, closed=False, **opts):
1385 def branches(ui, repo, active=False, closed=False, **opts):
1386 """list repository named branches
1386 """list repository named branches
1387
1387
1388 List the repository's named branches, indicating which ones are
1388 List the repository's named branches, indicating which ones are
1389 inactive. If -c/--closed is specified, also list branches which have
1389 inactive. If -c/--closed is specified, also list branches which have
1390 been marked closed (see :hg:`commit --close-branch`).
1390 been marked closed (see :hg:`commit --close-branch`).
1391
1391
1392 Use the command :hg:`update` to switch to an existing branch.
1392 Use the command :hg:`update` to switch to an existing branch.
1393
1393
1394 .. container:: verbose
1394 .. container:: verbose
1395
1395
1396 Template:
1396 Template:
1397
1397
1398 The following keywords are supported in addition to the common template
1398 The following keywords are supported in addition to the common template
1399 keywords and functions such as ``{branch}``. See also
1399 keywords and functions such as ``{branch}``. See also
1400 :hg:`help templates`.
1400 :hg:`help templates`.
1401
1401
1402 :active: Boolean. True if the branch is active.
1402 :active: Boolean. True if the branch is active.
1403 :closed: Boolean. True if the branch is closed.
1403 :closed: Boolean. True if the branch is closed.
1404 :current: Boolean. True if it is the current branch.
1404 :current: Boolean. True if it is the current branch.
1405
1405
1406 Returns 0.
1406 Returns 0.
1407 """
1407 """
1408
1408
1409 opts = pycompat.byteskwargs(opts)
1409 opts = pycompat.byteskwargs(opts)
1410 revs = opts.get(b'rev')
1410 revs = opts.get(b'rev')
1411 selectedbranches = None
1411 selectedbranches = None
1412 if revs:
1412 if revs:
1413 revs = scmutil.revrange(repo, revs)
1413 revs = scmutil.revrange(repo, revs)
1414 getbi = repo.revbranchcache().branchinfo
1414 getbi = repo.revbranchcache().branchinfo
1415 selectedbranches = {getbi(r)[0] for r in revs}
1415 selectedbranches = {getbi(r)[0] for r in revs}
1416
1416
1417 ui.pager(b'branches')
1417 ui.pager(b'branches')
1418 fm = ui.formatter(b'branches', opts)
1418 fm = ui.formatter(b'branches', opts)
1419 hexfunc = fm.hexfunc
1419 hexfunc = fm.hexfunc
1420
1420
1421 allheads = set(repo.heads())
1421 allheads = set(repo.heads())
1422 branches = []
1422 branches = []
1423 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1423 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1424 if selectedbranches is not None and tag not in selectedbranches:
1424 if selectedbranches is not None and tag not in selectedbranches:
1425 continue
1425 continue
1426 isactive = False
1426 isactive = False
1427 if not isclosed:
1427 if not isclosed:
1428 openheads = set(repo.branchmap().iteropen(heads))
1428 openheads = set(repo.branchmap().iteropen(heads))
1429 isactive = bool(openheads & allheads)
1429 isactive = bool(openheads & allheads)
1430 branches.append((tag, repo[tip], isactive, not isclosed))
1430 branches.append((tag, repo[tip], isactive, not isclosed))
1431 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1431 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1432
1432
1433 for tag, ctx, isactive, isopen in branches:
1433 for tag, ctx, isactive, isopen in branches:
1434 if active and not isactive:
1434 if active and not isactive:
1435 continue
1435 continue
1436 if isactive:
1436 if isactive:
1437 label = b'branches.active'
1437 label = b'branches.active'
1438 notice = b''
1438 notice = b''
1439 elif not isopen:
1439 elif not isopen:
1440 if not closed:
1440 if not closed:
1441 continue
1441 continue
1442 label = b'branches.closed'
1442 label = b'branches.closed'
1443 notice = _(b' (closed)')
1443 notice = _(b' (closed)')
1444 else:
1444 else:
1445 label = b'branches.inactive'
1445 label = b'branches.inactive'
1446 notice = _(b' (inactive)')
1446 notice = _(b' (inactive)')
1447 current = tag == repo.dirstate.branch()
1447 current = tag == repo.dirstate.branch()
1448 if current:
1448 if current:
1449 label = b'branches.current'
1449 label = b'branches.current'
1450
1450
1451 fm.startitem()
1451 fm.startitem()
1452 fm.write(b'branch', b'%s', tag, label=label)
1452 fm.write(b'branch', b'%s', tag, label=label)
1453 rev = ctx.rev()
1453 rev = ctx.rev()
1454 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1454 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1455 fmt = b' ' * padsize + b' %d:%s'
1455 fmt = b' ' * padsize + b' %d:%s'
1456 fm.condwrite(
1456 fm.condwrite(
1457 not ui.quiet,
1457 not ui.quiet,
1458 b'rev node',
1458 b'rev node',
1459 fmt,
1459 fmt,
1460 rev,
1460 rev,
1461 hexfunc(ctx.node()),
1461 hexfunc(ctx.node()),
1462 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1462 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1463 )
1463 )
1464 fm.context(ctx=ctx)
1464 fm.context(ctx=ctx)
1465 fm.data(active=isactive, closed=not isopen, current=current)
1465 fm.data(active=isactive, closed=not isopen, current=current)
1466 if not ui.quiet:
1466 if not ui.quiet:
1467 fm.plain(notice)
1467 fm.plain(notice)
1468 fm.plain(b'\n')
1468 fm.plain(b'\n')
1469 fm.end()
1469 fm.end()
1470
1470
1471
1471
1472 @command(
1472 @command(
1473 b'bundle',
1473 b'bundle',
1474 [
1474 [
1475 (
1475 (
1476 b'f',
1476 b'f',
1477 b'force',
1477 b'force',
1478 None,
1478 None,
1479 _(b'run even when the destination is unrelated'),
1479 _(b'run even when the destination is unrelated'),
1480 ),
1480 ),
1481 (
1481 (
1482 b'r',
1482 b'r',
1483 b'rev',
1483 b'rev',
1484 [],
1484 [],
1485 _(b'a changeset intended to be added to the destination'),
1485 _(b'a changeset intended to be added to the destination'),
1486 _(b'REV'),
1486 _(b'REV'),
1487 ),
1487 ),
1488 (
1488 (
1489 b'b',
1489 b'b',
1490 b'branch',
1490 b'branch',
1491 [],
1491 [],
1492 _(b'a specific branch you would like to bundle'),
1492 _(b'a specific branch you would like to bundle'),
1493 _(b'BRANCH'),
1493 _(b'BRANCH'),
1494 ),
1494 ),
1495 (
1495 (
1496 b'',
1496 b'',
1497 b'base',
1497 b'base',
1498 [],
1498 [],
1499 _(b'a base changeset assumed to be available at the destination'),
1499 _(b'a base changeset assumed to be available at the destination'),
1500 _(b'REV'),
1500 _(b'REV'),
1501 ),
1501 ),
1502 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1502 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1503 (
1503 (
1504 b't',
1504 b't',
1505 b'type',
1505 b'type',
1506 b'bzip2',
1506 b'bzip2',
1507 _(b'bundle compression type to use'),
1507 _(b'bundle compression type to use'),
1508 _(b'TYPE'),
1508 _(b'TYPE'),
1509 ),
1509 ),
1510 ]
1510 ]
1511 + remoteopts,
1511 + remoteopts,
1512 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1512 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1513 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1513 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1514 )
1514 )
1515 def bundle(ui, repo, fname, dest=None, **opts):
1515 def bundle(ui, repo, fname, dest=None, **opts):
1516 """create a bundle file
1516 """create a bundle file
1517
1517
1518 Generate a bundle file containing data to be transferred to another
1518 Generate a bundle file containing data to be transferred to another
1519 repository.
1519 repository.
1520
1520
1521 To create a bundle containing all changesets, use -a/--all
1521 To create a bundle containing all changesets, use -a/--all
1522 (or --base null). Otherwise, hg assumes the destination will have
1522 (or --base null). Otherwise, hg assumes the destination will have
1523 all the nodes you specify with --base parameters. Otherwise, hg
1523 all the nodes you specify with --base parameters. Otherwise, hg
1524 will assume the repository has all the nodes in destination, or
1524 will assume the repository has all the nodes in destination, or
1525 default-push/default if no destination is specified, where destination
1525 default-push/default if no destination is specified, where destination
1526 is the repository you provide through DEST option.
1526 is the repository you provide through DEST option.
1527
1527
1528 You can change bundle format with the -t/--type option. See
1528 You can change bundle format with the -t/--type option. See
1529 :hg:`help bundlespec` for documentation on this format. By default,
1529 :hg:`help bundlespec` for documentation on this format. By default,
1530 the most appropriate format is used and compression defaults to
1530 the most appropriate format is used and compression defaults to
1531 bzip2.
1531 bzip2.
1532
1532
1533 The bundle file can then be transferred using conventional means
1533 The bundle file can then be transferred using conventional means
1534 and applied to another repository with the unbundle or pull
1534 and applied to another repository with the unbundle or pull
1535 command. This is useful when direct push and pull are not
1535 command. This is useful when direct push and pull are not
1536 available or when exporting an entire repository is undesirable.
1536 available or when exporting an entire repository is undesirable.
1537
1537
1538 Applying bundles preserves all changeset contents including
1538 Applying bundles preserves all changeset contents including
1539 permissions, copy/rename information, and revision history.
1539 permissions, copy/rename information, and revision history.
1540
1540
1541 Returns 0 on success, 1 if no changes found.
1541 Returns 0 on success, 1 if no changes found.
1542 """
1542 """
1543 opts = pycompat.byteskwargs(opts)
1543 opts = pycompat.byteskwargs(opts)
1544 revs = None
1544 revs = None
1545 if b'rev' in opts:
1545 if b'rev' in opts:
1546 revstrings = opts[b'rev']
1546 revstrings = opts[b'rev']
1547 revs = scmutil.revrange(repo, revstrings)
1547 revs = scmutil.revrange(repo, revstrings)
1548 if revstrings and not revs:
1548 if revstrings and not revs:
1549 raise error.Abort(_(b'no commits to bundle'))
1549 raise error.Abort(_(b'no commits to bundle'))
1550
1550
1551 bundletype = opts.get(b'type', b'bzip2').lower()
1551 bundletype = opts.get(b'type', b'bzip2').lower()
1552 try:
1552 try:
1553 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1553 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1554 except error.UnsupportedBundleSpecification as e:
1554 except error.UnsupportedBundleSpecification as e:
1555 raise error.Abort(
1555 raise error.Abort(
1556 pycompat.bytestr(e),
1556 pycompat.bytestr(e),
1557 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1557 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1558 )
1558 )
1559 cgversion = bundlespec.contentopts[b"cg.version"]
1559 cgversion = bundlespec.contentopts[b"cg.version"]
1560
1560
1561 # Packed bundles are a pseudo bundle format for now.
1561 # Packed bundles are a pseudo bundle format for now.
1562 if cgversion == b's1':
1562 if cgversion == b's1':
1563 raise error.Abort(
1563 raise error.Abort(
1564 _(b'packed bundles cannot be produced by "hg bundle"'),
1564 _(b'packed bundles cannot be produced by "hg bundle"'),
1565 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1565 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1566 )
1566 )
1567
1567
1568 if opts.get(b'all'):
1568 if opts.get(b'all'):
1569 if dest:
1569 if dest:
1570 raise error.Abort(
1570 raise error.Abort(
1571 _(b"--all is incompatible with specifying a destination")
1571 _(b"--all is incompatible with specifying a destination")
1572 )
1572 )
1573 if opts.get(b'base'):
1573 if opts.get(b'base'):
1574 ui.warn(_(b"ignoring --base because --all was specified\n"))
1574 ui.warn(_(b"ignoring --base because --all was specified\n"))
1575 base = [nullrev]
1575 base = [nullrev]
1576 else:
1576 else:
1577 base = scmutil.revrange(repo, opts.get(b'base'))
1577 base = scmutil.revrange(repo, opts.get(b'base'))
1578 if cgversion not in changegroup.supportedoutgoingversions(repo):
1578 if cgversion not in changegroup.supportedoutgoingversions(repo):
1579 raise error.Abort(
1579 raise error.Abort(
1580 _(b"repository does not support bundle version %s") % cgversion
1580 _(b"repository does not support bundle version %s") % cgversion
1581 )
1581 )
1582
1582
1583 if base:
1583 if base:
1584 if dest:
1584 if dest:
1585 raise error.Abort(
1585 raise error.Abort(
1586 _(b"--base is incompatible with specifying a destination")
1586 _(b"--base is incompatible with specifying a destination")
1587 )
1587 )
1588 common = [repo[rev].node() for rev in base]
1588 common = [repo[rev].node() for rev in base]
1589 heads = [repo[r].node() for r in revs] if revs else None
1589 heads = [repo[r].node() for r in revs] if revs else None
1590 outgoing = discovery.outgoing(repo, common, heads)
1590 outgoing = discovery.outgoing(repo, common, heads)
1591 else:
1591 else:
1592 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1592 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1593 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1593 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1594 other = hg.peer(repo, opts, dest)
1594 other = hg.peer(repo, opts, dest)
1595 revs = [repo[r].hex() for r in revs]
1595 revs = [repo[r].hex() for r in revs]
1596 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1596 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1597 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1597 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1598 outgoing = discovery.findcommonoutgoing(
1598 outgoing = discovery.findcommonoutgoing(
1599 repo,
1599 repo,
1600 other,
1600 other,
1601 onlyheads=heads,
1601 onlyheads=heads,
1602 force=opts.get(b'force'),
1602 force=opts.get(b'force'),
1603 portable=True,
1603 portable=True,
1604 )
1604 )
1605
1605
1606 if not outgoing.missing:
1606 if not outgoing.missing:
1607 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1607 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1608 return 1
1608 return 1
1609
1609
1610 if cgversion == b'01': # bundle1
1610 if cgversion == b'01': # bundle1
1611 bversion = b'HG10' + bundlespec.wirecompression
1611 bversion = b'HG10' + bundlespec.wirecompression
1612 bcompression = None
1612 bcompression = None
1613 elif cgversion in (b'02', b'03'):
1613 elif cgversion in (b'02', b'03'):
1614 bversion = b'HG20'
1614 bversion = b'HG20'
1615 bcompression = bundlespec.wirecompression
1615 bcompression = bundlespec.wirecompression
1616 else:
1616 else:
1617 raise error.ProgrammingError(
1617 raise error.ProgrammingError(
1618 b'bundle: unexpected changegroup version %s' % cgversion
1618 b'bundle: unexpected changegroup version %s' % cgversion
1619 )
1619 )
1620
1620
1621 # TODO compression options should be derived from bundlespec parsing.
1621 # TODO compression options should be derived from bundlespec parsing.
1622 # This is a temporary hack to allow adjusting bundle compression
1622 # This is a temporary hack to allow adjusting bundle compression
1623 # level without a) formalizing the bundlespec changes to declare it
1623 # level without a) formalizing the bundlespec changes to declare it
1624 # b) introducing a command flag.
1624 # b) introducing a command flag.
1625 compopts = {}
1625 compopts = {}
1626 complevel = ui.configint(
1626 complevel = ui.configint(
1627 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1627 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1628 )
1628 )
1629 if complevel is None:
1629 if complevel is None:
1630 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1630 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1631 if complevel is not None:
1631 if complevel is not None:
1632 compopts[b'level'] = complevel
1632 compopts[b'level'] = complevel
1633
1633
1634 # Allow overriding the bundling of obsmarker in phases through
1634 # Allow overriding the bundling of obsmarker in phases through
1635 # configuration while we don't have a bundle version that include them
1635 # configuration while we don't have a bundle version that include them
1636 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1636 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1637 bundlespec.contentopts[b'obsolescence'] = True
1637 bundlespec.contentopts[b'obsolescence'] = True
1638 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1638 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1639 bundlespec.contentopts[b'phases'] = True
1639 bundlespec.contentopts[b'phases'] = True
1640
1640
1641 bundle2.writenewbundle(
1641 bundle2.writenewbundle(
1642 ui,
1642 ui,
1643 repo,
1643 repo,
1644 b'bundle',
1644 b'bundle',
1645 fname,
1645 fname,
1646 bversion,
1646 bversion,
1647 outgoing,
1647 outgoing,
1648 bundlespec.contentopts,
1648 bundlespec.contentopts,
1649 compression=bcompression,
1649 compression=bcompression,
1650 compopts=compopts,
1650 compopts=compopts,
1651 )
1651 )
1652
1652
1653
1653
1654 @command(
1654 @command(
1655 b'cat',
1655 b'cat',
1656 [
1656 [
1657 (
1657 (
1658 b'o',
1658 b'o',
1659 b'output',
1659 b'output',
1660 b'',
1660 b'',
1661 _(b'print output to file with formatted name'),
1661 _(b'print output to file with formatted name'),
1662 _(b'FORMAT'),
1662 _(b'FORMAT'),
1663 ),
1663 ),
1664 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1664 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1665 (b'', b'decode', None, _(b'apply any matching decode filter')),
1665 (b'', b'decode', None, _(b'apply any matching decode filter')),
1666 ]
1666 ]
1667 + walkopts
1667 + walkopts
1668 + formatteropts,
1668 + formatteropts,
1669 _(b'[OPTION]... FILE...'),
1669 _(b'[OPTION]... FILE...'),
1670 helpcategory=command.CATEGORY_FILE_CONTENTS,
1670 helpcategory=command.CATEGORY_FILE_CONTENTS,
1671 inferrepo=True,
1671 inferrepo=True,
1672 intents={INTENT_READONLY},
1672 intents={INTENT_READONLY},
1673 )
1673 )
1674 def cat(ui, repo, file1, *pats, **opts):
1674 def cat(ui, repo, file1, *pats, **opts):
1675 """output the current or given revision of files
1675 """output the current or given revision of files
1676
1676
1677 Print the specified files as they were at the given revision. If
1677 Print the specified files as they were at the given revision. If
1678 no revision is given, the parent of the working directory is used.
1678 no revision is given, the parent of the working directory is used.
1679
1679
1680 Output may be to a file, in which case the name of the file is
1680 Output may be to a file, in which case the name of the file is
1681 given using a template string. See :hg:`help templates`. In addition
1681 given using a template string. See :hg:`help templates`. In addition
1682 to the common template keywords, the following formatting rules are
1682 to the common template keywords, the following formatting rules are
1683 supported:
1683 supported:
1684
1684
1685 :``%%``: literal "%" character
1685 :``%%``: literal "%" character
1686 :``%s``: basename of file being printed
1686 :``%s``: basename of file being printed
1687 :``%d``: dirname of file being printed, or '.' if in repository root
1687 :``%d``: dirname of file being printed, or '.' if in repository root
1688 :``%p``: root-relative path name of file being printed
1688 :``%p``: root-relative path name of file being printed
1689 :``%H``: changeset hash (40 hexadecimal digits)
1689 :``%H``: changeset hash (40 hexadecimal digits)
1690 :``%R``: changeset revision number
1690 :``%R``: changeset revision number
1691 :``%h``: short-form changeset hash (12 hexadecimal digits)
1691 :``%h``: short-form changeset hash (12 hexadecimal digits)
1692 :``%r``: zero-padded changeset revision number
1692 :``%r``: zero-padded changeset revision number
1693 :``%b``: basename of the exporting repository
1693 :``%b``: basename of the exporting repository
1694 :``\\``: literal "\\" character
1694 :``\\``: literal "\\" character
1695
1695
1696 .. container:: verbose
1696 .. container:: verbose
1697
1697
1698 Template:
1698 Template:
1699
1699
1700 The following keywords are supported in addition to the common template
1700 The following keywords are supported in addition to the common template
1701 keywords and functions. See also :hg:`help templates`.
1701 keywords and functions. See also :hg:`help templates`.
1702
1702
1703 :data: String. File content.
1703 :data: String. File content.
1704 :path: String. Repository-absolute path of the file.
1704 :path: String. Repository-absolute path of the file.
1705
1705
1706 Returns 0 on success.
1706 Returns 0 on success.
1707 """
1707 """
1708 opts = pycompat.byteskwargs(opts)
1708 opts = pycompat.byteskwargs(opts)
1709 rev = opts.get(b'rev')
1709 rev = opts.get(b'rev')
1710 if rev:
1710 if rev:
1711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1712 ctx = scmutil.revsingle(repo, rev)
1712 ctx = scmutil.revsingle(repo, rev)
1713 m = scmutil.match(ctx, (file1,) + pats, opts)
1713 m = scmutil.match(ctx, (file1,) + pats, opts)
1714 fntemplate = opts.pop(b'output', b'')
1714 fntemplate = opts.pop(b'output', b'')
1715 if cmdutil.isstdiofilename(fntemplate):
1715 if cmdutil.isstdiofilename(fntemplate):
1716 fntemplate = b''
1716 fntemplate = b''
1717
1717
1718 if fntemplate:
1718 if fntemplate:
1719 fm = formatter.nullformatter(ui, b'cat', opts)
1719 fm = formatter.nullformatter(ui, b'cat', opts)
1720 else:
1720 else:
1721 ui.pager(b'cat')
1721 ui.pager(b'cat')
1722 fm = ui.formatter(b'cat', opts)
1722 fm = ui.formatter(b'cat', opts)
1723 with fm:
1723 with fm:
1724 return cmdutil.cat(
1724 return cmdutil.cat(
1725 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1725 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1726 )
1726 )
1727
1727
1728
1728
1729 @command(
1729 @command(
1730 b'clone',
1730 b'clone',
1731 [
1731 [
1732 (
1732 (
1733 b'U',
1733 b'U',
1734 b'noupdate',
1734 b'noupdate',
1735 None,
1735 None,
1736 _(
1736 _(
1737 b'the clone will include an empty working '
1737 b'the clone will include an empty working '
1738 b'directory (only a repository)'
1738 b'directory (only a repository)'
1739 ),
1739 ),
1740 ),
1740 ),
1741 (
1741 (
1742 b'u',
1742 b'u',
1743 b'updaterev',
1743 b'updaterev',
1744 b'',
1744 b'',
1745 _(b'revision, tag, or branch to check out'),
1745 _(b'revision, tag, or branch to check out'),
1746 _(b'REV'),
1746 _(b'REV'),
1747 ),
1747 ),
1748 (
1748 (
1749 b'r',
1749 b'r',
1750 b'rev',
1750 b'rev',
1751 [],
1751 [],
1752 _(
1752 _(
1753 b'do not clone everything, but include this changeset'
1753 b'do not clone everything, but include this changeset'
1754 b' and its ancestors'
1754 b' and its ancestors'
1755 ),
1755 ),
1756 _(b'REV'),
1756 _(b'REV'),
1757 ),
1757 ),
1758 (
1758 (
1759 b'b',
1759 b'b',
1760 b'branch',
1760 b'branch',
1761 [],
1761 [],
1762 _(
1762 _(
1763 b'do not clone everything, but include this branch\'s'
1763 b'do not clone everything, but include this branch\'s'
1764 b' changesets and their ancestors'
1764 b' changesets and their ancestors'
1765 ),
1765 ),
1766 _(b'BRANCH'),
1766 _(b'BRANCH'),
1767 ),
1767 ),
1768 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1768 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1769 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1769 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1770 (b'', b'stream', None, _(b'clone with minimal data processing')),
1770 (b'', b'stream', None, _(b'clone with minimal data processing')),
1771 ]
1771 ]
1772 + remoteopts,
1772 + remoteopts,
1773 _(b'[OPTION]... SOURCE [DEST]'),
1773 _(b'[OPTION]... SOURCE [DEST]'),
1774 helpcategory=command.CATEGORY_REPO_CREATION,
1774 helpcategory=command.CATEGORY_REPO_CREATION,
1775 helpbasic=True,
1775 helpbasic=True,
1776 norepo=True,
1776 norepo=True,
1777 )
1777 )
1778 def clone(ui, source, dest=None, **opts):
1778 def clone(ui, source, dest=None, **opts):
1779 """make a copy of an existing repository
1779 """make a copy of an existing repository
1780
1780
1781 Create a copy of an existing repository in a new directory.
1781 Create a copy of an existing repository in a new directory.
1782
1782
1783 If no destination directory name is specified, it defaults to the
1783 If no destination directory name is specified, it defaults to the
1784 basename of the source.
1784 basename of the source.
1785
1785
1786 The location of the source is added to the new repository's
1786 The location of the source is added to the new repository's
1787 ``.hg/hgrc`` file, as the default to be used for future pulls.
1787 ``.hg/hgrc`` file, as the default to be used for future pulls.
1788
1788
1789 Only local paths and ``ssh://`` URLs are supported as
1789 Only local paths and ``ssh://`` URLs are supported as
1790 destinations. For ``ssh://`` destinations, no working directory or
1790 destinations. For ``ssh://`` destinations, no working directory or
1791 ``.hg/hgrc`` will be created on the remote side.
1791 ``.hg/hgrc`` will be created on the remote side.
1792
1792
1793 If the source repository has a bookmark called '@' set, that
1793 If the source repository has a bookmark called '@' set, that
1794 revision will be checked out in the new repository by default.
1794 revision will be checked out in the new repository by default.
1795
1795
1796 To check out a particular version, use -u/--update, or
1796 To check out a particular version, use -u/--update, or
1797 -U/--noupdate to create a clone with no working directory.
1797 -U/--noupdate to create a clone with no working directory.
1798
1798
1799 To pull only a subset of changesets, specify one or more revisions
1799 To pull only a subset of changesets, specify one or more revisions
1800 identifiers with -r/--rev or branches with -b/--branch. The
1800 identifiers with -r/--rev or branches with -b/--branch. The
1801 resulting clone will contain only the specified changesets and
1801 resulting clone will contain only the specified changesets and
1802 their ancestors. These options (or 'clone src#rev dest') imply
1802 their ancestors. These options (or 'clone src#rev dest') imply
1803 --pull, even for local source repositories.
1803 --pull, even for local source repositories.
1804
1804
1805 In normal clone mode, the remote normalizes repository data into a common
1805 In normal clone mode, the remote normalizes repository data into a common
1806 exchange format and the receiving end translates this data into its local
1806 exchange format and the receiving end translates this data into its local
1807 storage format. --stream activates a different clone mode that essentially
1807 storage format. --stream activates a different clone mode that essentially
1808 copies repository files from the remote with minimal data processing. This
1808 copies repository files from the remote with minimal data processing. This
1809 significantly reduces the CPU cost of a clone both remotely and locally.
1809 significantly reduces the CPU cost of a clone both remotely and locally.
1810 However, it often increases the transferred data size by 30-40%. This can
1810 However, it often increases the transferred data size by 30-40%. This can
1811 result in substantially faster clones where I/O throughput is plentiful,
1811 result in substantially faster clones where I/O throughput is plentiful,
1812 especially for larger repositories. A side-effect of --stream clones is
1812 especially for larger repositories. A side-effect of --stream clones is
1813 that storage settings and requirements on the remote are applied locally:
1813 that storage settings and requirements on the remote are applied locally:
1814 a modern client may inherit legacy or inefficient storage used by the
1814 a modern client may inherit legacy or inefficient storage used by the
1815 remote or a legacy Mercurial client may not be able to clone from a
1815 remote or a legacy Mercurial client may not be able to clone from a
1816 modern Mercurial remote.
1816 modern Mercurial remote.
1817
1817
1818 .. note::
1818 .. note::
1819
1819
1820 Specifying a tag will include the tagged changeset but not the
1820 Specifying a tag will include the tagged changeset but not the
1821 changeset containing the tag.
1821 changeset containing the tag.
1822
1822
1823 .. container:: verbose
1823 .. container:: verbose
1824
1824
1825 For efficiency, hardlinks are used for cloning whenever the
1825 For efficiency, hardlinks are used for cloning whenever the
1826 source and destination are on the same filesystem (note this
1826 source and destination are on the same filesystem (note this
1827 applies only to the repository data, not to the working
1827 applies only to the repository data, not to the working
1828 directory). Some filesystems, such as AFS, implement hardlinking
1828 directory). Some filesystems, such as AFS, implement hardlinking
1829 incorrectly, but do not report errors. In these cases, use the
1829 incorrectly, but do not report errors. In these cases, use the
1830 --pull option to avoid hardlinking.
1830 --pull option to avoid hardlinking.
1831
1831
1832 Mercurial will update the working directory to the first applicable
1832 Mercurial will update the working directory to the first applicable
1833 revision from this list:
1833 revision from this list:
1834
1834
1835 a) null if -U or the source repository has no changesets
1835 a) null if -U or the source repository has no changesets
1836 b) if -u . and the source repository is local, the first parent of
1836 b) if -u . and the source repository is local, the first parent of
1837 the source repository's working directory
1837 the source repository's working directory
1838 c) the changeset specified with -u (if a branch name, this means the
1838 c) the changeset specified with -u (if a branch name, this means the
1839 latest head of that branch)
1839 latest head of that branch)
1840 d) the changeset specified with -r
1840 d) the changeset specified with -r
1841 e) the tipmost head specified with -b
1841 e) the tipmost head specified with -b
1842 f) the tipmost head specified with the url#branch source syntax
1842 f) the tipmost head specified with the url#branch source syntax
1843 g) the revision marked with the '@' bookmark, if present
1843 g) the revision marked with the '@' bookmark, if present
1844 h) the tipmost head of the default branch
1844 h) the tipmost head of the default branch
1845 i) tip
1845 i) tip
1846
1846
1847 When cloning from servers that support it, Mercurial may fetch
1847 When cloning from servers that support it, Mercurial may fetch
1848 pre-generated data from a server-advertised URL or inline from the
1848 pre-generated data from a server-advertised URL or inline from the
1849 same stream. When this is done, hooks operating on incoming changesets
1849 same stream. When this is done, hooks operating on incoming changesets
1850 and changegroups may fire more than once, once for each pre-generated
1850 and changegroups may fire more than once, once for each pre-generated
1851 bundle and as well as for any additional remaining data. In addition,
1851 bundle and as well as for any additional remaining data. In addition,
1852 if an error occurs, the repository may be rolled back to a partial
1852 if an error occurs, the repository may be rolled back to a partial
1853 clone. This behavior may change in future releases.
1853 clone. This behavior may change in future releases.
1854 See :hg:`help -e clonebundles` for more.
1854 See :hg:`help -e clonebundles` for more.
1855
1855
1856 Examples:
1856 Examples:
1857
1857
1858 - clone a remote repository to a new directory named hg/::
1858 - clone a remote repository to a new directory named hg/::
1859
1859
1860 hg clone https://www.mercurial-scm.org/repo/hg/
1860 hg clone https://www.mercurial-scm.org/repo/hg/
1861
1861
1862 - create a lightweight local clone::
1862 - create a lightweight local clone::
1863
1863
1864 hg clone project/ project-feature/
1864 hg clone project/ project-feature/
1865
1865
1866 - clone from an absolute path on an ssh server (note double-slash)::
1866 - clone from an absolute path on an ssh server (note double-slash)::
1867
1867
1868 hg clone ssh://user@server//home/projects/alpha/
1868 hg clone ssh://user@server//home/projects/alpha/
1869
1869
1870 - do a streaming clone while checking out a specified version::
1870 - do a streaming clone while checking out a specified version::
1871
1871
1872 hg clone --stream http://server/repo -u 1.5
1872 hg clone --stream http://server/repo -u 1.5
1873
1873
1874 - create a repository without changesets after a particular revision::
1874 - create a repository without changesets after a particular revision::
1875
1875
1876 hg clone -r 04e544 experimental/ good/
1876 hg clone -r 04e544 experimental/ good/
1877
1877
1878 - clone (and track) a particular named branch::
1878 - clone (and track) a particular named branch::
1879
1879
1880 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1880 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1881
1881
1882 See :hg:`help urls` for details on specifying URLs.
1882 See :hg:`help urls` for details on specifying URLs.
1883
1883
1884 Returns 0 on success.
1884 Returns 0 on success.
1885 """
1885 """
1886 opts = pycompat.byteskwargs(opts)
1886 opts = pycompat.byteskwargs(opts)
1887 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1887 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1888
1888
1889 # --include/--exclude can come from narrow or sparse.
1889 # --include/--exclude can come from narrow or sparse.
1890 includepats, excludepats = None, None
1890 includepats, excludepats = None, None
1891
1891
1892 # hg.clone() differentiates between None and an empty set. So make sure
1892 # hg.clone() differentiates between None and an empty set. So make sure
1893 # patterns are sets if narrow is requested without patterns.
1893 # patterns are sets if narrow is requested without patterns.
1894 if opts.get(b'narrow'):
1894 if opts.get(b'narrow'):
1895 includepats = set()
1895 includepats = set()
1896 excludepats = set()
1896 excludepats = set()
1897
1897
1898 if opts.get(b'include'):
1898 if opts.get(b'include'):
1899 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1899 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1900 if opts.get(b'exclude'):
1900 if opts.get(b'exclude'):
1901 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1901 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1902
1902
1903 r = hg.clone(
1903 r = hg.clone(
1904 ui,
1904 ui,
1905 opts,
1905 opts,
1906 source,
1906 source,
1907 dest,
1907 dest,
1908 pull=opts.get(b'pull'),
1908 pull=opts.get(b'pull'),
1909 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1909 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1910 revs=opts.get(b'rev'),
1910 revs=opts.get(b'rev'),
1911 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1911 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1912 branch=opts.get(b'branch'),
1912 branch=opts.get(b'branch'),
1913 shareopts=opts.get(b'shareopts'),
1913 shareopts=opts.get(b'shareopts'),
1914 storeincludepats=includepats,
1914 storeincludepats=includepats,
1915 storeexcludepats=excludepats,
1915 storeexcludepats=excludepats,
1916 depth=opts.get(b'depth') or None,
1916 depth=opts.get(b'depth') or None,
1917 )
1917 )
1918
1918
1919 return r is None
1919 return r is None
1920
1920
1921
1921
1922 @command(
1922 @command(
1923 b'commit|ci',
1923 b'commit|ci',
1924 [
1924 [
1925 (
1925 (
1926 b'A',
1926 b'A',
1927 b'addremove',
1927 b'addremove',
1928 None,
1928 None,
1929 _(b'mark new/missing files as added/removed before committing'),
1929 _(b'mark new/missing files as added/removed before committing'),
1930 ),
1930 ),
1931 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1931 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1932 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1932 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1933 (b's', b'secret', None, _(b'use the secret phase for committing')),
1933 (b's', b'secret', None, _(b'use the secret phase for committing')),
1934 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1934 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1935 (
1935 (
1936 b'',
1936 b'',
1937 b'force-close-branch',
1937 b'force-close-branch',
1938 None,
1938 None,
1939 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1939 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1940 ),
1940 ),
1941 (b'i', b'interactive', None, _(b'use interactive mode')),
1941 (b'i', b'interactive', None, _(b'use interactive mode')),
1942 ]
1942 ]
1943 + walkopts
1943 + walkopts
1944 + commitopts
1944 + commitopts
1945 + commitopts2
1945 + commitopts2
1946 + subrepoopts,
1946 + subrepoopts,
1947 _(b'[OPTION]... [FILE]...'),
1947 _(b'[OPTION]... [FILE]...'),
1948 helpcategory=command.CATEGORY_COMMITTING,
1948 helpcategory=command.CATEGORY_COMMITTING,
1949 helpbasic=True,
1949 helpbasic=True,
1950 inferrepo=True,
1950 inferrepo=True,
1951 )
1951 )
1952 def commit(ui, repo, *pats, **opts):
1952 def commit(ui, repo, *pats, **opts):
1953 """commit the specified files or all outstanding changes
1953 """commit the specified files or all outstanding changes
1954
1954
1955 Commit changes to the given files into the repository. Unlike a
1955 Commit changes to the given files into the repository. Unlike a
1956 centralized SCM, this operation is a local operation. See
1956 centralized SCM, this operation is a local operation. See
1957 :hg:`push` for a way to actively distribute your changes.
1957 :hg:`push` for a way to actively distribute your changes.
1958
1958
1959 If a list of files is omitted, all changes reported by :hg:`status`
1959 If a list of files is omitted, all changes reported by :hg:`status`
1960 will be committed.
1960 will be committed.
1961
1961
1962 If you are committing the result of a merge, do not provide any
1962 If you are committing the result of a merge, do not provide any
1963 filenames or -I/-X filters.
1963 filenames or -I/-X filters.
1964
1964
1965 If no commit message is specified, Mercurial starts your
1965 If no commit message is specified, Mercurial starts your
1966 configured editor where you can enter a message. In case your
1966 configured editor where you can enter a message. In case your
1967 commit fails, you will find a backup of your message in
1967 commit fails, you will find a backup of your message in
1968 ``.hg/last-message.txt``.
1968 ``.hg/last-message.txt``.
1969
1969
1970 The --close-branch flag can be used to mark the current branch
1970 The --close-branch flag can be used to mark the current branch
1971 head closed. When all heads of a branch are closed, the branch
1971 head closed. When all heads of a branch are closed, the branch
1972 will be considered closed and no longer listed.
1972 will be considered closed and no longer listed.
1973
1973
1974 The --amend flag can be used to amend the parent of the
1974 The --amend flag can be used to amend the parent of the
1975 working directory with a new commit that contains the changes
1975 working directory with a new commit that contains the changes
1976 in the parent in addition to those currently reported by :hg:`status`,
1976 in the parent in addition to those currently reported by :hg:`status`,
1977 if there are any. The old commit is stored in a backup bundle in
1977 if there are any. The old commit is stored in a backup bundle in
1978 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1978 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1979 on how to restore it).
1979 on how to restore it).
1980
1980
1981 Message, user and date are taken from the amended commit unless
1981 Message, user and date are taken from the amended commit unless
1982 specified. When a message isn't specified on the command line,
1982 specified. When a message isn't specified on the command line,
1983 the editor will open with the message of the amended commit.
1983 the editor will open with the message of the amended commit.
1984
1984
1985 It is not possible to amend public changesets (see :hg:`help phases`)
1985 It is not possible to amend public changesets (see :hg:`help phases`)
1986 or changesets that have children.
1986 or changesets that have children.
1987
1987
1988 See :hg:`help dates` for a list of formats valid for -d/--date.
1988 See :hg:`help dates` for a list of formats valid for -d/--date.
1989
1989
1990 Returns 0 on success, 1 if nothing changed.
1990 Returns 0 on success, 1 if nothing changed.
1991
1991
1992 .. container:: verbose
1992 .. container:: verbose
1993
1993
1994 Examples:
1994 Examples:
1995
1995
1996 - commit all files ending in .py::
1996 - commit all files ending in .py::
1997
1997
1998 hg commit --include "set:**.py"
1998 hg commit --include "set:**.py"
1999
1999
2000 - commit all non-binary files::
2000 - commit all non-binary files::
2001
2001
2002 hg commit --exclude "set:binary()"
2002 hg commit --exclude "set:binary()"
2003
2003
2004 - amend the current commit and set the date to now::
2004 - amend the current commit and set the date to now::
2005
2005
2006 hg commit --amend --date now
2006 hg commit --amend --date now
2007 """
2007 """
2008 with repo.wlock(), repo.lock():
2008 with repo.wlock(), repo.lock():
2009 return _docommit(ui, repo, *pats, **opts)
2009 return _docommit(ui, repo, *pats, **opts)
2010
2010
2011
2011
2012 def _docommit(ui, repo, *pats, **opts):
2012 def _docommit(ui, repo, *pats, **opts):
2013 if opts.get('interactive'):
2013 if opts.get('interactive'):
2014 opts.pop('interactive')
2014 opts.pop('interactive')
2015 ret = cmdutil.dorecord(
2015 ret = cmdutil.dorecord(
2016 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2016 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2017 )
2017 )
2018 # ret can be 0 (no changes to record) or the value returned by
2018 # ret can be 0 (no changes to record) or the value returned by
2019 # commit(), 1 if nothing changed or None on success.
2019 # commit(), 1 if nothing changed or None on success.
2020 return 1 if ret == 0 else ret
2020 return 1 if ret == 0 else ret
2021
2021
2022 opts = pycompat.byteskwargs(opts)
2022 opts = pycompat.byteskwargs(opts)
2023 if opts.get(b'subrepos'):
2023 if opts.get(b'subrepos'):
2024 if opts.get(b'amend'):
2024 if opts.get(b'amend'):
2025 raise error.Abort(_(b'cannot amend with --subrepos'))
2025 raise error.Abort(_(b'cannot amend with --subrepos'))
2026 # Let --subrepos on the command line override config setting.
2026 # Let --subrepos on the command line override config setting.
2027 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2027 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2028
2028
2029 cmdutil.checkunfinished(repo, commit=True)
2029 cmdutil.checkunfinished(repo, commit=True)
2030
2030
2031 branch = repo[None].branch()
2031 branch = repo[None].branch()
2032 bheads = repo.branchheads(branch)
2032 bheads = repo.branchheads(branch)
2033
2033
2034 extra = {}
2034 extra = {}
2035 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2035 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2036 extra[b'close'] = b'1'
2036 extra[b'close'] = b'1'
2037
2037
2038 if repo[b'.'].closesbranch():
2038 if repo[b'.'].closesbranch():
2039 raise error.Abort(
2039 raise error.Abort(
2040 _(b'current revision is already a branch closing head')
2040 _(b'current revision is already a branch closing head')
2041 )
2041 )
2042 elif not bheads:
2042 elif not bheads:
2043 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2043 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2044 elif (
2044 elif (
2045 branch == repo[b'.'].branch()
2045 branch == repo[b'.'].branch()
2046 and repo[b'.'].node() not in bheads
2046 and repo[b'.'].node() not in bheads
2047 and not opts.get(b'force_close_branch')
2047 and not opts.get(b'force_close_branch')
2048 ):
2048 ):
2049 hint = _(
2049 hint = _(
2050 b'use --force-close-branch to close branch from a non-head'
2050 b'use --force-close-branch to close branch from a non-head'
2051 b' changeset'
2051 b' changeset'
2052 )
2052 )
2053 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2053 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2054 elif opts.get(b'amend'):
2054 elif opts.get(b'amend'):
2055 if (
2055 if (
2056 repo[b'.'].p1().branch() != branch
2056 repo[b'.'].p1().branch() != branch
2057 and repo[b'.'].p2().branch() != branch
2057 and repo[b'.'].p2().branch() != branch
2058 ):
2058 ):
2059 raise error.Abort(_(b'can only close branch heads'))
2059 raise error.Abort(_(b'can only close branch heads'))
2060
2060
2061 if opts.get(b'amend'):
2061 if opts.get(b'amend'):
2062 if ui.configbool(b'ui', b'commitsubrepos'):
2062 if ui.configbool(b'ui', b'commitsubrepos'):
2063 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2063 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2064
2064
2065 old = repo[b'.']
2065 old = repo[b'.']
2066 rewriteutil.precheck(repo, [old.rev()], b'amend')
2066 rewriteutil.precheck(repo, [old.rev()], b'amend')
2067
2067
2068 # Currently histedit gets confused if an amend happens while histedit
2068 # Currently histedit gets confused if an amend happens while histedit
2069 # is in progress. Since we have a checkunfinished command, we are
2069 # is in progress. Since we have a checkunfinished command, we are
2070 # temporarily honoring it.
2070 # temporarily honoring it.
2071 #
2071 #
2072 # Note: eventually this guard will be removed. Please do not expect
2072 # Note: eventually this guard will be removed. Please do not expect
2073 # this behavior to remain.
2073 # this behavior to remain.
2074 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2074 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2075 cmdutil.checkunfinished(repo)
2075 cmdutil.checkunfinished(repo)
2076
2076
2077 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2077 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2078 if node == old.node():
2078 if node == old.node():
2079 ui.status(_(b"nothing changed\n"))
2079 ui.status(_(b"nothing changed\n"))
2080 return 1
2080 return 1
2081 else:
2081 else:
2082
2082
2083 def commitfunc(ui, repo, message, match, opts):
2083 def commitfunc(ui, repo, message, match, opts):
2084 overrides = {}
2084 overrides = {}
2085 if opts.get(b'secret'):
2085 if opts.get(b'secret'):
2086 overrides[(b'phases', b'new-commit')] = b'secret'
2086 overrides[(b'phases', b'new-commit')] = b'secret'
2087
2087
2088 baseui = repo.baseui
2088 baseui = repo.baseui
2089 with baseui.configoverride(overrides, b'commit'):
2089 with baseui.configoverride(overrides, b'commit'):
2090 with ui.configoverride(overrides, b'commit'):
2090 with ui.configoverride(overrides, b'commit'):
2091 editform = cmdutil.mergeeditform(
2091 editform = cmdutil.mergeeditform(
2092 repo[None], b'commit.normal'
2092 repo[None], b'commit.normal'
2093 )
2093 )
2094 editor = cmdutil.getcommiteditor(
2094 editor = cmdutil.getcommiteditor(
2095 editform=editform, **pycompat.strkwargs(opts)
2095 editform=editform, **pycompat.strkwargs(opts)
2096 )
2096 )
2097 return repo.commit(
2097 return repo.commit(
2098 message,
2098 message,
2099 opts.get(b'user'),
2099 opts.get(b'user'),
2100 opts.get(b'date'),
2100 opts.get(b'date'),
2101 match,
2101 match,
2102 editor=editor,
2102 editor=editor,
2103 extra=extra,
2103 extra=extra,
2104 )
2104 )
2105
2105
2106 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2106 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2107
2107
2108 if not node:
2108 if not node:
2109 stat = cmdutil.postcommitstatus(repo, pats, opts)
2109 stat = cmdutil.postcommitstatus(repo, pats, opts)
2110 if stat.deleted:
2110 if stat.deleted:
2111 ui.status(
2111 ui.status(
2112 _(
2112 _(
2113 b"nothing changed (%d missing files, see "
2113 b"nothing changed (%d missing files, see "
2114 b"'hg status')\n"
2114 b"'hg status')\n"
2115 )
2115 )
2116 % len(stat.deleted)
2116 % len(stat.deleted)
2117 )
2117 )
2118 else:
2118 else:
2119 ui.status(_(b"nothing changed\n"))
2119 ui.status(_(b"nothing changed\n"))
2120 return 1
2120 return 1
2121
2121
2122 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2122 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2123
2123
2124 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2124 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2125 status(
2125 status(
2126 ui,
2126 ui,
2127 repo,
2127 repo,
2128 modified=True,
2128 modified=True,
2129 added=True,
2129 added=True,
2130 removed=True,
2130 removed=True,
2131 deleted=True,
2131 deleted=True,
2132 unknown=True,
2132 unknown=True,
2133 subrepos=opts.get(b'subrepos'),
2133 subrepos=opts.get(b'subrepos'),
2134 )
2134 )
2135
2135
2136
2136
2137 @command(
2137 @command(
2138 b'config|showconfig|debugconfig',
2138 b'config|showconfig|debugconfig',
2139 [
2139 [
2140 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2140 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2141 (b'e', b'edit', None, _(b'edit user config')),
2141 (b'e', b'edit', None, _(b'edit user config')),
2142 (b'l', b'local', None, _(b'edit repository config')),
2142 (b'l', b'local', None, _(b'edit repository config')),
2143 (b'g', b'global', None, _(b'edit global config')),
2143 (b'g', b'global', None, _(b'edit global config')),
2144 ]
2144 ]
2145 + formatteropts,
2145 + formatteropts,
2146 _(b'[-u] [NAME]...'),
2146 _(b'[-u] [NAME]...'),
2147 helpcategory=command.CATEGORY_HELP,
2147 helpcategory=command.CATEGORY_HELP,
2148 optionalrepo=True,
2148 optionalrepo=True,
2149 intents={INTENT_READONLY},
2149 intents={INTENT_READONLY},
2150 )
2150 )
2151 def config(ui, repo, *values, **opts):
2151 def config(ui, repo, *values, **opts):
2152 """show combined config settings from all hgrc files
2152 """show combined config settings from all hgrc files
2153
2153
2154 With no arguments, print names and values of all config items.
2154 With no arguments, print names and values of all config items.
2155
2155
2156 With one argument of the form section.name, print just the value
2156 With one argument of the form section.name, print just the value
2157 of that config item.
2157 of that config item.
2158
2158
2159 With multiple arguments, print names and values of all config
2159 With multiple arguments, print names and values of all config
2160 items with matching section names or section.names.
2160 items with matching section names or section.names.
2161
2161
2162 With --edit, start an editor on the user-level config file. With
2162 With --edit, start an editor on the user-level config file. With
2163 --global, edit the system-wide config file. With --local, edit the
2163 --global, edit the system-wide config file. With --local, edit the
2164 repository-level config file.
2164 repository-level config file.
2165
2165
2166 With --debug, the source (filename and line number) is printed
2166 With --debug, the source (filename and line number) is printed
2167 for each config item.
2167 for each config item.
2168
2168
2169 See :hg:`help config` for more information about config files.
2169 See :hg:`help config` for more information about config files.
2170
2170
2171 .. container:: verbose
2171 .. container:: verbose
2172
2172
2173 Template:
2173 Template:
2174
2174
2175 The following keywords are supported. See also :hg:`help templates`.
2175 The following keywords are supported. See also :hg:`help templates`.
2176
2176
2177 :name: String. Config name.
2177 :name: String. Config name.
2178 :source: String. Filename and line number where the item is defined.
2178 :source: String. Filename and line number where the item is defined.
2179 :value: String. Config value.
2179 :value: String. Config value.
2180
2180
2181 Returns 0 on success, 1 if NAME does not exist.
2181 Returns 0 on success, 1 if NAME does not exist.
2182
2182
2183 """
2183 """
2184
2184
2185 opts = pycompat.byteskwargs(opts)
2185 opts = pycompat.byteskwargs(opts)
2186 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2186 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2187 if opts.get(b'local') and opts.get(b'global'):
2187 if opts.get(b'local') and opts.get(b'global'):
2188 raise error.Abort(_(b"can't use --local and --global together"))
2188 raise error.Abort(_(b"can't use --local and --global together"))
2189
2189
2190 if opts.get(b'local'):
2190 if opts.get(b'local'):
2191 if not repo:
2191 if not repo:
2192 raise error.Abort(_(b"can't use --local outside a repository"))
2192 raise error.Abort(_(b"can't use --local outside a repository"))
2193 paths = [repo.vfs.join(b'hgrc')]
2193 paths = [repo.vfs.join(b'hgrc')]
2194 elif opts.get(b'global'):
2194 elif opts.get(b'global'):
2195 paths = rcutil.systemrcpath()
2195 paths = rcutil.systemrcpath()
2196 else:
2196 else:
2197 paths = rcutil.userrcpath()
2197 paths = rcutil.userrcpath()
2198
2198
2199 for f in paths:
2199 for f in paths:
2200 if os.path.exists(f):
2200 if os.path.exists(f):
2201 break
2201 break
2202 else:
2202 else:
2203 if opts.get(b'global'):
2203 if opts.get(b'global'):
2204 samplehgrc = uimod.samplehgrcs[b'global']
2204 samplehgrc = uimod.samplehgrcs[b'global']
2205 elif opts.get(b'local'):
2205 elif opts.get(b'local'):
2206 samplehgrc = uimod.samplehgrcs[b'local']
2206 samplehgrc = uimod.samplehgrcs[b'local']
2207 else:
2207 else:
2208 samplehgrc = uimod.samplehgrcs[b'user']
2208 samplehgrc = uimod.samplehgrcs[b'user']
2209
2209
2210 f = paths[0]
2210 f = paths[0]
2211 fp = open(f, b"wb")
2211 fp = open(f, b"wb")
2212 fp.write(util.tonativeeol(samplehgrc))
2212 fp.write(util.tonativeeol(samplehgrc))
2213 fp.close()
2213 fp.close()
2214
2214
2215 editor = ui.geteditor()
2215 editor = ui.geteditor()
2216 ui.system(
2216 ui.system(
2217 b"%s \"%s\"" % (editor, f),
2217 b"%s \"%s\"" % (editor, f),
2218 onerr=error.Abort,
2218 onerr=error.Abort,
2219 errprefix=_(b"edit failed"),
2219 errprefix=_(b"edit failed"),
2220 blockedtag=b'config_edit',
2220 blockedtag=b'config_edit',
2221 )
2221 )
2222 return
2222 return
2223 ui.pager(b'config')
2223 ui.pager(b'config')
2224 fm = ui.formatter(b'config', opts)
2224 fm = ui.formatter(b'config', opts)
2225 for t, f in rcutil.rccomponents():
2225 for t, f in rcutil.rccomponents():
2226 if t == b'path':
2226 if t == b'path':
2227 ui.debug(b'read config from: %s\n' % f)
2227 ui.debug(b'read config from: %s\n' % f)
2228 elif t == b'resource':
2228 elif t == b'resource':
2229 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2229 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2230 elif t == b'items':
2230 elif t == b'items':
2231 # Don't print anything for 'items'.
2231 # Don't print anything for 'items'.
2232 pass
2232 pass
2233 else:
2233 else:
2234 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2234 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2235 untrusted = bool(opts.get(b'untrusted'))
2235 untrusted = bool(opts.get(b'untrusted'))
2236
2236
2237 selsections = selentries = []
2237 selsections = selentries = []
2238 if values:
2238 if values:
2239 selsections = [v for v in values if b'.' not in v]
2239 selsections = [v for v in values if b'.' not in v]
2240 selentries = [v for v in values if b'.' in v]
2240 selentries = [v for v in values if b'.' in v]
2241 uniquesel = len(selentries) == 1 and not selsections
2241 uniquesel = len(selentries) == 1 and not selsections
2242 selsections = set(selsections)
2242 selsections = set(selsections)
2243 selentries = set(selentries)
2243 selentries = set(selentries)
2244
2244
2245 matched = False
2245 matched = False
2246 for section, name, value in ui.walkconfig(untrusted=untrusted):
2246 for section, name, value in ui.walkconfig(untrusted=untrusted):
2247 source = ui.configsource(section, name, untrusted)
2247 source = ui.configsource(section, name, untrusted)
2248 value = pycompat.bytestr(value)
2248 value = pycompat.bytestr(value)
2249 defaultvalue = ui.configdefault(section, name)
2249 defaultvalue = ui.configdefault(section, name)
2250 if fm.isplain():
2250 if fm.isplain():
2251 source = source or b'none'
2251 source = source or b'none'
2252 value = value.replace(b'\n', b'\\n')
2252 value = value.replace(b'\n', b'\\n')
2253 entryname = section + b'.' + name
2253 entryname = section + b'.' + name
2254 if values and not (section in selsections or entryname in selentries):
2254 if values and not (section in selsections or entryname in selentries):
2255 continue
2255 continue
2256 fm.startitem()
2256 fm.startitem()
2257 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2257 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2258 if uniquesel:
2258 if uniquesel:
2259 fm.data(name=entryname)
2259 fm.data(name=entryname)
2260 fm.write(b'value', b'%s\n', value)
2260 fm.write(b'value', b'%s\n', value)
2261 else:
2261 else:
2262 fm.write(b'name value', b'%s=%s\n', entryname, value)
2262 fm.write(b'name value', b'%s=%s\n', entryname, value)
2263 if formatter.isprintable(defaultvalue):
2263 if formatter.isprintable(defaultvalue):
2264 fm.data(defaultvalue=defaultvalue)
2264 fm.data(defaultvalue=defaultvalue)
2265 elif isinstance(defaultvalue, list) and all(
2265 elif isinstance(defaultvalue, list) and all(
2266 formatter.isprintable(e) for e in defaultvalue
2266 formatter.isprintable(e) for e in defaultvalue
2267 ):
2267 ):
2268 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2268 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2269 # TODO: no idea how to process unsupported defaultvalue types
2269 # TODO: no idea how to process unsupported defaultvalue types
2270 matched = True
2270 matched = True
2271 fm.end()
2271 fm.end()
2272 if matched:
2272 if matched:
2273 return 0
2273 return 0
2274 return 1
2274 return 1
2275
2275
2276
2276
2277 @command(
2277 @command(
2278 b'continue',
2278 b'continue',
2279 dryrunopts,
2279 dryrunopts,
2280 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2280 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2281 helpbasic=True,
2281 helpbasic=True,
2282 )
2282 )
2283 def continuecmd(ui, repo, **opts):
2283 def continuecmd(ui, repo, **opts):
2284 """resumes an interrupted operation (EXPERIMENTAL)
2284 """resumes an interrupted operation (EXPERIMENTAL)
2285
2285
2286 Finishes a multistep operation like graft, histedit, rebase, merge,
2286 Finishes a multistep operation like graft, histedit, rebase, merge,
2287 and unshelve if they are in an interrupted state.
2287 and unshelve if they are in an interrupted state.
2288
2288
2289 use --dry-run/-n to dry run the command.
2289 use --dry-run/-n to dry run the command.
2290 """
2290 """
2291 dryrun = opts.get('dry_run')
2291 dryrun = opts.get('dry_run')
2292 contstate = cmdutil.getunfinishedstate(repo)
2292 contstate = cmdutil.getunfinishedstate(repo)
2293 if not contstate:
2293 if not contstate:
2294 raise error.Abort(_(b'no operation in progress'))
2294 raise error.Abort(_(b'no operation in progress'))
2295 if not contstate.continuefunc:
2295 if not contstate.continuefunc:
2296 raise error.Abort(
2296 raise error.Abort(
2297 (
2297 (
2298 _(b"%s in progress but does not support 'hg continue'")
2298 _(b"%s in progress but does not support 'hg continue'")
2299 % (contstate._opname)
2299 % (contstate._opname)
2300 ),
2300 ),
2301 hint=contstate.continuemsg(),
2301 hint=contstate.continuemsg(),
2302 )
2302 )
2303 if dryrun:
2303 if dryrun:
2304 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2304 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2305 return
2305 return
2306 return contstate.continuefunc(ui, repo)
2306 return contstate.continuefunc(ui, repo)
2307
2307
2308
2308
2309 @command(
2309 @command(
2310 b'copy|cp',
2310 b'copy|cp',
2311 [
2311 [
2312 (b'', b'forget', None, _(b'unmark a file as copied')),
2312 (b'', b'forget', None, _(b'unmark a file as copied')),
2313 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2313 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2314 (
2314 (
2315 b'',
2315 b'',
2316 b'at-rev',
2316 b'at-rev',
2317 b'',
2317 b'',
2318 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2318 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2319 _(b'REV'),
2319 _(b'REV'),
2320 ),
2320 ),
2321 (
2321 (
2322 b'f',
2322 b'f',
2323 b'force',
2323 b'force',
2324 None,
2324 None,
2325 _(b'forcibly copy over an existing managed file'),
2325 _(b'forcibly copy over an existing managed file'),
2326 ),
2326 ),
2327 ]
2327 ]
2328 + walkopts
2328 + walkopts
2329 + dryrunopts,
2329 + dryrunopts,
2330 _(b'[OPTION]... SOURCE... DEST'),
2330 _(b'[OPTION]... SOURCE... DEST'),
2331 helpcategory=command.CATEGORY_FILE_CONTENTS,
2331 helpcategory=command.CATEGORY_FILE_CONTENTS,
2332 )
2332 )
2333 def copy(ui, repo, *pats, **opts):
2333 def copy(ui, repo, *pats, **opts):
2334 """mark files as copied for the next commit
2334 """mark files as copied for the next commit
2335
2335
2336 Mark dest as having copies of source files. If dest is a
2336 Mark dest as having copies of source files. If dest is a
2337 directory, copies are put in that directory. If dest is a file,
2337 directory, copies are put in that directory. If dest is a file,
2338 the source must be a single file.
2338 the source must be a single file.
2339
2339
2340 By default, this command copies the contents of files as they
2340 By default, this command copies the contents of files as they
2341 exist in the working directory. If invoked with -A/--after, the
2341 exist in the working directory. If invoked with -A/--after, the
2342 operation is recorded, but no copying is performed.
2342 operation is recorded, but no copying is performed.
2343
2343
2344 To undo marking a file as copied, use --forget. With that option,
2344 To undo marking a file as copied, use --forget. With that option,
2345 all given (positional) arguments are unmarked as copies. The destination
2345 all given (positional) arguments are unmarked as copies. The destination
2346 file(s) will be left in place (still tracked).
2346 file(s) will be left in place (still tracked).
2347
2347
2348 This command takes effect with the next commit by default.
2348 This command takes effect with the next commit by default.
2349
2349
2350 Returns 0 on success, 1 if errors are encountered.
2350 Returns 0 on success, 1 if errors are encountered.
2351 """
2351 """
2352 opts = pycompat.byteskwargs(opts)
2352 opts = pycompat.byteskwargs(opts)
2353 with repo.wlock(False):
2353 with repo.wlock(False):
2354 return cmdutil.copy(ui, repo, pats, opts)
2354 return cmdutil.copy(ui, repo, pats, opts)
2355
2355
2356
2356
2357 @command(
2357 @command(
2358 b'debugcommands',
2358 b'debugcommands',
2359 [],
2359 [],
2360 _(b'[COMMAND]'),
2360 _(b'[COMMAND]'),
2361 helpcategory=command.CATEGORY_HELP,
2361 helpcategory=command.CATEGORY_HELP,
2362 norepo=True,
2362 norepo=True,
2363 )
2363 )
2364 def debugcommands(ui, cmd=b'', *args):
2364 def debugcommands(ui, cmd=b'', *args):
2365 """list all available commands and options"""
2365 """list all available commands and options"""
2366 for cmd, vals in sorted(pycompat.iteritems(table)):
2366 for cmd, vals in sorted(pycompat.iteritems(table)):
2367 cmd = cmd.split(b'|')[0]
2367 cmd = cmd.split(b'|')[0]
2368 opts = b', '.join([i[1] for i in vals[1]])
2368 opts = b', '.join([i[1] for i in vals[1]])
2369 ui.write(b'%s: %s\n' % (cmd, opts))
2369 ui.write(b'%s: %s\n' % (cmd, opts))
2370
2370
2371
2371
2372 @command(
2372 @command(
2373 b'debugcomplete',
2373 b'debugcomplete',
2374 [(b'o', b'options', None, _(b'show the command options'))],
2374 [(b'o', b'options', None, _(b'show the command options'))],
2375 _(b'[-o] CMD'),
2375 _(b'[-o] CMD'),
2376 helpcategory=command.CATEGORY_HELP,
2376 helpcategory=command.CATEGORY_HELP,
2377 norepo=True,
2377 norepo=True,
2378 )
2378 )
2379 def debugcomplete(ui, cmd=b'', **opts):
2379 def debugcomplete(ui, cmd=b'', **opts):
2380 """returns the completion list associated with the given command"""
2380 """returns the completion list associated with the given command"""
2381
2381
2382 if opts.get('options'):
2382 if opts.get('options'):
2383 options = []
2383 options = []
2384 otables = [globalopts]
2384 otables = [globalopts]
2385 if cmd:
2385 if cmd:
2386 aliases, entry = cmdutil.findcmd(cmd, table, False)
2386 aliases, entry = cmdutil.findcmd(cmd, table, False)
2387 otables.append(entry[1])
2387 otables.append(entry[1])
2388 for t in otables:
2388 for t in otables:
2389 for o in t:
2389 for o in t:
2390 if b"(DEPRECATED)" in o[3]:
2390 if b"(DEPRECATED)" in o[3]:
2391 continue
2391 continue
2392 if o[0]:
2392 if o[0]:
2393 options.append(b'-%s' % o[0])
2393 options.append(b'-%s' % o[0])
2394 options.append(b'--%s' % o[1])
2394 options.append(b'--%s' % o[1])
2395 ui.write(b"%s\n" % b"\n".join(options))
2395 ui.write(b"%s\n" % b"\n".join(options))
2396 return
2396 return
2397
2397
2398 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2398 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2399 if ui.verbose:
2399 if ui.verbose:
2400 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2400 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2401 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2401 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2402
2402
2403
2403
2404 @command(
2404 @command(
2405 b'diff',
2405 b'diff',
2406 [
2406 [
2407 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2407 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2408 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2408 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2409 ]
2409 ]
2410 + diffopts
2410 + diffopts
2411 + diffopts2
2411 + diffopts2
2412 + walkopts
2412 + walkopts
2413 + subrepoopts,
2413 + subrepoopts,
2414 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2414 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2415 helpcategory=command.CATEGORY_FILE_CONTENTS,
2415 helpcategory=command.CATEGORY_FILE_CONTENTS,
2416 helpbasic=True,
2416 helpbasic=True,
2417 inferrepo=True,
2417 inferrepo=True,
2418 intents={INTENT_READONLY},
2418 intents={INTENT_READONLY},
2419 )
2419 )
2420 def diff(ui, repo, *pats, **opts):
2420 def diff(ui, repo, *pats, **opts):
2421 """diff repository (or selected files)
2421 """diff repository (or selected files)
2422
2422
2423 Show differences between revisions for the specified files.
2423 Show differences between revisions for the specified files.
2424
2424
2425 Differences between files are shown using the unified diff format.
2425 Differences between files are shown using the unified diff format.
2426
2426
2427 .. note::
2427 .. note::
2428
2428
2429 :hg:`diff` may generate unexpected results for merges, as it will
2429 :hg:`diff` may generate unexpected results for merges, as it will
2430 default to comparing against the working directory's first
2430 default to comparing against the working directory's first
2431 parent changeset if no revisions are specified.
2431 parent changeset if no revisions are specified.
2432
2432
2433 When two revision arguments are given, then changes are shown
2433 When two revision arguments are given, then changes are shown
2434 between those revisions. If only one revision is specified then
2434 between those revisions. If only one revision is specified then
2435 that revision is compared to the working directory, and, when no
2435 that revision is compared to the working directory, and, when no
2436 revisions are specified, the working directory files are compared
2436 revisions are specified, the working directory files are compared
2437 to its first parent.
2437 to its first parent.
2438
2438
2439 Alternatively you can specify -c/--change with a revision to see
2439 Alternatively you can specify -c/--change with a revision to see
2440 the changes in that changeset relative to its first parent.
2440 the changes in that changeset relative to its first parent.
2441
2441
2442 Without the -a/--text option, diff will avoid generating diffs of
2442 Without the -a/--text option, diff will avoid generating diffs of
2443 files it detects as binary. With -a, diff will generate a diff
2443 files it detects as binary. With -a, diff will generate a diff
2444 anyway, probably with undesirable results.
2444 anyway, probably with undesirable results.
2445
2445
2446 Use the -g/--git option to generate diffs in the git extended diff
2446 Use the -g/--git option to generate diffs in the git extended diff
2447 format. For more information, read :hg:`help diffs`.
2447 format. For more information, read :hg:`help diffs`.
2448
2448
2449 .. container:: verbose
2449 .. container:: verbose
2450
2450
2451 Examples:
2451 Examples:
2452
2452
2453 - compare a file in the current working directory to its parent::
2453 - compare a file in the current working directory to its parent::
2454
2454
2455 hg diff foo.c
2455 hg diff foo.c
2456
2456
2457 - compare two historical versions of a directory, with rename info::
2457 - compare two historical versions of a directory, with rename info::
2458
2458
2459 hg diff --git -r 1.0:1.2 lib/
2459 hg diff --git -r 1.0:1.2 lib/
2460
2460
2461 - get change stats relative to the last change on some date::
2461 - get change stats relative to the last change on some date::
2462
2462
2463 hg diff --stat -r "date('may 2')"
2463 hg diff --stat -r "date('may 2')"
2464
2464
2465 - diff all newly-added files that contain a keyword::
2465 - diff all newly-added files that contain a keyword::
2466
2466
2467 hg diff "set:added() and grep(GNU)"
2467 hg diff "set:added() and grep(GNU)"
2468
2468
2469 - compare a revision and its parents::
2469 - compare a revision and its parents::
2470
2470
2471 hg diff -c 9353 # compare against first parent
2471 hg diff -c 9353 # compare against first parent
2472 hg diff -r 9353^:9353 # same using revset syntax
2472 hg diff -r 9353^:9353 # same using revset syntax
2473 hg diff -r 9353^2:9353 # compare against the second parent
2473 hg diff -r 9353^2:9353 # compare against the second parent
2474
2474
2475 Returns 0 on success.
2475 Returns 0 on success.
2476 """
2476 """
2477
2477
2478 opts = pycompat.byteskwargs(opts)
2478 opts = pycompat.byteskwargs(opts)
2479 revs = opts.get(b'rev')
2479 revs = opts.get(b'rev')
2480 change = opts.get(b'change')
2480 change = opts.get(b'change')
2481 stat = opts.get(b'stat')
2481 stat = opts.get(b'stat')
2482 reverse = opts.get(b'reverse')
2482 reverse = opts.get(b'reverse')
2483
2483
2484 if revs and change:
2484 if revs and change:
2485 msg = _(b'cannot specify --rev and --change at the same time')
2485 msg = _(b'cannot specify --rev and --change at the same time')
2486 raise error.Abort(msg)
2486 raise error.Abort(msg)
2487 elif change:
2487 elif change:
2488 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2488 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2489 ctx2 = scmutil.revsingle(repo, change, None)
2489 ctx2 = scmutil.revsingle(repo, change, None)
2490 ctx1 = ctx2.p1()
2490 ctx1 = ctx2.p1()
2491 else:
2491 else:
2492 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2492 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2493 ctx1, ctx2 = scmutil.revpair(repo, revs)
2493 ctx1, ctx2 = scmutil.revpair(repo, revs)
2494 node1, node2 = ctx1.node(), ctx2.node()
2494 node1, node2 = ctx1.node(), ctx2.node()
2495
2495
2496 if reverse:
2496 if reverse:
2497 node1, node2 = node2, node1
2497 node1, node2 = node2, node1
2498
2498
2499 diffopts = patch.diffallopts(ui, opts)
2499 diffopts = patch.diffallopts(ui, opts)
2500 m = scmutil.match(ctx2, pats, opts)
2500 m = scmutil.match(ctx2, pats, opts)
2501 m = repo.narrowmatch(m)
2501 m = repo.narrowmatch(m)
2502 ui.pager(b'diff')
2502 ui.pager(b'diff')
2503 logcmdutil.diffordiffstat(
2503 logcmdutil.diffordiffstat(
2504 ui,
2504 ui,
2505 repo,
2505 repo,
2506 diffopts,
2506 diffopts,
2507 node1,
2507 node1,
2508 node2,
2508 node2,
2509 m,
2509 m,
2510 stat=stat,
2510 stat=stat,
2511 listsubrepos=opts.get(b'subrepos'),
2511 listsubrepos=opts.get(b'subrepos'),
2512 root=opts.get(b'root'),
2512 root=opts.get(b'root'),
2513 )
2513 )
2514
2514
2515
2515
2516 @command(
2516 @command(
2517 b'export',
2517 b'export',
2518 [
2518 [
2519 (
2519 (
2520 b'B',
2520 b'B',
2521 b'bookmark',
2521 b'bookmark',
2522 b'',
2522 b'',
2523 _(b'export changes only reachable by given bookmark'),
2523 _(b'export changes only reachable by given bookmark'),
2524 _(b'BOOKMARK'),
2524 _(b'BOOKMARK'),
2525 ),
2525 ),
2526 (
2526 (
2527 b'o',
2527 b'o',
2528 b'output',
2528 b'output',
2529 b'',
2529 b'',
2530 _(b'print output to file with formatted name'),
2530 _(b'print output to file with formatted name'),
2531 _(b'FORMAT'),
2531 _(b'FORMAT'),
2532 ),
2532 ),
2533 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2533 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2534 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2534 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2535 ]
2535 ]
2536 + diffopts
2536 + diffopts
2537 + formatteropts,
2537 + formatteropts,
2538 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2538 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2539 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2539 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2540 helpbasic=True,
2540 helpbasic=True,
2541 intents={INTENT_READONLY},
2541 intents={INTENT_READONLY},
2542 )
2542 )
2543 def export(ui, repo, *changesets, **opts):
2543 def export(ui, repo, *changesets, **opts):
2544 """dump the header and diffs for one or more changesets
2544 """dump the header and diffs for one or more changesets
2545
2545
2546 Print the changeset header and diffs for one or more revisions.
2546 Print the changeset header and diffs for one or more revisions.
2547 If no revision is given, the parent of the working directory is used.
2547 If no revision is given, the parent of the working directory is used.
2548
2548
2549 The information shown in the changeset header is: author, date,
2549 The information shown in the changeset header is: author, date,
2550 branch name (if non-default), changeset hash, parent(s) and commit
2550 branch name (if non-default), changeset hash, parent(s) and commit
2551 comment.
2551 comment.
2552
2552
2553 .. note::
2553 .. note::
2554
2554
2555 :hg:`export` may generate unexpected diff output for merge
2555 :hg:`export` may generate unexpected diff output for merge
2556 changesets, as it will compare the merge changeset against its
2556 changesets, as it will compare the merge changeset against its
2557 first parent only.
2557 first parent only.
2558
2558
2559 Output may be to a file, in which case the name of the file is
2559 Output may be to a file, in which case the name of the file is
2560 given using a template string. See :hg:`help templates`. In addition
2560 given using a template string. See :hg:`help templates`. In addition
2561 to the common template keywords, the following formatting rules are
2561 to the common template keywords, the following formatting rules are
2562 supported:
2562 supported:
2563
2563
2564 :``%%``: literal "%" character
2564 :``%%``: literal "%" character
2565 :``%H``: changeset hash (40 hexadecimal digits)
2565 :``%H``: changeset hash (40 hexadecimal digits)
2566 :``%N``: number of patches being generated
2566 :``%N``: number of patches being generated
2567 :``%R``: changeset revision number
2567 :``%R``: changeset revision number
2568 :``%b``: basename of the exporting repository
2568 :``%b``: basename of the exporting repository
2569 :``%h``: short-form changeset hash (12 hexadecimal digits)
2569 :``%h``: short-form changeset hash (12 hexadecimal digits)
2570 :``%m``: first line of the commit message (only alphanumeric characters)
2570 :``%m``: first line of the commit message (only alphanumeric characters)
2571 :``%n``: zero-padded sequence number, starting at 1
2571 :``%n``: zero-padded sequence number, starting at 1
2572 :``%r``: zero-padded changeset revision number
2572 :``%r``: zero-padded changeset revision number
2573 :``\\``: literal "\\" character
2573 :``\\``: literal "\\" character
2574
2574
2575 Without the -a/--text option, export will avoid generating diffs
2575 Without the -a/--text option, export will avoid generating diffs
2576 of files it detects as binary. With -a, export will generate a
2576 of files it detects as binary. With -a, export will generate a
2577 diff anyway, probably with undesirable results.
2577 diff anyway, probably with undesirable results.
2578
2578
2579 With -B/--bookmark changesets reachable by the given bookmark are
2579 With -B/--bookmark changesets reachable by the given bookmark are
2580 selected.
2580 selected.
2581
2581
2582 Use the -g/--git option to generate diffs in the git extended diff
2582 Use the -g/--git option to generate diffs in the git extended diff
2583 format. See :hg:`help diffs` for more information.
2583 format. See :hg:`help diffs` for more information.
2584
2584
2585 With the --switch-parent option, the diff will be against the
2585 With the --switch-parent option, the diff will be against the
2586 second parent. It can be useful to review a merge.
2586 second parent. It can be useful to review a merge.
2587
2587
2588 .. container:: verbose
2588 .. container:: verbose
2589
2589
2590 Template:
2590 Template:
2591
2591
2592 The following keywords are supported in addition to the common template
2592 The following keywords are supported in addition to the common template
2593 keywords and functions. See also :hg:`help templates`.
2593 keywords and functions. See also :hg:`help templates`.
2594
2594
2595 :diff: String. Diff content.
2595 :diff: String. Diff content.
2596 :parents: List of strings. Parent nodes of the changeset.
2596 :parents: List of strings. Parent nodes of the changeset.
2597
2597
2598 Examples:
2598 Examples:
2599
2599
2600 - use export and import to transplant a bugfix to the current
2600 - use export and import to transplant a bugfix to the current
2601 branch::
2601 branch::
2602
2602
2603 hg export -r 9353 | hg import -
2603 hg export -r 9353 | hg import -
2604
2604
2605 - export all the changesets between two revisions to a file with
2605 - export all the changesets between two revisions to a file with
2606 rename information::
2606 rename information::
2607
2607
2608 hg export --git -r 123:150 > changes.txt
2608 hg export --git -r 123:150 > changes.txt
2609
2609
2610 - split outgoing changes into a series of patches with
2610 - split outgoing changes into a series of patches with
2611 descriptive names::
2611 descriptive names::
2612
2612
2613 hg export -r "outgoing()" -o "%n-%m.patch"
2613 hg export -r "outgoing()" -o "%n-%m.patch"
2614
2614
2615 Returns 0 on success.
2615 Returns 0 on success.
2616 """
2616 """
2617 opts = pycompat.byteskwargs(opts)
2617 opts = pycompat.byteskwargs(opts)
2618 bookmark = opts.get(b'bookmark')
2618 bookmark = opts.get(b'bookmark')
2619 changesets += tuple(opts.get(b'rev', []))
2619 changesets += tuple(opts.get(b'rev', []))
2620
2620
2621 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2621 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2622
2622
2623 if bookmark:
2623 if bookmark:
2624 if bookmark not in repo._bookmarks:
2624 if bookmark not in repo._bookmarks:
2625 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2625 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2626
2626
2627 revs = scmutil.bookmarkrevs(repo, bookmark)
2627 revs = scmutil.bookmarkrevs(repo, bookmark)
2628 else:
2628 else:
2629 if not changesets:
2629 if not changesets:
2630 changesets = [b'.']
2630 changesets = [b'.']
2631
2631
2632 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2632 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2633 revs = scmutil.revrange(repo, changesets)
2633 revs = scmutil.revrange(repo, changesets)
2634
2634
2635 if not revs:
2635 if not revs:
2636 raise error.Abort(_(b"export requires at least one changeset"))
2636 raise error.Abort(_(b"export requires at least one changeset"))
2637 if len(revs) > 1:
2637 if len(revs) > 1:
2638 ui.note(_(b'exporting patches:\n'))
2638 ui.note(_(b'exporting patches:\n'))
2639 else:
2639 else:
2640 ui.note(_(b'exporting patch:\n'))
2640 ui.note(_(b'exporting patch:\n'))
2641
2641
2642 fntemplate = opts.get(b'output')
2642 fntemplate = opts.get(b'output')
2643 if cmdutil.isstdiofilename(fntemplate):
2643 if cmdutil.isstdiofilename(fntemplate):
2644 fntemplate = b''
2644 fntemplate = b''
2645
2645
2646 if fntemplate:
2646 if fntemplate:
2647 fm = formatter.nullformatter(ui, b'export', opts)
2647 fm = formatter.nullformatter(ui, b'export', opts)
2648 else:
2648 else:
2649 ui.pager(b'export')
2649 ui.pager(b'export')
2650 fm = ui.formatter(b'export', opts)
2650 fm = ui.formatter(b'export', opts)
2651 with fm:
2651 with fm:
2652 cmdutil.export(
2652 cmdutil.export(
2653 repo,
2653 repo,
2654 revs,
2654 revs,
2655 fm,
2655 fm,
2656 fntemplate=fntemplate,
2656 fntemplate=fntemplate,
2657 switch_parent=opts.get(b'switch_parent'),
2657 switch_parent=opts.get(b'switch_parent'),
2658 opts=patch.diffallopts(ui, opts),
2658 opts=patch.diffallopts(ui, opts),
2659 )
2659 )
2660
2660
2661
2661
2662 @command(
2662 @command(
2663 b'files',
2663 b'files',
2664 [
2664 [
2665 (
2665 (
2666 b'r',
2666 b'r',
2667 b'rev',
2667 b'rev',
2668 b'',
2668 b'',
2669 _(b'search the repository as it is in REV'),
2669 _(b'search the repository as it is in REV'),
2670 _(b'REV'),
2670 _(b'REV'),
2671 ),
2671 ),
2672 (
2672 (
2673 b'0',
2673 b'0',
2674 b'print0',
2674 b'print0',
2675 None,
2675 None,
2676 _(b'end filenames with NUL, for use with xargs'),
2676 _(b'end filenames with NUL, for use with xargs'),
2677 ),
2677 ),
2678 ]
2678 ]
2679 + walkopts
2679 + walkopts
2680 + formatteropts
2680 + formatteropts
2681 + subrepoopts,
2681 + subrepoopts,
2682 _(b'[OPTION]... [FILE]...'),
2682 _(b'[OPTION]... [FILE]...'),
2683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2684 intents={INTENT_READONLY},
2684 intents={INTENT_READONLY},
2685 )
2685 )
2686 def files(ui, repo, *pats, **opts):
2686 def files(ui, repo, *pats, **opts):
2687 """list tracked files
2687 """list tracked files
2688
2688
2689 Print files under Mercurial control in the working directory or
2689 Print files under Mercurial control in the working directory or
2690 specified revision for given files (excluding removed files).
2690 specified revision for given files (excluding removed files).
2691 Files can be specified as filenames or filesets.
2691 Files can be specified as filenames or filesets.
2692
2692
2693 If no files are given to match, this command prints the names
2693 If no files are given to match, this command prints the names
2694 of all files under Mercurial control.
2694 of all files under Mercurial control.
2695
2695
2696 .. container:: verbose
2696 .. container:: verbose
2697
2697
2698 Template:
2698 Template:
2699
2699
2700 The following keywords are supported in addition to the common template
2700 The following keywords are supported in addition to the common template
2701 keywords and functions. See also :hg:`help templates`.
2701 keywords and functions. See also :hg:`help templates`.
2702
2702
2703 :flags: String. Character denoting file's symlink and executable bits.
2703 :flags: String. Character denoting file's symlink and executable bits.
2704 :path: String. Repository-absolute path of the file.
2704 :path: String. Repository-absolute path of the file.
2705 :size: Integer. Size of the file in bytes.
2705 :size: Integer. Size of the file in bytes.
2706
2706
2707 Examples:
2707 Examples:
2708
2708
2709 - list all files under the current directory::
2709 - list all files under the current directory::
2710
2710
2711 hg files .
2711 hg files .
2712
2712
2713 - shows sizes and flags for current revision::
2713 - shows sizes and flags for current revision::
2714
2714
2715 hg files -vr .
2715 hg files -vr .
2716
2716
2717 - list all files named README::
2717 - list all files named README::
2718
2718
2719 hg files -I "**/README"
2719 hg files -I "**/README"
2720
2720
2721 - list all binary files::
2721 - list all binary files::
2722
2722
2723 hg files "set:binary()"
2723 hg files "set:binary()"
2724
2724
2725 - find files containing a regular expression::
2725 - find files containing a regular expression::
2726
2726
2727 hg files "set:grep('bob')"
2727 hg files "set:grep('bob')"
2728
2728
2729 - search tracked file contents with xargs and grep::
2729 - search tracked file contents with xargs and grep::
2730
2730
2731 hg files -0 | xargs -0 grep foo
2731 hg files -0 | xargs -0 grep foo
2732
2732
2733 See :hg:`help patterns` and :hg:`help filesets` for more information
2733 See :hg:`help patterns` and :hg:`help filesets` for more information
2734 on specifying file patterns.
2734 on specifying file patterns.
2735
2735
2736 Returns 0 if a match is found, 1 otherwise.
2736 Returns 0 if a match is found, 1 otherwise.
2737
2737
2738 """
2738 """
2739
2739
2740 opts = pycompat.byteskwargs(opts)
2740 opts = pycompat.byteskwargs(opts)
2741 rev = opts.get(b'rev')
2741 rev = opts.get(b'rev')
2742 if rev:
2742 if rev:
2743 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2743 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2744 ctx = scmutil.revsingle(repo, rev, None)
2744 ctx = scmutil.revsingle(repo, rev, None)
2745
2745
2746 end = b'\n'
2746 end = b'\n'
2747 if opts.get(b'print0'):
2747 if opts.get(b'print0'):
2748 end = b'\0'
2748 end = b'\0'
2749 fmt = b'%s' + end
2749 fmt = b'%s' + end
2750
2750
2751 m = scmutil.match(ctx, pats, opts)
2751 m = scmutil.match(ctx, pats, opts)
2752 ui.pager(b'files')
2752 ui.pager(b'files')
2753 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2753 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2754 with ui.formatter(b'files', opts) as fm:
2754 with ui.formatter(b'files', opts) as fm:
2755 return cmdutil.files(
2755 return cmdutil.files(
2756 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2756 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2757 )
2757 )
2758
2758
2759
2759
2760 @command(
2760 @command(
2761 b'forget',
2761 b'forget',
2762 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2762 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2763 + walkopts
2763 + walkopts
2764 + dryrunopts,
2764 + dryrunopts,
2765 _(b'[OPTION]... FILE...'),
2765 _(b'[OPTION]... FILE...'),
2766 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2766 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2767 helpbasic=True,
2767 helpbasic=True,
2768 inferrepo=True,
2768 inferrepo=True,
2769 )
2769 )
2770 def forget(ui, repo, *pats, **opts):
2770 def forget(ui, repo, *pats, **opts):
2771 """forget the specified files on the next commit
2771 """forget the specified files on the next commit
2772
2772
2773 Mark the specified files so they will no longer be tracked
2773 Mark the specified files so they will no longer be tracked
2774 after the next commit.
2774 after the next commit.
2775
2775
2776 This only removes files from the current branch, not from the
2776 This only removes files from the current branch, not from the
2777 entire project history, and it does not delete them from the
2777 entire project history, and it does not delete them from the
2778 working directory.
2778 working directory.
2779
2779
2780 To delete the file from the working directory, see :hg:`remove`.
2780 To delete the file from the working directory, see :hg:`remove`.
2781
2781
2782 To undo a forget before the next commit, see :hg:`add`.
2782 To undo a forget before the next commit, see :hg:`add`.
2783
2783
2784 .. container:: verbose
2784 .. container:: verbose
2785
2785
2786 Examples:
2786 Examples:
2787
2787
2788 - forget newly-added binary files::
2788 - forget newly-added binary files::
2789
2789
2790 hg forget "set:added() and binary()"
2790 hg forget "set:added() and binary()"
2791
2791
2792 - forget files that would be excluded by .hgignore::
2792 - forget files that would be excluded by .hgignore::
2793
2793
2794 hg forget "set:hgignore()"
2794 hg forget "set:hgignore()"
2795
2795
2796 Returns 0 on success.
2796 Returns 0 on success.
2797 """
2797 """
2798
2798
2799 opts = pycompat.byteskwargs(opts)
2799 opts = pycompat.byteskwargs(opts)
2800 if not pats:
2800 if not pats:
2801 raise error.Abort(_(b'no files specified'))
2801 raise error.Abort(_(b'no files specified'))
2802
2802
2803 m = scmutil.match(repo[None], pats, opts)
2803 m = scmutil.match(repo[None], pats, opts)
2804 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2804 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2805 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2805 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2806 rejected = cmdutil.forget(
2806 rejected = cmdutil.forget(
2807 ui,
2807 ui,
2808 repo,
2808 repo,
2809 m,
2809 m,
2810 prefix=b"",
2810 prefix=b"",
2811 uipathfn=uipathfn,
2811 uipathfn=uipathfn,
2812 explicitonly=False,
2812 explicitonly=False,
2813 dryrun=dryrun,
2813 dryrun=dryrun,
2814 interactive=interactive,
2814 interactive=interactive,
2815 )[0]
2815 )[0]
2816 return rejected and 1 or 0
2816 return rejected and 1 or 0
2817
2817
2818
2818
2819 @command(
2819 @command(
2820 b'graft',
2820 b'graft',
2821 [
2821 [
2822 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2822 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2823 (
2823 (
2824 b'',
2824 b'',
2825 b'base',
2825 b'base',
2826 b'',
2826 b'',
2827 _(b'base revision when doing the graft merge (ADVANCED)'),
2827 _(b'base revision when doing the graft merge (ADVANCED)'),
2828 _(b'REV'),
2828 _(b'REV'),
2829 ),
2829 ),
2830 (b'c', b'continue', False, _(b'resume interrupted graft')),
2830 (b'c', b'continue', False, _(b'resume interrupted graft')),
2831 (b'', b'stop', False, _(b'stop interrupted graft')),
2831 (b'', b'stop', False, _(b'stop interrupted graft')),
2832 (b'', b'abort', False, _(b'abort interrupted graft')),
2832 (b'', b'abort', False, _(b'abort interrupted graft')),
2833 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2833 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2834 (b'', b'log', None, _(b'append graft info to log message')),
2834 (b'', b'log', None, _(b'append graft info to log message')),
2835 (
2835 (
2836 b'',
2836 b'',
2837 b'no-commit',
2837 b'no-commit',
2838 None,
2838 None,
2839 _(b"don't commit, just apply the changes in working directory"),
2839 _(b"don't commit, just apply the changes in working directory"),
2840 ),
2840 ),
2841 (b'f', b'force', False, _(b'force graft')),
2841 (b'f', b'force', False, _(b'force graft')),
2842 (
2842 (
2843 b'D',
2843 b'D',
2844 b'currentdate',
2844 b'currentdate',
2845 False,
2845 False,
2846 _(b'record the current date as commit date'),
2846 _(b'record the current date as commit date'),
2847 ),
2847 ),
2848 (
2848 (
2849 b'U',
2849 b'U',
2850 b'currentuser',
2850 b'currentuser',
2851 False,
2851 False,
2852 _(b'record the current user as committer'),
2852 _(b'record the current user as committer'),
2853 ),
2853 ),
2854 ]
2854 ]
2855 + commitopts2
2855 + commitopts2
2856 + mergetoolopts
2856 + mergetoolopts
2857 + dryrunopts,
2857 + dryrunopts,
2858 _(b'[OPTION]... [-r REV]... REV...'),
2858 _(b'[OPTION]... [-r REV]... REV...'),
2859 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2859 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2860 )
2860 )
2861 def graft(ui, repo, *revs, **opts):
2861 def graft(ui, repo, *revs, **opts):
2862 '''copy changes from other branches onto the current branch
2862 '''copy changes from other branches onto the current branch
2863
2863
2864 This command uses Mercurial's merge logic to copy individual
2864 This command uses Mercurial's merge logic to copy individual
2865 changes from other branches without merging branches in the
2865 changes from other branches without merging branches in the
2866 history graph. This is sometimes known as 'backporting' or
2866 history graph. This is sometimes known as 'backporting' or
2867 'cherry-picking'. By default, graft will copy user, date, and
2867 'cherry-picking'. By default, graft will copy user, date, and
2868 description from the source changesets.
2868 description from the source changesets.
2869
2869
2870 Changesets that are ancestors of the current revision, that have
2870 Changesets that are ancestors of the current revision, that have
2871 already been grafted, or that are merges will be skipped.
2871 already been grafted, or that are merges will be skipped.
2872
2872
2873 If --log is specified, log messages will have a comment appended
2873 If --log is specified, log messages will have a comment appended
2874 of the form::
2874 of the form::
2875
2875
2876 (grafted from CHANGESETHASH)
2876 (grafted from CHANGESETHASH)
2877
2877
2878 If --force is specified, revisions will be grafted even if they
2878 If --force is specified, revisions will be grafted even if they
2879 are already ancestors of, or have been grafted to, the destination.
2879 are already ancestors of, or have been grafted to, the destination.
2880 This is useful when the revisions have since been backed out.
2880 This is useful when the revisions have since been backed out.
2881
2881
2882 If a graft merge results in conflicts, the graft process is
2882 If a graft merge results in conflicts, the graft process is
2883 interrupted so that the current merge can be manually resolved.
2883 interrupted so that the current merge can be manually resolved.
2884 Once all conflicts are addressed, the graft process can be
2884 Once all conflicts are addressed, the graft process can be
2885 continued with the -c/--continue option.
2885 continued with the -c/--continue option.
2886
2886
2887 The -c/--continue option reapplies all the earlier options.
2887 The -c/--continue option reapplies all the earlier options.
2888
2888
2889 .. container:: verbose
2889 .. container:: verbose
2890
2890
2891 The --base option exposes more of how graft internally uses merge with a
2891 The --base option exposes more of how graft internally uses merge with a
2892 custom base revision. --base can be used to specify another ancestor than
2892 custom base revision. --base can be used to specify another ancestor than
2893 the first and only parent.
2893 the first and only parent.
2894
2894
2895 The command::
2895 The command::
2896
2896
2897 hg graft -r 345 --base 234
2897 hg graft -r 345 --base 234
2898
2898
2899 is thus pretty much the same as::
2899 is thus pretty much the same as::
2900
2900
2901 hg diff -r 234 -r 345 | hg import
2901 hg diff -r 234 -r 345 | hg import
2902
2902
2903 but using merge to resolve conflicts and track moved files.
2903 but using merge to resolve conflicts and track moved files.
2904
2904
2905 The result of a merge can thus be backported as a single commit by
2905 The result of a merge can thus be backported as a single commit by
2906 specifying one of the merge parents as base, and thus effectively
2906 specifying one of the merge parents as base, and thus effectively
2907 grafting the changes from the other side.
2907 grafting the changes from the other side.
2908
2908
2909 It is also possible to collapse multiple changesets and clean up history
2909 It is also possible to collapse multiple changesets and clean up history
2910 by specifying another ancestor as base, much like rebase --collapse
2910 by specifying another ancestor as base, much like rebase --collapse
2911 --keep.
2911 --keep.
2912
2912
2913 The commit message can be tweaked after the fact using commit --amend .
2913 The commit message can be tweaked after the fact using commit --amend .
2914
2914
2915 For using non-ancestors as the base to backout changes, see the backout
2915 For using non-ancestors as the base to backout changes, see the backout
2916 command and the hidden --parent option.
2916 command and the hidden --parent option.
2917
2917
2918 .. container:: verbose
2918 .. container:: verbose
2919
2919
2920 Examples:
2920 Examples:
2921
2921
2922 - copy a single change to the stable branch and edit its description::
2922 - copy a single change to the stable branch and edit its description::
2923
2923
2924 hg update stable
2924 hg update stable
2925 hg graft --edit 9393
2925 hg graft --edit 9393
2926
2926
2927 - graft a range of changesets with one exception, updating dates::
2927 - graft a range of changesets with one exception, updating dates::
2928
2928
2929 hg graft -D "2085::2093 and not 2091"
2929 hg graft -D "2085::2093 and not 2091"
2930
2930
2931 - continue a graft after resolving conflicts::
2931 - continue a graft after resolving conflicts::
2932
2932
2933 hg graft -c
2933 hg graft -c
2934
2934
2935 - show the source of a grafted changeset::
2935 - show the source of a grafted changeset::
2936
2936
2937 hg log --debug -r .
2937 hg log --debug -r .
2938
2938
2939 - show revisions sorted by date::
2939 - show revisions sorted by date::
2940
2940
2941 hg log -r "sort(all(), date)"
2941 hg log -r "sort(all(), date)"
2942
2942
2943 - backport the result of a merge as a single commit::
2943 - backport the result of a merge as a single commit::
2944
2944
2945 hg graft -r 123 --base 123^
2945 hg graft -r 123 --base 123^
2946
2946
2947 - land a feature branch as one changeset::
2947 - land a feature branch as one changeset::
2948
2948
2949 hg up -cr default
2949 hg up -cr default
2950 hg graft -r featureX --base "ancestor('featureX', 'default')"
2950 hg graft -r featureX --base "ancestor('featureX', 'default')"
2951
2951
2952 See :hg:`help revisions` for more about specifying revisions.
2952 See :hg:`help revisions` for more about specifying revisions.
2953
2953
2954 Returns 0 on successful completion.
2954 Returns 0 on successful completion.
2955 '''
2955 '''
2956 with repo.wlock():
2956 with repo.wlock():
2957 return _dograft(ui, repo, *revs, **opts)
2957 return _dograft(ui, repo, *revs, **opts)
2958
2958
2959
2959
2960 def _dograft(ui, repo, *revs, **opts):
2960 def _dograft(ui, repo, *revs, **opts):
2961 opts = pycompat.byteskwargs(opts)
2961 opts = pycompat.byteskwargs(opts)
2962 if revs and opts.get(b'rev'):
2962 if revs and opts.get(b'rev'):
2963 ui.warn(
2963 ui.warn(
2964 _(
2964 _(
2965 b'warning: inconsistent use of --rev might give unexpected '
2965 b'warning: inconsistent use of --rev might give unexpected '
2966 b'revision ordering!\n'
2966 b'revision ordering!\n'
2967 )
2967 )
2968 )
2968 )
2969
2969
2970 revs = list(revs)
2970 revs = list(revs)
2971 revs.extend(opts.get(b'rev'))
2971 revs.extend(opts.get(b'rev'))
2972 basectx = None
2972 basectx = None
2973 if opts.get(b'base'):
2973 if opts.get(b'base'):
2974 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2974 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2975 # a dict of data to be stored in state file
2975 # a dict of data to be stored in state file
2976 statedata = {}
2976 statedata = {}
2977 # list of new nodes created by ongoing graft
2977 # list of new nodes created by ongoing graft
2978 statedata[b'newnodes'] = []
2978 statedata[b'newnodes'] = []
2979
2979
2980 cmdutil.resolvecommitoptions(ui, opts)
2980 cmdutil.resolvecommitoptions(ui, opts)
2981
2981
2982 editor = cmdutil.getcommiteditor(
2982 editor = cmdutil.getcommiteditor(
2983 editform=b'graft', **pycompat.strkwargs(opts)
2983 editform=b'graft', **pycompat.strkwargs(opts)
2984 )
2984 )
2985
2985
2986 cont = False
2986 cont = False
2987 if opts.get(b'no_commit'):
2987 if opts.get(b'no_commit'):
2988 if opts.get(b'edit'):
2988 if opts.get(b'edit'):
2989 raise error.Abort(
2989 raise error.Abort(
2990 _(b"cannot specify --no-commit and --edit together")
2990 _(b"cannot specify --no-commit and --edit together")
2991 )
2991 )
2992 if opts.get(b'currentuser'):
2992 if opts.get(b'currentuser'):
2993 raise error.Abort(
2993 raise error.Abort(
2994 _(b"cannot specify --no-commit and --currentuser together")
2994 _(b"cannot specify --no-commit and --currentuser together")
2995 )
2995 )
2996 if opts.get(b'currentdate'):
2996 if opts.get(b'currentdate'):
2997 raise error.Abort(
2997 raise error.Abort(
2998 _(b"cannot specify --no-commit and --currentdate together")
2998 _(b"cannot specify --no-commit and --currentdate together")
2999 )
2999 )
3000 if opts.get(b'log'):
3000 if opts.get(b'log'):
3001 raise error.Abort(
3001 raise error.Abort(
3002 _(b"cannot specify --no-commit and --log together")
3002 _(b"cannot specify --no-commit and --log together")
3003 )
3003 )
3004
3004
3005 graftstate = statemod.cmdstate(repo, b'graftstate')
3005 graftstate = statemod.cmdstate(repo, b'graftstate')
3006
3006
3007 if opts.get(b'stop'):
3007 if opts.get(b'stop'):
3008 if opts.get(b'continue'):
3008 if opts.get(b'continue'):
3009 raise error.Abort(
3009 raise error.Abort(
3010 _(b"cannot use '--continue' and '--stop' together")
3010 _(b"cannot use '--continue' and '--stop' together")
3011 )
3011 )
3012 if opts.get(b'abort'):
3012 if opts.get(b'abort'):
3013 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3013 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3014
3014
3015 if any(
3015 if any(
3016 (
3016 (
3017 opts.get(b'edit'),
3017 opts.get(b'edit'),
3018 opts.get(b'log'),
3018 opts.get(b'log'),
3019 opts.get(b'user'),
3019 opts.get(b'user'),
3020 opts.get(b'date'),
3020 opts.get(b'date'),
3021 opts.get(b'currentdate'),
3021 opts.get(b'currentdate'),
3022 opts.get(b'currentuser'),
3022 opts.get(b'currentuser'),
3023 opts.get(b'rev'),
3023 opts.get(b'rev'),
3024 )
3024 )
3025 ):
3025 ):
3026 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3026 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3027 return _stopgraft(ui, repo, graftstate)
3027 return _stopgraft(ui, repo, graftstate)
3028 elif opts.get(b'abort'):
3028 elif opts.get(b'abort'):
3029 if opts.get(b'continue'):
3029 if opts.get(b'continue'):
3030 raise error.Abort(
3030 raise error.Abort(
3031 _(b"cannot use '--continue' and '--abort' together")
3031 _(b"cannot use '--continue' and '--abort' together")
3032 )
3032 )
3033 if any(
3033 if any(
3034 (
3034 (
3035 opts.get(b'edit'),
3035 opts.get(b'edit'),
3036 opts.get(b'log'),
3036 opts.get(b'log'),
3037 opts.get(b'user'),
3037 opts.get(b'user'),
3038 opts.get(b'date'),
3038 opts.get(b'date'),
3039 opts.get(b'currentdate'),
3039 opts.get(b'currentdate'),
3040 opts.get(b'currentuser'),
3040 opts.get(b'currentuser'),
3041 opts.get(b'rev'),
3041 opts.get(b'rev'),
3042 )
3042 )
3043 ):
3043 ):
3044 raise error.Abort(
3044 raise error.Abort(
3045 _(b"cannot specify any other flag with '--abort'")
3045 _(b"cannot specify any other flag with '--abort'")
3046 )
3046 )
3047
3047
3048 return cmdutil.abortgraft(ui, repo, graftstate)
3048 return cmdutil.abortgraft(ui, repo, graftstate)
3049 elif opts.get(b'continue'):
3049 elif opts.get(b'continue'):
3050 cont = True
3050 cont = True
3051 if revs:
3051 if revs:
3052 raise error.Abort(_(b"can't specify --continue and revisions"))
3052 raise error.Abort(_(b"can't specify --continue and revisions"))
3053 # read in unfinished revisions
3053 # read in unfinished revisions
3054 if graftstate.exists():
3054 if graftstate.exists():
3055 statedata = cmdutil.readgraftstate(repo, graftstate)
3055 statedata = cmdutil.readgraftstate(repo, graftstate)
3056 if statedata.get(b'date'):
3056 if statedata.get(b'date'):
3057 opts[b'date'] = statedata[b'date']
3057 opts[b'date'] = statedata[b'date']
3058 if statedata.get(b'user'):
3058 if statedata.get(b'user'):
3059 opts[b'user'] = statedata[b'user']
3059 opts[b'user'] = statedata[b'user']
3060 if statedata.get(b'log'):
3060 if statedata.get(b'log'):
3061 opts[b'log'] = True
3061 opts[b'log'] = True
3062 if statedata.get(b'no_commit'):
3062 if statedata.get(b'no_commit'):
3063 opts[b'no_commit'] = statedata.get(b'no_commit')
3063 opts[b'no_commit'] = statedata.get(b'no_commit')
3064 nodes = statedata[b'nodes']
3064 nodes = statedata[b'nodes']
3065 revs = [repo[node].rev() for node in nodes]
3065 revs = [repo[node].rev() for node in nodes]
3066 else:
3066 else:
3067 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3067 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3068 else:
3068 else:
3069 if not revs:
3069 if not revs:
3070 raise error.Abort(_(b'no revisions specified'))
3070 raise error.Abort(_(b'no revisions specified'))
3071 cmdutil.checkunfinished(repo)
3071 cmdutil.checkunfinished(repo)
3072 cmdutil.bailifchanged(repo)
3072 cmdutil.bailifchanged(repo)
3073 revs = scmutil.revrange(repo, revs)
3073 revs = scmutil.revrange(repo, revs)
3074
3074
3075 skipped = set()
3075 skipped = set()
3076 if basectx is None:
3076 if basectx is None:
3077 # check for merges
3077 # check for merges
3078 for rev in repo.revs(b'%ld and merge()', revs):
3078 for rev in repo.revs(b'%ld and merge()', revs):
3079 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3079 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3080 skipped.add(rev)
3080 skipped.add(rev)
3081 revs = [r for r in revs if r not in skipped]
3081 revs = [r for r in revs if r not in skipped]
3082 if not revs:
3082 if not revs:
3083 return -1
3083 return -1
3084 if basectx is not None and len(revs) != 1:
3084 if basectx is not None and len(revs) != 1:
3085 raise error.Abort(_(b'only one revision allowed with --base '))
3085 raise error.Abort(_(b'only one revision allowed with --base '))
3086
3086
3087 # Don't check in the --continue case, in effect retaining --force across
3087 # Don't check in the --continue case, in effect retaining --force across
3088 # --continues. That's because without --force, any revisions we decided to
3088 # --continues. That's because without --force, any revisions we decided to
3089 # skip would have been filtered out here, so they wouldn't have made their
3089 # skip would have been filtered out here, so they wouldn't have made their
3090 # way to the graftstate. With --force, any revisions we would have otherwise
3090 # way to the graftstate. With --force, any revisions we would have otherwise
3091 # skipped would not have been filtered out, and if they hadn't been applied
3091 # skipped would not have been filtered out, and if they hadn't been applied
3092 # already, they'd have been in the graftstate.
3092 # already, they'd have been in the graftstate.
3093 if not (cont or opts.get(b'force')) and basectx is None:
3093 if not (cont or opts.get(b'force')) and basectx is None:
3094 # check for ancestors of dest branch
3094 # check for ancestors of dest branch
3095 ancestors = repo.revs(b'%ld & (::.)', revs)
3095 ancestors = repo.revs(b'%ld & (::.)', revs)
3096 for rev in ancestors:
3096 for rev in ancestors:
3097 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3097 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3098
3098
3099 revs = [r for r in revs if r not in ancestors]
3099 revs = [r for r in revs if r not in ancestors]
3100
3100
3101 if not revs:
3101 if not revs:
3102 return -1
3102 return -1
3103
3103
3104 # analyze revs for earlier grafts
3104 # analyze revs for earlier grafts
3105 ids = {}
3105 ids = {}
3106 for ctx in repo.set(b"%ld", revs):
3106 for ctx in repo.set(b"%ld", revs):
3107 ids[ctx.hex()] = ctx.rev()
3107 ids[ctx.hex()] = ctx.rev()
3108 n = ctx.extra().get(b'source')
3108 n = ctx.extra().get(b'source')
3109 if n:
3109 if n:
3110 ids[n] = ctx.rev()
3110 ids[n] = ctx.rev()
3111
3111
3112 # check ancestors for earlier grafts
3112 # check ancestors for earlier grafts
3113 ui.debug(b'scanning for duplicate grafts\n')
3113 ui.debug(b'scanning for duplicate grafts\n')
3114
3114
3115 # The only changesets we can be sure doesn't contain grafts of any
3115 # The only changesets we can be sure doesn't contain grafts of any
3116 # revs, are the ones that are common ancestors of *all* revs:
3116 # revs, are the ones that are common ancestors of *all* revs:
3117 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3117 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3118 ctx = repo[rev]
3118 ctx = repo[rev]
3119 n = ctx.extra().get(b'source')
3119 n = ctx.extra().get(b'source')
3120 if n in ids:
3120 if n in ids:
3121 try:
3121 try:
3122 r = repo[n].rev()
3122 r = repo[n].rev()
3123 except error.RepoLookupError:
3123 except error.RepoLookupError:
3124 r = None
3124 r = None
3125 if r in revs:
3125 if r in revs:
3126 ui.warn(
3126 ui.warn(
3127 _(
3127 _(
3128 b'skipping revision %d:%s '
3128 b'skipping revision %d:%s '
3129 b'(already grafted to %d:%s)\n'
3129 b'(already grafted to %d:%s)\n'
3130 )
3130 )
3131 % (r, repo[r], rev, ctx)
3131 % (r, repo[r], rev, ctx)
3132 )
3132 )
3133 revs.remove(r)
3133 revs.remove(r)
3134 elif ids[n] in revs:
3134 elif ids[n] in revs:
3135 if r is None:
3135 if r is None:
3136 ui.warn(
3136 ui.warn(
3137 _(
3137 _(
3138 b'skipping already grafted revision %d:%s '
3138 b'skipping already grafted revision %d:%s '
3139 b'(%d:%s also has unknown origin %s)\n'
3139 b'(%d:%s also has unknown origin %s)\n'
3140 )
3140 )
3141 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3141 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3142 )
3142 )
3143 else:
3143 else:
3144 ui.warn(
3144 ui.warn(
3145 _(
3145 _(
3146 b'skipping already grafted revision %d:%s '
3146 b'skipping already grafted revision %d:%s '
3147 b'(%d:%s also has origin %d:%s)\n'
3147 b'(%d:%s also has origin %d:%s)\n'
3148 )
3148 )
3149 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3149 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3150 )
3150 )
3151 revs.remove(ids[n])
3151 revs.remove(ids[n])
3152 elif ctx.hex() in ids:
3152 elif ctx.hex() in ids:
3153 r = ids[ctx.hex()]
3153 r = ids[ctx.hex()]
3154 if r in revs:
3154 if r in revs:
3155 ui.warn(
3155 ui.warn(
3156 _(
3156 _(
3157 b'skipping already grafted revision %d:%s '
3157 b'skipping already grafted revision %d:%s '
3158 b'(was grafted from %d:%s)\n'
3158 b'(was grafted from %d:%s)\n'
3159 )
3159 )
3160 % (r, repo[r], rev, ctx)
3160 % (r, repo[r], rev, ctx)
3161 )
3161 )
3162 revs.remove(r)
3162 revs.remove(r)
3163 if not revs:
3163 if not revs:
3164 return -1
3164 return -1
3165
3165
3166 if opts.get(b'no_commit'):
3166 if opts.get(b'no_commit'):
3167 statedata[b'no_commit'] = True
3167 statedata[b'no_commit'] = True
3168 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3168 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3169 desc = b'%d:%s "%s"' % (
3169 desc = b'%d:%s "%s"' % (
3170 ctx.rev(),
3170 ctx.rev(),
3171 ctx,
3171 ctx,
3172 ctx.description().split(b'\n', 1)[0],
3172 ctx.description().split(b'\n', 1)[0],
3173 )
3173 )
3174 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3174 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3175 if names:
3175 if names:
3176 desc += b' (%s)' % b' '.join(names)
3176 desc += b' (%s)' % b' '.join(names)
3177 ui.status(_(b'grafting %s\n') % desc)
3177 ui.status(_(b'grafting %s\n') % desc)
3178 if opts.get(b'dry_run'):
3178 if opts.get(b'dry_run'):
3179 continue
3179 continue
3180
3180
3181 source = ctx.extra().get(b'source')
3181 source = ctx.extra().get(b'source')
3182 extra = {}
3182 extra = {}
3183 if source:
3183 if source:
3184 extra[b'source'] = source
3184 extra[b'source'] = source
3185 extra[b'intermediate-source'] = ctx.hex()
3185 extra[b'intermediate-source'] = ctx.hex()
3186 else:
3186 else:
3187 extra[b'source'] = ctx.hex()
3187 extra[b'source'] = ctx.hex()
3188 user = ctx.user()
3188 user = ctx.user()
3189 if opts.get(b'user'):
3189 if opts.get(b'user'):
3190 user = opts[b'user']
3190 user = opts[b'user']
3191 statedata[b'user'] = user
3191 statedata[b'user'] = user
3192 date = ctx.date()
3192 date = ctx.date()
3193 if opts.get(b'date'):
3193 if opts.get(b'date'):
3194 date = opts[b'date']
3194 date = opts[b'date']
3195 statedata[b'date'] = date
3195 statedata[b'date'] = date
3196 message = ctx.description()
3196 message = ctx.description()
3197 if opts.get(b'log'):
3197 if opts.get(b'log'):
3198 message += b'\n(grafted from %s)' % ctx.hex()
3198 message += b'\n(grafted from %s)' % ctx.hex()
3199 statedata[b'log'] = True
3199 statedata[b'log'] = True
3200
3200
3201 # we don't merge the first commit when continuing
3201 # we don't merge the first commit when continuing
3202 if not cont:
3202 if not cont:
3203 # perform the graft merge with p1(rev) as 'ancestor'
3203 # perform the graft merge with p1(rev) as 'ancestor'
3204 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3204 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3205 base = ctx.p1() if basectx is None else basectx
3205 base = ctx.p1() if basectx is None else basectx
3206 with ui.configoverride(overrides, b'graft'):
3206 with ui.configoverride(overrides, b'graft'):
3207 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3207 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3208 # report any conflicts
3208 # report any conflicts
3209 if stats.unresolvedcount > 0:
3209 if stats.unresolvedcount > 0:
3210 # write out state for --continue
3210 # write out state for --continue
3211 nodes = [repo[rev].hex() for rev in revs[pos:]]
3211 nodes = [repo[rev].hex() for rev in revs[pos:]]
3212 statedata[b'nodes'] = nodes
3212 statedata[b'nodes'] = nodes
3213 stateversion = 1
3213 stateversion = 1
3214 graftstate.save(stateversion, statedata)
3214 graftstate.save(stateversion, statedata)
3215 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3215 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3216 raise error.Abort(
3216 raise error.Abort(
3217 _(b"unresolved conflicts, can't continue"), hint=hint
3217 _(b"unresolved conflicts, can't continue"), hint=hint
3218 )
3218 )
3219 else:
3219 else:
3220 cont = False
3220 cont = False
3221
3221
3222 # commit if --no-commit is false
3222 # commit if --no-commit is false
3223 if not opts.get(b'no_commit'):
3223 if not opts.get(b'no_commit'):
3224 node = repo.commit(
3224 node = repo.commit(
3225 text=message, user=user, date=date, extra=extra, editor=editor
3225 text=message, user=user, date=date, extra=extra, editor=editor
3226 )
3226 )
3227 if node is None:
3227 if node is None:
3228 ui.warn(
3228 ui.warn(
3229 _(b'note: graft of %d:%s created no changes to commit\n')
3229 _(b'note: graft of %d:%s created no changes to commit\n')
3230 % (ctx.rev(), ctx)
3230 % (ctx.rev(), ctx)
3231 )
3231 )
3232 # checking that newnodes exist because old state files won't have it
3232 # checking that newnodes exist because old state files won't have it
3233 elif statedata.get(b'newnodes') is not None:
3233 elif statedata.get(b'newnodes') is not None:
3234 statedata[b'newnodes'].append(node)
3234 statedata[b'newnodes'].append(node)
3235
3235
3236 # remove state when we complete successfully
3236 # remove state when we complete successfully
3237 if not opts.get(b'dry_run'):
3237 if not opts.get(b'dry_run'):
3238 graftstate.delete()
3238 graftstate.delete()
3239
3239
3240 return 0
3240 return 0
3241
3241
3242
3242
3243 def _stopgraft(ui, repo, graftstate):
3243 def _stopgraft(ui, repo, graftstate):
3244 """stop the interrupted graft"""
3244 """stop the interrupted graft"""
3245 if not graftstate.exists():
3245 if not graftstate.exists():
3246 raise error.Abort(_(b"no interrupted graft found"))
3246 raise error.Abort(_(b"no interrupted graft found"))
3247 pctx = repo[b'.']
3247 pctx = repo[b'.']
3248 hg.updaterepo(repo, pctx.node(), overwrite=True)
3248 hg.updaterepo(repo, pctx.node(), overwrite=True)
3249 graftstate.delete()
3249 graftstate.delete()
3250 ui.status(_(b"stopped the interrupted graft\n"))
3250 ui.status(_(b"stopped the interrupted graft\n"))
3251 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3251 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3252 return 0
3252 return 0
3253
3253
3254
3254
3255 statemod.addunfinished(
3255 statemod.addunfinished(
3256 b'graft',
3256 b'graft',
3257 fname=b'graftstate',
3257 fname=b'graftstate',
3258 clearable=True,
3258 clearable=True,
3259 stopflag=True,
3259 stopflag=True,
3260 continueflag=True,
3260 continueflag=True,
3261 abortfunc=cmdutil.hgabortgraft,
3261 abortfunc=cmdutil.hgabortgraft,
3262 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3262 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3263 )
3263 )
3264
3264
3265
3265
3266 @command(
3266 @command(
3267 b'grep',
3267 b'grep',
3268 [
3268 [
3269 (b'0', b'print0', None, _(b'end fields with NUL')),
3269 (b'0', b'print0', None, _(b'end fields with NUL')),
3270 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3270 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3271 (
3271 (
3272 b'',
3272 b'',
3273 b'diff',
3273 b'diff',
3274 None,
3274 None,
3275 _(
3275 _(
3276 b'search revision differences for when the pattern was added '
3276 b'search revision differences for when the pattern was added '
3277 b'or removed'
3277 b'or removed'
3278 ),
3278 ),
3279 ),
3279 ),
3280 (b'a', b'text', None, _(b'treat all files as text')),
3280 (b'a', b'text', None, _(b'treat all files as text')),
3281 (
3281 (
3282 b'f',
3282 b'f',
3283 b'follow',
3283 b'follow',
3284 None,
3284 None,
3285 _(
3285 _(
3286 b'follow changeset history,'
3286 b'follow changeset history,'
3287 b' or file history across copies and renames'
3287 b' or file history across copies and renames'
3288 ),
3288 ),
3289 ),
3289 ),
3290 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3290 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3291 (
3291 (
3292 b'l',
3292 b'l',
3293 b'files-with-matches',
3293 b'files-with-matches',
3294 None,
3294 None,
3295 _(b'print only filenames and revisions that match'),
3295 _(b'print only filenames and revisions that match'),
3296 ),
3296 ),
3297 (b'n', b'line-number', None, _(b'print matching line numbers')),
3297 (b'n', b'line-number', None, _(b'print matching line numbers')),
3298 (
3298 (
3299 b'r',
3299 b'r',
3300 b'rev',
3300 b'rev',
3301 [],
3301 [],
3302 _(b'search files changed within revision range'),
3302 _(b'search files changed within revision range'),
3303 _(b'REV'),
3303 _(b'REV'),
3304 ),
3304 ),
3305 (
3305 (
3306 b'',
3306 b'',
3307 b'all-files',
3307 b'all-files',
3308 None,
3308 None,
3309 _(
3309 _(
3310 b'include all files in the changeset while grepping (DEPRECATED)'
3310 b'include all files in the changeset while grepping (DEPRECATED)'
3311 ),
3311 ),
3312 ),
3312 ),
3313 (b'u', b'user', None, _(b'list the author (long with -v)')),
3313 (b'u', b'user', None, _(b'list the author (long with -v)')),
3314 (b'd', b'date', None, _(b'list the date (short with -q)')),
3314 (b'd', b'date', None, _(b'list the date (short with -q)')),
3315 ]
3315 ]
3316 + formatteropts
3316 + formatteropts
3317 + walkopts,
3317 + walkopts,
3318 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3318 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3319 helpcategory=command.CATEGORY_FILE_CONTENTS,
3319 helpcategory=command.CATEGORY_FILE_CONTENTS,
3320 inferrepo=True,
3320 inferrepo=True,
3321 intents={INTENT_READONLY},
3321 intents={INTENT_READONLY},
3322 )
3322 )
3323 def grep(ui, repo, pattern, *pats, **opts):
3323 def grep(ui, repo, pattern, *pats, **opts):
3324 """search for a pattern in specified files
3324 """search for a pattern in specified files
3325
3325
3326 Search the working directory or revision history for a regular
3326 Search the working directory or revision history for a regular
3327 expression in the specified files for the entire repository.
3327 expression in the specified files for the entire repository.
3328
3328
3329 By default, grep searches the repository files in the working
3329 By default, grep searches the repository files in the working
3330 directory and prints the files where it finds a match. To specify
3330 directory and prints the files where it finds a match. To specify
3331 historical revisions instead of the working directory, use the
3331 historical revisions instead of the working directory, use the
3332 --rev flag.
3332 --rev flag.
3333
3333
3334 To search instead historical revision differences that contains a
3334 To search instead historical revision differences that contains a
3335 change in match status ("-" for a match that becomes a non-match,
3335 change in match status ("-" for a match that becomes a non-match,
3336 or "+" for a non-match that becomes a match), use the --diff flag.
3336 or "+" for a non-match that becomes a match), use the --diff flag.
3337
3337
3338 PATTERN can be any Python (roughly Perl-compatible) regular
3338 PATTERN can be any Python (roughly Perl-compatible) regular
3339 expression.
3339 expression.
3340
3340
3341 If no FILEs are specified and the --rev flag isn't supplied, all
3341 If no FILEs are specified and the --rev flag isn't supplied, all
3342 files in the working directory are searched. When using the --rev
3342 files in the working directory are searched. When using the --rev
3343 flag and specifying FILEs, use the --follow argument to also
3343 flag and specifying FILEs, use the --follow argument to also
3344 follow the specified FILEs across renames and copies.
3344 follow the specified FILEs across renames and copies.
3345
3345
3346 .. container:: verbose
3346 .. container:: verbose
3347
3347
3348 Template:
3348 Template:
3349
3349
3350 The following keywords are supported in addition to the common template
3350 The following keywords are supported in addition to the common template
3351 keywords and functions. See also :hg:`help templates`.
3351 keywords and functions. See also :hg:`help templates`.
3352
3352
3353 :change: String. Character denoting insertion ``+`` or removal ``-``.
3353 :change: String. Character denoting insertion ``+`` or removal ``-``.
3354 Available if ``--diff`` is specified.
3354 Available if ``--diff`` is specified.
3355 :lineno: Integer. Line number of the match.
3355 :lineno: Integer. Line number of the match.
3356 :path: String. Repository-absolute path of the file.
3356 :path: String. Repository-absolute path of the file.
3357 :texts: List of text chunks.
3357 :texts: List of text chunks.
3358
3358
3359 And each entry of ``{texts}`` provides the following sub-keywords.
3359 And each entry of ``{texts}`` provides the following sub-keywords.
3360
3360
3361 :matched: Boolean. True if the chunk matches the specified pattern.
3361 :matched: Boolean. True if the chunk matches the specified pattern.
3362 :text: String. Chunk content.
3362 :text: String. Chunk content.
3363
3363
3364 See :hg:`help templates.operators` for the list expansion syntax.
3364 See :hg:`help templates.operators` for the list expansion syntax.
3365
3365
3366 Returns 0 if a match is found, 1 otherwise.
3366 Returns 0 if a match is found, 1 otherwise.
3367
3367
3368 """
3368 """
3369 opts = pycompat.byteskwargs(opts)
3369 opts = pycompat.byteskwargs(opts)
3370 diff = opts.get(b'all') or opts.get(b'diff')
3370 diff = opts.get(b'all') or opts.get(b'diff')
3371 if diff and opts.get(b'all_files'):
3371 if diff and opts.get(b'all_files'):
3372 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3372 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3373 if opts.get(b'all_files') is None and not diff:
3373 if opts.get(b'all_files') is None and not diff:
3374 opts[b'all_files'] = True
3374 opts[b'all_files'] = True
3375 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3375 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3376 all_files = opts.get(b'all_files')
3376 all_files = opts.get(b'all_files')
3377 if plaingrep:
3377 if plaingrep:
3378 opts[b'rev'] = [b'wdir()']
3378 opts[b'rev'] = [b'wdir()']
3379
3379
3380 reflags = re.M
3380 reflags = re.M
3381 if opts.get(b'ignore_case'):
3381 if opts.get(b'ignore_case'):
3382 reflags |= re.I
3382 reflags |= re.I
3383 try:
3383 try:
3384 regexp = util.re.compile(pattern, reflags)
3384 regexp = util.re.compile(pattern, reflags)
3385 except re.error as inst:
3385 except re.error as inst:
3386 ui.warn(
3386 ui.warn(
3387 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3387 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3388 )
3388 )
3389 return 1
3389 return 1
3390 sep, eol = b':', b'\n'
3390 sep, eol = b':', b'\n'
3391 if opts.get(b'print0'):
3391 if opts.get(b'print0'):
3392 sep = eol = b'\0'
3392 sep = eol = b'\0'
3393
3393
3394 getfile = util.lrucachefunc(repo.file)
3394 getfile = util.lrucachefunc(repo.file)
3395
3395
3396 def matchlines(body):
3396 def matchlines(body):
3397 begin = 0
3397 begin = 0
3398 linenum = 0
3398 linenum = 0
3399 while begin < len(body):
3399 while begin < len(body):
3400 match = regexp.search(body, begin)
3400 match = regexp.search(body, begin)
3401 if not match:
3401 if not match:
3402 break
3402 break
3403 mstart, mend = match.span()
3403 mstart, mend = match.span()
3404 linenum += body.count(b'\n', begin, mstart) + 1
3404 linenum += body.count(b'\n', begin, mstart) + 1
3405 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3405 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3406 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3406 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3407 lend = begin - 1
3407 lend = begin - 1
3408 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3408 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3409
3409
3410 class linestate(object):
3410 class linestate(object):
3411 def __init__(self, line, linenum, colstart, colend):
3411 def __init__(self, line, linenum, colstart, colend):
3412 self.line = line
3412 self.line = line
3413 self.linenum = linenum
3413 self.linenum = linenum
3414 self.colstart = colstart
3414 self.colstart = colstart
3415 self.colend = colend
3415 self.colend = colend
3416
3416
3417 def __hash__(self):
3417 def __hash__(self):
3418 return hash((self.linenum, self.line))
3418 return hash((self.linenum, self.line))
3419
3419
3420 def __eq__(self, other):
3420 def __eq__(self, other):
3421 return self.line == other.line
3421 return self.line == other.line
3422
3422
3423 def findpos(self):
3423 def findpos(self):
3424 """Iterate all (start, end) indices of matches"""
3424 """Iterate all (start, end) indices of matches"""
3425 yield self.colstart, self.colend
3425 yield self.colstart, self.colend
3426 p = self.colend
3426 p = self.colend
3427 while p < len(self.line):
3427 while p < len(self.line):
3428 m = regexp.search(self.line, p)
3428 m = regexp.search(self.line, p)
3429 if not m:
3429 if not m:
3430 break
3430 break
3431 yield m.span()
3431 yield m.span()
3432 p = m.end()
3432 p = m.end()
3433
3433
3434 matches = {}
3434 matches = {}
3435 copies = {}
3435 copies = {}
3436
3436
3437 def grepbody(fn, rev, body):
3437 def grepbody(fn, rev, body):
3438 matches[rev].setdefault(fn, [])
3438 matches[rev].setdefault(fn, [])
3439 m = matches[rev][fn]
3439 m = matches[rev][fn]
3440 if body is None:
3440 if body is None:
3441 return
3441 return
3442
3442
3443 for lnum, cstart, cend, line in matchlines(body):
3443 for lnum, cstart, cend, line in matchlines(body):
3444 s = linestate(line, lnum, cstart, cend)
3444 s = linestate(line, lnum, cstart, cend)
3445 m.append(s)
3445 m.append(s)
3446
3446
3447 def difflinestates(a, b):
3447 def difflinestates(a, b):
3448 sm = difflib.SequenceMatcher(None, a, b)
3448 sm = difflib.SequenceMatcher(None, a, b)
3449 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3449 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3450 if tag == 'insert':
3450 if tag == 'insert':
3451 for i in pycompat.xrange(blo, bhi):
3451 for i in pycompat.xrange(blo, bhi):
3452 yield (b'+', b[i])
3452 yield (b'+', b[i])
3453 elif tag == 'delete':
3453 elif tag == 'delete':
3454 for i in pycompat.xrange(alo, ahi):
3454 for i in pycompat.xrange(alo, ahi):
3455 yield (b'-', a[i])
3455 yield (b'-', a[i])
3456 elif tag == 'replace':
3456 elif tag == 'replace':
3457 for i in pycompat.xrange(alo, ahi):
3457 for i in pycompat.xrange(alo, ahi):
3458 yield (b'-', a[i])
3458 yield (b'-', a[i])
3459 for i in pycompat.xrange(blo, bhi):
3459 for i in pycompat.xrange(blo, bhi):
3460 yield (b'+', b[i])
3460 yield (b'+', b[i])
3461
3461
3462 uipathfn = scmutil.getuipathfn(repo)
3462 uipathfn = scmutil.getuipathfn(repo)
3463
3463
3464 def display(fm, fn, ctx, pstates, states):
3464 def display(fm, fn, ctx, pstates, states):
3465 rev = scmutil.intrev(ctx)
3465 rev = scmutil.intrev(ctx)
3466 if fm.isplain():
3466 if fm.isplain():
3467 formatuser = ui.shortuser
3467 formatuser = ui.shortuser
3468 else:
3468 else:
3469 formatuser = pycompat.bytestr
3469 formatuser = pycompat.bytestr
3470 if ui.quiet:
3470 if ui.quiet:
3471 datefmt = b'%Y-%m-%d'
3471 datefmt = b'%Y-%m-%d'
3472 else:
3472 else:
3473 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3473 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3474 found = False
3474 found = False
3475
3475
3476 @util.cachefunc
3476 @util.cachefunc
3477 def binary():
3477 def binary():
3478 flog = getfile(fn)
3478 flog = getfile(fn)
3479 try:
3479 try:
3480 return stringutil.binary(flog.read(ctx.filenode(fn)))
3480 return stringutil.binary(flog.read(ctx.filenode(fn)))
3481 except error.WdirUnsupported:
3481 except error.WdirUnsupported:
3482 return ctx[fn].isbinary()
3482 return ctx[fn].isbinary()
3483
3483
3484 fieldnamemap = {b'linenumber': b'lineno'}
3484 fieldnamemap = {b'linenumber': b'lineno'}
3485 if diff:
3485 if diff:
3486 iter = difflinestates(pstates, states)
3486 iter = difflinestates(pstates, states)
3487 else:
3487 else:
3488 iter = [(b'', l) for l in states]
3488 iter = [(b'', l) for l in states]
3489 for change, l in iter:
3489 for change, l in iter:
3490 fm.startitem()
3490 fm.startitem()
3491 fm.context(ctx=ctx)
3491 fm.context(ctx=ctx)
3492 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3492 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3493 fm.plain(uipathfn(fn), label=b'grep.filename')
3493 fm.plain(uipathfn(fn), label=b'grep.filename')
3494
3494
3495 cols = [
3495 cols = [
3496 (b'rev', b'%d', rev, not plaingrep, b''),
3496 (b'rev', b'%d', rev, not plaingrep, b''),
3497 (
3497 (
3498 b'linenumber',
3498 b'linenumber',
3499 b'%d',
3499 b'%d',
3500 l.linenum,
3500 l.linenum,
3501 opts.get(b'line_number'),
3501 opts.get(b'line_number'),
3502 b'',
3502 b'',
3503 ),
3503 ),
3504 ]
3504 ]
3505 if diff:
3505 if diff:
3506 cols.append(
3506 cols.append(
3507 (
3507 (
3508 b'change',
3508 b'change',
3509 b'%s',
3509 b'%s',
3510 change,
3510 change,
3511 True,
3511 True,
3512 b'grep.inserted '
3512 b'grep.inserted '
3513 if change == b'+'
3513 if change == b'+'
3514 else b'grep.deleted ',
3514 else b'grep.deleted ',
3515 )
3515 )
3516 )
3516 )
3517 cols.extend(
3517 cols.extend(
3518 [
3518 [
3519 (
3519 (
3520 b'user',
3520 b'user',
3521 b'%s',
3521 b'%s',
3522 formatuser(ctx.user()),
3522 formatuser(ctx.user()),
3523 opts.get(b'user'),
3523 opts.get(b'user'),
3524 b'',
3524 b'',
3525 ),
3525 ),
3526 (
3526 (
3527 b'date',
3527 b'date',
3528 b'%s',
3528 b'%s',
3529 fm.formatdate(ctx.date(), datefmt),
3529 fm.formatdate(ctx.date(), datefmt),
3530 opts.get(b'date'),
3530 opts.get(b'date'),
3531 b'',
3531 b'',
3532 ),
3532 ),
3533 ]
3533 ]
3534 )
3534 )
3535 for name, fmt, data, cond, extra_label in cols:
3535 for name, fmt, data, cond, extra_label in cols:
3536 if cond:
3536 if cond:
3537 fm.plain(sep, label=b'grep.sep')
3537 fm.plain(sep, label=b'grep.sep')
3538 field = fieldnamemap.get(name, name)
3538 field = fieldnamemap.get(name, name)
3539 label = extra_label + (b'grep.%s' % name)
3539 label = extra_label + (b'grep.%s' % name)
3540 fm.condwrite(cond, field, fmt, data, label=label)
3540 fm.condwrite(cond, field, fmt, data, label=label)
3541 if not opts.get(b'files_with_matches'):
3541 if not opts.get(b'files_with_matches'):
3542 fm.plain(sep, label=b'grep.sep')
3542 fm.plain(sep, label=b'grep.sep')
3543 if not opts.get(b'text') and binary():
3543 if not opts.get(b'text') and binary():
3544 fm.plain(_(b" Binary file matches"))
3544 fm.plain(_(b" Binary file matches"))
3545 else:
3545 else:
3546 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3546 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3547 fm.plain(eol)
3547 fm.plain(eol)
3548 found = True
3548 found = True
3549 if opts.get(b'files_with_matches'):
3549 if opts.get(b'files_with_matches'):
3550 break
3550 break
3551 return found
3551 return found
3552
3552
3553 def displaymatches(fm, l):
3553 def displaymatches(fm, l):
3554 p = 0
3554 p = 0
3555 for s, e in l.findpos():
3555 for s, e in l.findpos():
3556 if p < s:
3556 if p < s:
3557 fm.startitem()
3557 fm.startitem()
3558 fm.write(b'text', b'%s', l.line[p:s])
3558 fm.write(b'text', b'%s', l.line[p:s])
3559 fm.data(matched=False)
3559 fm.data(matched=False)
3560 fm.startitem()
3560 fm.startitem()
3561 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3561 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3562 fm.data(matched=True)
3562 fm.data(matched=True)
3563 p = e
3563 p = e
3564 if p < len(l.line):
3564 if p < len(l.line):
3565 fm.startitem()
3565 fm.startitem()
3566 fm.write(b'text', b'%s', l.line[p:])
3566 fm.write(b'text', b'%s', l.line[p:])
3567 fm.data(matched=False)
3567 fm.data(matched=False)
3568 fm.end()
3568 fm.end()
3569
3569
3570 skip = set()
3570 skip = set()
3571 revfiles = {}
3571 revfiles = {}
3572 match = scmutil.match(repo[None], pats, opts)
3572 match = scmutil.match(repo[None], pats, opts)
3573 found = False
3573 found = False
3574 follow = opts.get(b'follow')
3574 follow = opts.get(b'follow')
3575
3575
3576 getrenamed = scmutil.getrenamedfn(repo)
3576 getrenamed = scmutil.getrenamedfn(repo)
3577
3577
3578 def get_file_content(filename, filelog, filenode, context, revision):
3578 def get_file_content(filename, filelog, filenode, context, revision):
3579 try:
3579 try:
3580 content = filelog.read(filenode)
3580 content = filelog.read(filenode)
3581 except error.WdirUnsupported:
3581 except error.WdirUnsupported:
3582 content = context[filename].data()
3582 content = context[filename].data()
3583 except error.CensoredNodeError:
3583 except error.CensoredNodeError:
3584 content = None
3584 content = None
3585 ui.warn(
3585 ui.warn(
3586 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3586 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3587 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3587 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3588 )
3588 )
3589 return content
3589 return content
3590
3590
3591 def prep(ctx, fns):
3591 def prep(ctx, fns):
3592 rev = ctx.rev()
3592 rev = ctx.rev()
3593 pctx = ctx.p1()
3593 pctx = ctx.p1()
3594 parent = pctx.rev()
3594 parent = pctx.rev()
3595 matches.setdefault(rev, {})
3595 matches.setdefault(rev, {})
3596 matches.setdefault(parent, {})
3596 matches.setdefault(parent, {})
3597 files = revfiles.setdefault(rev, [])
3597 files = revfiles.setdefault(rev, [])
3598 for fn in fns:
3598 for fn in fns:
3599 flog = getfile(fn)
3599 flog = getfile(fn)
3600 try:
3600 try:
3601 fnode = ctx.filenode(fn)
3601 fnode = ctx.filenode(fn)
3602 except error.LookupError:
3602 except error.LookupError:
3603 continue
3603 continue
3604
3604
3605 copy = None
3605 copy = None
3606 if follow:
3606 if follow:
3607 copy = getrenamed(fn, rev)
3607 copy = getrenamed(fn, rev)
3608 if copy:
3608 if copy:
3609 copies.setdefault(rev, {})[fn] = copy
3609 copies.setdefault(rev, {})[fn] = copy
3610 if fn in skip:
3610 if fn in skip:
3611 skip.add(copy)
3611 skip.add(copy)
3612 if fn in skip:
3612 if fn in skip:
3613 continue
3613 continue
3614 files.append(fn)
3614 files.append(fn)
3615
3615
3616 if fn not in matches[rev]:
3616 if fn not in matches[rev]:
3617 content = get_file_content(fn, flog, fnode, ctx, rev)
3617 content = get_file_content(fn, flog, fnode, ctx, rev)
3618 grepbody(fn, rev, content)
3618 grepbody(fn, rev, content)
3619
3619
3620 pfn = copy or fn
3620 pfn = copy or fn
3621 if pfn not in matches[parent]:
3621 if pfn not in matches[parent]:
3622 try:
3622 try:
3623 pfnode = pctx.filenode(pfn)
3623 pfnode = pctx.filenode(pfn)
3624 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3624 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3625 grepbody(pfn, parent, pcontent)
3625 grepbody(pfn, parent, pcontent)
3626 except error.LookupError:
3626 except error.LookupError:
3627 pass
3627 pass
3628
3628
3629 ui.pager(b'grep')
3629 ui.pager(b'grep')
3630 fm = ui.formatter(b'grep', opts)
3630 fm = ui.formatter(b'grep', opts)
3631 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3631 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3632 rev = ctx.rev()
3632 rev = ctx.rev()
3633 parent = ctx.p1().rev()
3633 parent = ctx.p1().rev()
3634 for fn in sorted(revfiles.get(rev, [])):
3634 for fn in sorted(revfiles.get(rev, [])):
3635 states = matches[rev][fn]
3635 states = matches[rev][fn]
3636 copy = copies.get(rev, {}).get(fn)
3636 copy = copies.get(rev, {}).get(fn)
3637 if fn in skip:
3637 if fn in skip:
3638 if copy:
3638 if copy:
3639 skip.add(copy)
3639 skip.add(copy)
3640 continue
3640 continue
3641 pstates = matches.get(parent, {}).get(copy or fn, [])
3641 pstates = matches.get(parent, {}).get(copy or fn, [])
3642 if pstates or states:
3642 if pstates or states:
3643 r = display(fm, fn, ctx, pstates, states)
3643 r = display(fm, fn, ctx, pstates, states)
3644 found = found or r
3644 found = found or r
3645 if r and not diff and not all_files:
3645 if r and not diff and not all_files:
3646 skip.add(fn)
3646 skip.add(fn)
3647 if copy:
3647 if copy:
3648 skip.add(copy)
3648 skip.add(copy)
3649 del revfiles[rev]
3649 del revfiles[rev]
3650 # We will keep the matches dict for the duration of the window
3650 # We will keep the matches dict for the duration of the window
3651 # clear the matches dict once the window is over
3651 # clear the matches dict once the window is over
3652 if not revfiles:
3652 if not revfiles:
3653 matches.clear()
3653 matches.clear()
3654 fm.end()
3654 fm.end()
3655
3655
3656 return not found
3656 return not found
3657
3657
3658
3658
3659 @command(
3659 @command(
3660 b'heads',
3660 b'heads',
3661 [
3661 [
3662 (
3662 (
3663 b'r',
3663 b'r',
3664 b'rev',
3664 b'rev',
3665 b'',
3665 b'',
3666 _(b'show only heads which are descendants of STARTREV'),
3666 _(b'show only heads which are descendants of STARTREV'),
3667 _(b'STARTREV'),
3667 _(b'STARTREV'),
3668 ),
3668 ),
3669 (b't', b'topo', False, _(b'show topological heads only')),
3669 (b't', b'topo', False, _(b'show topological heads only')),
3670 (
3670 (
3671 b'a',
3671 b'a',
3672 b'active',
3672 b'active',
3673 False,
3673 False,
3674 _(b'show active branchheads only (DEPRECATED)'),
3674 _(b'show active branchheads only (DEPRECATED)'),
3675 ),
3675 ),
3676 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3676 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3677 ]
3677 ]
3678 + templateopts,
3678 + templateopts,
3679 _(b'[-ct] [-r STARTREV] [REV]...'),
3679 _(b'[-ct] [-r STARTREV] [REV]...'),
3680 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3680 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3681 intents={INTENT_READONLY},
3681 intents={INTENT_READONLY},
3682 )
3682 )
3683 def heads(ui, repo, *branchrevs, **opts):
3683 def heads(ui, repo, *branchrevs, **opts):
3684 """show branch heads
3684 """show branch heads
3685
3685
3686 With no arguments, show all open branch heads in the repository.
3686 With no arguments, show all open branch heads in the repository.
3687 Branch heads are changesets that have no descendants on the
3687 Branch heads are changesets that have no descendants on the
3688 same branch. They are where development generally takes place and
3688 same branch. They are where development generally takes place and
3689 are the usual targets for update and merge operations.
3689 are the usual targets for update and merge operations.
3690
3690
3691 If one or more REVs are given, only open branch heads on the
3691 If one or more REVs are given, only open branch heads on the
3692 branches associated with the specified changesets are shown. This
3692 branches associated with the specified changesets are shown. This
3693 means that you can use :hg:`heads .` to see the heads on the
3693 means that you can use :hg:`heads .` to see the heads on the
3694 currently checked-out branch.
3694 currently checked-out branch.
3695
3695
3696 If -c/--closed is specified, also show branch heads marked closed
3696 If -c/--closed is specified, also show branch heads marked closed
3697 (see :hg:`commit --close-branch`).
3697 (see :hg:`commit --close-branch`).
3698
3698
3699 If STARTREV is specified, only those heads that are descendants of
3699 If STARTREV is specified, only those heads that are descendants of
3700 STARTREV will be displayed.
3700 STARTREV will be displayed.
3701
3701
3702 If -t/--topo is specified, named branch mechanics will be ignored and only
3702 If -t/--topo is specified, named branch mechanics will be ignored and only
3703 topological heads (changesets with no children) will be shown.
3703 topological heads (changesets with no children) will be shown.
3704
3704
3705 Returns 0 if matching heads are found, 1 if not.
3705 Returns 0 if matching heads are found, 1 if not.
3706 """
3706 """
3707
3707
3708 opts = pycompat.byteskwargs(opts)
3708 opts = pycompat.byteskwargs(opts)
3709 start = None
3709 start = None
3710 rev = opts.get(b'rev')
3710 rev = opts.get(b'rev')
3711 if rev:
3711 if rev:
3712 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3712 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3713 start = scmutil.revsingle(repo, rev, None).node()
3713 start = scmutil.revsingle(repo, rev, None).node()
3714
3714
3715 if opts.get(b'topo'):
3715 if opts.get(b'topo'):
3716 heads = [repo[h] for h in repo.heads(start)]
3716 heads = [repo[h] for h in repo.heads(start)]
3717 else:
3717 else:
3718 heads = []
3718 heads = []
3719 for branch in repo.branchmap():
3719 for branch in repo.branchmap():
3720 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3720 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3721 heads = [repo[h] for h in heads]
3721 heads = [repo[h] for h in heads]
3722
3722
3723 if branchrevs:
3723 if branchrevs:
3724 branches = {
3724 branches = {
3725 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3725 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3726 }
3726 }
3727 heads = [h for h in heads if h.branch() in branches]
3727 heads = [h for h in heads if h.branch() in branches]
3728
3728
3729 if opts.get(b'active') and branchrevs:
3729 if opts.get(b'active') and branchrevs:
3730 dagheads = repo.heads(start)
3730 dagheads = repo.heads(start)
3731 heads = [h for h in heads if h.node() in dagheads]
3731 heads = [h for h in heads if h.node() in dagheads]
3732
3732
3733 if branchrevs:
3733 if branchrevs:
3734 haveheads = {h.branch() for h in heads}
3734 haveheads = {h.branch() for h in heads}
3735 if branches - haveheads:
3735 if branches - haveheads:
3736 headless = b', '.join(b for b in branches - haveheads)
3736 headless = b', '.join(b for b in branches - haveheads)
3737 msg = _(b'no open branch heads found on branches %s')
3737 msg = _(b'no open branch heads found on branches %s')
3738 if opts.get(b'rev'):
3738 if opts.get(b'rev'):
3739 msg += _(b' (started at %s)') % opts[b'rev']
3739 msg += _(b' (started at %s)') % opts[b'rev']
3740 ui.warn((msg + b'\n') % headless)
3740 ui.warn((msg + b'\n') % headless)
3741
3741
3742 if not heads:
3742 if not heads:
3743 return 1
3743 return 1
3744
3744
3745 ui.pager(b'heads')
3745 ui.pager(b'heads')
3746 heads = sorted(heads, key=lambda x: -(x.rev()))
3746 heads = sorted(heads, key=lambda x: -(x.rev()))
3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3748 for ctx in heads:
3748 for ctx in heads:
3749 displayer.show(ctx)
3749 displayer.show(ctx)
3750 displayer.close()
3750 displayer.close()
3751
3751
3752
3752
3753 @command(
3753 @command(
3754 b'help',
3754 b'help',
3755 [
3755 [
3756 (b'e', b'extension', None, _(b'show only help for extensions')),
3756 (b'e', b'extension', None, _(b'show only help for extensions')),
3757 (b'c', b'command', None, _(b'show only help for commands')),
3757 (b'c', b'command', None, _(b'show only help for commands')),
3758 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3758 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3759 (
3759 (
3760 b's',
3760 b's',
3761 b'system',
3761 b'system',
3762 [],
3762 [],
3763 _(b'show help for specific platform(s)'),
3763 _(b'show help for specific platform(s)'),
3764 _(b'PLATFORM'),
3764 _(b'PLATFORM'),
3765 ),
3765 ),
3766 ],
3766 ],
3767 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3767 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3768 helpcategory=command.CATEGORY_HELP,
3768 helpcategory=command.CATEGORY_HELP,
3769 norepo=True,
3769 norepo=True,
3770 intents={INTENT_READONLY},
3770 intents={INTENT_READONLY},
3771 )
3771 )
3772 def help_(ui, name=None, **opts):
3772 def help_(ui, name=None, **opts):
3773 """show help for a given topic or a help overview
3773 """show help for a given topic or a help overview
3774
3774
3775 With no arguments, print a list of commands with short help messages.
3775 With no arguments, print a list of commands with short help messages.
3776
3776
3777 Given a topic, extension, or command name, print help for that
3777 Given a topic, extension, or command name, print help for that
3778 topic.
3778 topic.
3779
3779
3780 Returns 0 if successful.
3780 Returns 0 if successful.
3781 """
3781 """
3782
3782
3783 keep = opts.get('system') or []
3783 keep = opts.get('system') or []
3784 if len(keep) == 0:
3784 if len(keep) == 0:
3785 if pycompat.sysplatform.startswith(b'win'):
3785 if pycompat.sysplatform.startswith(b'win'):
3786 keep.append(b'windows')
3786 keep.append(b'windows')
3787 elif pycompat.sysplatform == b'OpenVMS':
3787 elif pycompat.sysplatform == b'OpenVMS':
3788 keep.append(b'vms')
3788 keep.append(b'vms')
3789 elif pycompat.sysplatform == b'plan9':
3789 elif pycompat.sysplatform == b'plan9':
3790 keep.append(b'plan9')
3790 keep.append(b'plan9')
3791 else:
3791 else:
3792 keep.append(b'unix')
3792 keep.append(b'unix')
3793 keep.append(pycompat.sysplatform.lower())
3793 keep.append(pycompat.sysplatform.lower())
3794 if ui.verbose:
3794 if ui.verbose:
3795 keep.append(b'verbose')
3795 keep.append(b'verbose')
3796
3796
3797 commands = sys.modules[__name__]
3797 commands = sys.modules[__name__]
3798 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3798 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3799 ui.pager(b'help')
3799 ui.pager(b'help')
3800 ui.write(formatted)
3800 ui.write(formatted)
3801
3801
3802
3802
3803 @command(
3803 @command(
3804 b'identify|id',
3804 b'identify|id',
3805 [
3805 [
3806 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3806 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3807 (b'n', b'num', None, _(b'show local revision number')),
3807 (b'n', b'num', None, _(b'show local revision number')),
3808 (b'i', b'id', None, _(b'show global revision id')),
3808 (b'i', b'id', None, _(b'show global revision id')),
3809 (b'b', b'branch', None, _(b'show branch')),
3809 (b'b', b'branch', None, _(b'show branch')),
3810 (b't', b'tags', None, _(b'show tags')),
3810 (b't', b'tags', None, _(b'show tags')),
3811 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3811 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3812 ]
3812 ]
3813 + remoteopts
3813 + remoteopts
3814 + formatteropts,
3814 + formatteropts,
3815 _(b'[-nibtB] [-r REV] [SOURCE]'),
3815 _(b'[-nibtB] [-r REV] [SOURCE]'),
3816 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3816 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3817 optionalrepo=True,
3817 optionalrepo=True,
3818 intents={INTENT_READONLY},
3818 intents={INTENT_READONLY},
3819 )
3819 )
3820 def identify(
3820 def identify(
3821 ui,
3821 ui,
3822 repo,
3822 repo,
3823 source=None,
3823 source=None,
3824 rev=None,
3824 rev=None,
3825 num=None,
3825 num=None,
3826 id=None,
3826 id=None,
3827 branch=None,
3827 branch=None,
3828 tags=None,
3828 tags=None,
3829 bookmarks=None,
3829 bookmarks=None,
3830 **opts
3830 **opts
3831 ):
3831 ):
3832 """identify the working directory or specified revision
3832 """identify the working directory or specified revision
3833
3833
3834 Print a summary identifying the repository state at REV using one or
3834 Print a summary identifying the repository state at REV using one or
3835 two parent hash identifiers, followed by a "+" if the working
3835 two parent hash identifiers, followed by a "+" if the working
3836 directory has uncommitted changes, the branch name (if not default),
3836 directory has uncommitted changes, the branch name (if not default),
3837 a list of tags, and a list of bookmarks.
3837 a list of tags, and a list of bookmarks.
3838
3838
3839 When REV is not given, print a summary of the current state of the
3839 When REV is not given, print a summary of the current state of the
3840 repository including the working directory. Specify -r. to get information
3840 repository including the working directory. Specify -r. to get information
3841 of the working directory parent without scanning uncommitted changes.
3841 of the working directory parent without scanning uncommitted changes.
3842
3842
3843 Specifying a path to a repository root or Mercurial bundle will
3843 Specifying a path to a repository root or Mercurial bundle will
3844 cause lookup to operate on that repository/bundle.
3844 cause lookup to operate on that repository/bundle.
3845
3845
3846 .. container:: verbose
3846 .. container:: verbose
3847
3847
3848 Template:
3848 Template:
3849
3849
3850 The following keywords are supported in addition to the common template
3850 The following keywords are supported in addition to the common template
3851 keywords and functions. See also :hg:`help templates`.
3851 keywords and functions. See also :hg:`help templates`.
3852
3852
3853 :dirty: String. Character ``+`` denoting if the working directory has
3853 :dirty: String. Character ``+`` denoting if the working directory has
3854 uncommitted changes.
3854 uncommitted changes.
3855 :id: String. One or two nodes, optionally followed by ``+``.
3855 :id: String. One or two nodes, optionally followed by ``+``.
3856 :parents: List of strings. Parent nodes of the changeset.
3856 :parents: List of strings. Parent nodes of the changeset.
3857
3857
3858 Examples:
3858 Examples:
3859
3859
3860 - generate a build identifier for the working directory::
3860 - generate a build identifier for the working directory::
3861
3861
3862 hg id --id > build-id.dat
3862 hg id --id > build-id.dat
3863
3863
3864 - find the revision corresponding to a tag::
3864 - find the revision corresponding to a tag::
3865
3865
3866 hg id -n -r 1.3
3866 hg id -n -r 1.3
3867
3867
3868 - check the most recent revision of a remote repository::
3868 - check the most recent revision of a remote repository::
3869
3869
3870 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3870 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3871
3871
3872 See :hg:`log` for generating more information about specific revisions,
3872 See :hg:`log` for generating more information about specific revisions,
3873 including full hash identifiers.
3873 including full hash identifiers.
3874
3874
3875 Returns 0 if successful.
3875 Returns 0 if successful.
3876 """
3876 """
3877
3877
3878 opts = pycompat.byteskwargs(opts)
3878 opts = pycompat.byteskwargs(opts)
3879 if not repo and not source:
3879 if not repo and not source:
3880 raise error.Abort(
3880 raise error.Abort(
3881 _(b"there is no Mercurial repository here (.hg not found)")
3881 _(b"there is no Mercurial repository here (.hg not found)")
3882 )
3882 )
3883
3883
3884 default = not (num or id or branch or tags or bookmarks)
3884 default = not (num or id or branch or tags or bookmarks)
3885 output = []
3885 output = []
3886 revs = []
3886 revs = []
3887
3887
3888 if source:
3888 if source:
3889 source, branches = hg.parseurl(ui.expandpath(source))
3889 source, branches = hg.parseurl(ui.expandpath(source))
3890 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3890 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3891 repo = peer.local()
3891 repo = peer.local()
3892 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3892 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3893
3893
3894 fm = ui.formatter(b'identify', opts)
3894 fm = ui.formatter(b'identify', opts)
3895 fm.startitem()
3895 fm.startitem()
3896
3896
3897 if not repo:
3897 if not repo:
3898 if num or branch or tags:
3898 if num or branch or tags:
3899 raise error.Abort(
3899 raise error.Abort(
3900 _(b"can't query remote revision number, branch, or tags")
3900 _(b"can't query remote revision number, branch, or tags")
3901 )
3901 )
3902 if not rev and revs:
3902 if not rev and revs:
3903 rev = revs[0]
3903 rev = revs[0]
3904 if not rev:
3904 if not rev:
3905 rev = b"tip"
3905 rev = b"tip"
3906
3906
3907 remoterev = peer.lookup(rev)
3907 remoterev = peer.lookup(rev)
3908 hexrev = fm.hexfunc(remoterev)
3908 hexrev = fm.hexfunc(remoterev)
3909 if default or id:
3909 if default or id:
3910 output = [hexrev]
3910 output = [hexrev]
3911 fm.data(id=hexrev)
3911 fm.data(id=hexrev)
3912
3912
3913 @util.cachefunc
3913 @util.cachefunc
3914 def getbms():
3914 def getbms():
3915 bms = []
3915 bms = []
3916
3916
3917 if b'bookmarks' in peer.listkeys(b'namespaces'):
3917 if b'bookmarks' in peer.listkeys(b'namespaces'):
3918 hexremoterev = hex(remoterev)
3918 hexremoterev = hex(remoterev)
3919 bms = [
3919 bms = [
3920 bm
3920 bm
3921 for bm, bmr in pycompat.iteritems(
3921 for bm, bmr in pycompat.iteritems(
3922 peer.listkeys(b'bookmarks')
3922 peer.listkeys(b'bookmarks')
3923 )
3923 )
3924 if bmr == hexremoterev
3924 if bmr == hexremoterev
3925 ]
3925 ]
3926
3926
3927 return sorted(bms)
3927 return sorted(bms)
3928
3928
3929 if fm.isplain():
3929 if fm.isplain():
3930 if bookmarks:
3930 if bookmarks:
3931 output.extend(getbms())
3931 output.extend(getbms())
3932 elif default and not ui.quiet:
3932 elif default and not ui.quiet:
3933 # multiple bookmarks for a single parent separated by '/'
3933 # multiple bookmarks for a single parent separated by '/'
3934 bm = b'/'.join(getbms())
3934 bm = b'/'.join(getbms())
3935 if bm:
3935 if bm:
3936 output.append(bm)
3936 output.append(bm)
3937 else:
3937 else:
3938 fm.data(node=hex(remoterev))
3938 fm.data(node=hex(remoterev))
3939 if bookmarks or b'bookmarks' in fm.datahint():
3939 if bookmarks or b'bookmarks' in fm.datahint():
3940 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3940 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3941 else:
3941 else:
3942 if rev:
3942 if rev:
3943 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3943 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3944 ctx = scmutil.revsingle(repo, rev, None)
3944 ctx = scmutil.revsingle(repo, rev, None)
3945
3945
3946 if ctx.rev() is None:
3946 if ctx.rev() is None:
3947 ctx = repo[None]
3947 ctx = repo[None]
3948 parents = ctx.parents()
3948 parents = ctx.parents()
3949 taglist = []
3949 taglist = []
3950 for p in parents:
3950 for p in parents:
3951 taglist.extend(p.tags())
3951 taglist.extend(p.tags())
3952
3952
3953 dirty = b""
3953 dirty = b""
3954 if ctx.dirty(missing=True, merge=False, branch=False):
3954 if ctx.dirty(missing=True, merge=False, branch=False):
3955 dirty = b'+'
3955 dirty = b'+'
3956 fm.data(dirty=dirty)
3956 fm.data(dirty=dirty)
3957
3957
3958 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3958 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3959 if default or id:
3959 if default or id:
3960 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3960 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3961 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3961 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3962
3962
3963 if num:
3963 if num:
3964 numoutput = [b"%d" % p.rev() for p in parents]
3964 numoutput = [b"%d" % p.rev() for p in parents]
3965 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3965 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3966
3966
3967 fm.data(
3967 fm.data(
3968 parents=fm.formatlist(
3968 parents=fm.formatlist(
3969 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3969 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3970 )
3970 )
3971 )
3971 )
3972 else:
3972 else:
3973 hexoutput = fm.hexfunc(ctx.node())
3973 hexoutput = fm.hexfunc(ctx.node())
3974 if default or id:
3974 if default or id:
3975 output = [hexoutput]
3975 output = [hexoutput]
3976 fm.data(id=hexoutput)
3976 fm.data(id=hexoutput)
3977
3977
3978 if num:
3978 if num:
3979 output.append(pycompat.bytestr(ctx.rev()))
3979 output.append(pycompat.bytestr(ctx.rev()))
3980 taglist = ctx.tags()
3980 taglist = ctx.tags()
3981
3981
3982 if default and not ui.quiet:
3982 if default and not ui.quiet:
3983 b = ctx.branch()
3983 b = ctx.branch()
3984 if b != b'default':
3984 if b != b'default':
3985 output.append(b"(%s)" % b)
3985 output.append(b"(%s)" % b)
3986
3986
3987 # multiple tags for a single parent separated by '/'
3987 # multiple tags for a single parent separated by '/'
3988 t = b'/'.join(taglist)
3988 t = b'/'.join(taglist)
3989 if t:
3989 if t:
3990 output.append(t)
3990 output.append(t)
3991
3991
3992 # multiple bookmarks for a single parent separated by '/'
3992 # multiple bookmarks for a single parent separated by '/'
3993 bm = b'/'.join(ctx.bookmarks())
3993 bm = b'/'.join(ctx.bookmarks())
3994 if bm:
3994 if bm:
3995 output.append(bm)
3995 output.append(bm)
3996 else:
3996 else:
3997 if branch:
3997 if branch:
3998 output.append(ctx.branch())
3998 output.append(ctx.branch())
3999
3999
4000 if tags:
4000 if tags:
4001 output.extend(taglist)
4001 output.extend(taglist)
4002
4002
4003 if bookmarks:
4003 if bookmarks:
4004 output.extend(ctx.bookmarks())
4004 output.extend(ctx.bookmarks())
4005
4005
4006 fm.data(node=ctx.hex())
4006 fm.data(node=ctx.hex())
4007 fm.data(branch=ctx.branch())
4007 fm.data(branch=ctx.branch())
4008 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4008 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4009 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4009 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4010 fm.context(ctx=ctx)
4010 fm.context(ctx=ctx)
4011
4011
4012 fm.plain(b"%s\n" % b' '.join(output))
4012 fm.plain(b"%s\n" % b' '.join(output))
4013 fm.end()
4013 fm.end()
4014
4014
4015
4015
4016 @command(
4016 @command(
4017 b'import|patch',
4017 b'import|patch',
4018 [
4018 [
4019 (
4019 (
4020 b'p',
4020 b'p',
4021 b'strip',
4021 b'strip',
4022 1,
4022 1,
4023 _(
4023 _(
4024 b'directory strip option for patch. This has the same '
4024 b'directory strip option for patch. This has the same '
4025 b'meaning as the corresponding patch option'
4025 b'meaning as the corresponding patch option'
4026 ),
4026 ),
4027 _(b'NUM'),
4027 _(b'NUM'),
4028 ),
4028 ),
4029 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4029 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4030 (b'', b'secret', None, _(b'use the secret phase for committing')),
4030 (b'', b'secret', None, _(b'use the secret phase for committing')),
4031 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4031 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4032 (
4032 (
4033 b'f',
4033 b'f',
4034 b'force',
4034 b'force',
4035 None,
4035 None,
4036 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4036 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4037 ),
4037 ),
4038 (
4038 (
4039 b'',
4039 b'',
4040 b'no-commit',
4040 b'no-commit',
4041 None,
4041 None,
4042 _(b"don't commit, just update the working directory"),
4042 _(b"don't commit, just update the working directory"),
4043 ),
4043 ),
4044 (
4044 (
4045 b'',
4045 b'',
4046 b'bypass',
4046 b'bypass',
4047 None,
4047 None,
4048 _(b"apply patch without touching the working directory"),
4048 _(b"apply patch without touching the working directory"),
4049 ),
4049 ),
4050 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4050 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4051 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4051 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4052 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4052 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4053 (
4053 (
4054 b'',
4054 b'',
4055 b'import-branch',
4055 b'import-branch',
4056 None,
4056 None,
4057 _(b'use any branch information in patch (implied by --exact)'),
4057 _(b'use any branch information in patch (implied by --exact)'),
4058 ),
4058 ),
4059 ]
4059 ]
4060 + commitopts
4060 + commitopts
4061 + commitopts2
4061 + commitopts2
4062 + similarityopts,
4062 + similarityopts,
4063 _(b'[OPTION]... PATCH...'),
4063 _(b'[OPTION]... PATCH...'),
4064 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4064 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4065 )
4065 )
4066 def import_(ui, repo, patch1=None, *patches, **opts):
4066 def import_(ui, repo, patch1=None, *patches, **opts):
4067 """import an ordered set of patches
4067 """import an ordered set of patches
4068
4068
4069 Import a list of patches and commit them individually (unless
4069 Import a list of patches and commit them individually (unless
4070 --no-commit is specified).
4070 --no-commit is specified).
4071
4071
4072 To read a patch from standard input (stdin), use "-" as the patch
4072 To read a patch from standard input (stdin), use "-" as the patch
4073 name. If a URL is specified, the patch will be downloaded from
4073 name. If a URL is specified, the patch will be downloaded from
4074 there.
4074 there.
4075
4075
4076 Import first applies changes to the working directory (unless
4076 Import first applies changes to the working directory (unless
4077 --bypass is specified), import will abort if there are outstanding
4077 --bypass is specified), import will abort if there are outstanding
4078 changes.
4078 changes.
4079
4079
4080 Use --bypass to apply and commit patches directly to the
4080 Use --bypass to apply and commit patches directly to the
4081 repository, without affecting the working directory. Without
4081 repository, without affecting the working directory. Without
4082 --exact, patches will be applied on top of the working directory
4082 --exact, patches will be applied on top of the working directory
4083 parent revision.
4083 parent revision.
4084
4084
4085 You can import a patch straight from a mail message. Even patches
4085 You can import a patch straight from a mail message. Even patches
4086 as attachments work (to use the body part, it must have type
4086 as attachments work (to use the body part, it must have type
4087 text/plain or text/x-patch). From and Subject headers of email
4087 text/plain or text/x-patch). From and Subject headers of email
4088 message are used as default committer and commit message. All
4088 message are used as default committer and commit message. All
4089 text/plain body parts before first diff are added to the commit
4089 text/plain body parts before first diff are added to the commit
4090 message.
4090 message.
4091
4091
4092 If the imported patch was generated by :hg:`export`, user and
4092 If the imported patch was generated by :hg:`export`, user and
4093 description from patch override values from message headers and
4093 description from patch override values from message headers and
4094 body. Values given on command line with -m/--message and -u/--user
4094 body. Values given on command line with -m/--message and -u/--user
4095 override these.
4095 override these.
4096
4096
4097 If --exact is specified, import will set the working directory to
4097 If --exact is specified, import will set the working directory to
4098 the parent of each patch before applying it, and will abort if the
4098 the parent of each patch before applying it, and will abort if the
4099 resulting changeset has a different ID than the one recorded in
4099 resulting changeset has a different ID than the one recorded in
4100 the patch. This will guard against various ways that portable
4100 the patch. This will guard against various ways that portable
4101 patch formats and mail systems might fail to transfer Mercurial
4101 patch formats and mail systems might fail to transfer Mercurial
4102 data or metadata. See :hg:`bundle` for lossless transmission.
4102 data or metadata. See :hg:`bundle` for lossless transmission.
4103
4103
4104 Use --partial to ensure a changeset will be created from the patch
4104 Use --partial to ensure a changeset will be created from the patch
4105 even if some hunks fail to apply. Hunks that fail to apply will be
4105 even if some hunks fail to apply. Hunks that fail to apply will be
4106 written to a <target-file>.rej file. Conflicts can then be resolved
4106 written to a <target-file>.rej file. Conflicts can then be resolved
4107 by hand before :hg:`commit --amend` is run to update the created
4107 by hand before :hg:`commit --amend` is run to update the created
4108 changeset. This flag exists to let people import patches that
4108 changeset. This flag exists to let people import patches that
4109 partially apply without losing the associated metadata (author,
4109 partially apply without losing the associated metadata (author,
4110 date, description, ...).
4110 date, description, ...).
4111
4111
4112 .. note::
4112 .. note::
4113
4113
4114 When no hunks apply cleanly, :hg:`import --partial` will create
4114 When no hunks apply cleanly, :hg:`import --partial` will create
4115 an empty changeset, importing only the patch metadata.
4115 an empty changeset, importing only the patch metadata.
4116
4116
4117 With -s/--similarity, hg will attempt to discover renames and
4117 With -s/--similarity, hg will attempt to discover renames and
4118 copies in the patch in the same way as :hg:`addremove`.
4118 copies in the patch in the same way as :hg:`addremove`.
4119
4119
4120 It is possible to use external patch programs to perform the patch
4120 It is possible to use external patch programs to perform the patch
4121 by setting the ``ui.patch`` configuration option. For the default
4121 by setting the ``ui.patch`` configuration option. For the default
4122 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4122 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4123 See :hg:`help config` for more information about configuration
4123 See :hg:`help config` for more information about configuration
4124 files and how to use these options.
4124 files and how to use these options.
4125
4125
4126 See :hg:`help dates` for a list of formats valid for -d/--date.
4126 See :hg:`help dates` for a list of formats valid for -d/--date.
4127
4127
4128 .. container:: verbose
4128 .. container:: verbose
4129
4129
4130 Examples:
4130 Examples:
4131
4131
4132 - import a traditional patch from a website and detect renames::
4132 - import a traditional patch from a website and detect renames::
4133
4133
4134 hg import -s 80 http://example.com/bugfix.patch
4134 hg import -s 80 http://example.com/bugfix.patch
4135
4135
4136 - import a changeset from an hgweb server::
4136 - import a changeset from an hgweb server::
4137
4137
4138 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4138 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4139
4139
4140 - import all the patches in an Unix-style mbox::
4140 - import all the patches in an Unix-style mbox::
4141
4141
4142 hg import incoming-patches.mbox
4142 hg import incoming-patches.mbox
4143
4143
4144 - import patches from stdin::
4144 - import patches from stdin::
4145
4145
4146 hg import -
4146 hg import -
4147
4147
4148 - attempt to exactly restore an exported changeset (not always
4148 - attempt to exactly restore an exported changeset (not always
4149 possible)::
4149 possible)::
4150
4150
4151 hg import --exact proposed-fix.patch
4151 hg import --exact proposed-fix.patch
4152
4152
4153 - use an external tool to apply a patch which is too fuzzy for
4153 - use an external tool to apply a patch which is too fuzzy for
4154 the default internal tool.
4154 the default internal tool.
4155
4155
4156 hg import --config ui.patch="patch --merge" fuzzy.patch
4156 hg import --config ui.patch="patch --merge" fuzzy.patch
4157
4157
4158 - change the default fuzzing from 2 to a less strict 7
4158 - change the default fuzzing from 2 to a less strict 7
4159
4159
4160 hg import --config ui.fuzz=7 fuzz.patch
4160 hg import --config ui.fuzz=7 fuzz.patch
4161
4161
4162 Returns 0 on success, 1 on partial success (see --partial).
4162 Returns 0 on success, 1 on partial success (see --partial).
4163 """
4163 """
4164
4164
4165 opts = pycompat.byteskwargs(opts)
4165 opts = pycompat.byteskwargs(opts)
4166 if not patch1:
4166 if not patch1:
4167 raise error.Abort(_(b'need at least one patch to import'))
4167 raise error.Abort(_(b'need at least one patch to import'))
4168
4168
4169 patches = (patch1,) + patches
4169 patches = (patch1,) + patches
4170
4170
4171 date = opts.get(b'date')
4171 date = opts.get(b'date')
4172 if date:
4172 if date:
4173 opts[b'date'] = dateutil.parsedate(date)
4173 opts[b'date'] = dateutil.parsedate(date)
4174
4174
4175 exact = opts.get(b'exact')
4175 exact = opts.get(b'exact')
4176 update = not opts.get(b'bypass')
4176 update = not opts.get(b'bypass')
4177 if not update and opts.get(b'no_commit'):
4177 if not update and opts.get(b'no_commit'):
4178 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4178 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4179 if opts.get(b'secret') and opts.get(b'no_commit'):
4179 if opts.get(b'secret') and opts.get(b'no_commit'):
4180 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4180 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4181 try:
4181 try:
4182 sim = float(opts.get(b'similarity') or 0)
4182 sim = float(opts.get(b'similarity') or 0)
4183 except ValueError:
4183 except ValueError:
4184 raise error.Abort(_(b'similarity must be a number'))
4184 raise error.Abort(_(b'similarity must be a number'))
4185 if sim < 0 or sim > 100:
4185 if sim < 0 or sim > 100:
4186 raise error.Abort(_(b'similarity must be between 0 and 100'))
4186 raise error.Abort(_(b'similarity must be between 0 and 100'))
4187 if sim and not update:
4187 if sim and not update:
4188 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4188 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4189 if exact:
4189 if exact:
4190 if opts.get(b'edit'):
4190 if opts.get(b'edit'):
4191 raise error.Abort(_(b'cannot use --exact with --edit'))
4191 raise error.Abort(_(b'cannot use --exact with --edit'))
4192 if opts.get(b'prefix'):
4192 if opts.get(b'prefix'):
4193 raise error.Abort(_(b'cannot use --exact with --prefix'))
4193 raise error.Abort(_(b'cannot use --exact with --prefix'))
4194
4194
4195 base = opts[b"base"]
4195 base = opts[b"base"]
4196 msgs = []
4196 msgs = []
4197 ret = 0
4197 ret = 0
4198
4198
4199 with repo.wlock():
4199 with repo.wlock():
4200 if update:
4200 if update:
4201 cmdutil.checkunfinished(repo)
4201 cmdutil.checkunfinished(repo)
4202 if exact or not opts.get(b'force'):
4202 if exact or not opts.get(b'force'):
4203 cmdutil.bailifchanged(repo)
4203 cmdutil.bailifchanged(repo)
4204
4204
4205 if not opts.get(b'no_commit'):
4205 if not opts.get(b'no_commit'):
4206 lock = repo.lock
4206 lock = repo.lock
4207 tr = lambda: repo.transaction(b'import')
4207 tr = lambda: repo.transaction(b'import')
4208 dsguard = util.nullcontextmanager
4208 dsguard = util.nullcontextmanager
4209 else:
4209 else:
4210 lock = util.nullcontextmanager
4210 lock = util.nullcontextmanager
4211 tr = util.nullcontextmanager
4211 tr = util.nullcontextmanager
4212 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4212 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4213 with lock(), tr(), dsguard():
4213 with lock(), tr(), dsguard():
4214 parents = repo[None].parents()
4214 parents = repo[None].parents()
4215 for patchurl in patches:
4215 for patchurl in patches:
4216 if patchurl == b'-':
4216 if patchurl == b'-':
4217 ui.status(_(b'applying patch from stdin\n'))
4217 ui.status(_(b'applying patch from stdin\n'))
4218 patchfile = ui.fin
4218 patchfile = ui.fin
4219 patchurl = b'stdin' # for error message
4219 patchurl = b'stdin' # for error message
4220 else:
4220 else:
4221 patchurl = os.path.join(base, patchurl)
4221 patchurl = os.path.join(base, patchurl)
4222 ui.status(_(b'applying %s\n') % patchurl)
4222 ui.status(_(b'applying %s\n') % patchurl)
4223 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4223 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4224
4224
4225 haspatch = False
4225 haspatch = False
4226 for hunk in patch.split(patchfile):
4226 for hunk in patch.split(patchfile):
4227 with patch.extract(ui, hunk) as patchdata:
4227 with patch.extract(ui, hunk) as patchdata:
4228 msg, node, rej = cmdutil.tryimportone(
4228 msg, node, rej = cmdutil.tryimportone(
4229 ui, repo, patchdata, parents, opts, msgs, hg.clean
4229 ui, repo, patchdata, parents, opts, msgs, hg.clean
4230 )
4230 )
4231 if msg:
4231 if msg:
4232 haspatch = True
4232 haspatch = True
4233 ui.note(msg + b'\n')
4233 ui.note(msg + b'\n')
4234 if update or exact:
4234 if update or exact:
4235 parents = repo[None].parents()
4235 parents = repo[None].parents()
4236 else:
4236 else:
4237 parents = [repo[node]]
4237 parents = [repo[node]]
4238 if rej:
4238 if rej:
4239 ui.write_err(_(b"patch applied partially\n"))
4239 ui.write_err(_(b"patch applied partially\n"))
4240 ui.write_err(
4240 ui.write_err(
4241 _(
4241 _(
4242 b"(fix the .rej files and run "
4242 b"(fix the .rej files and run "
4243 b"`hg commit --amend`)\n"
4243 b"`hg commit --amend`)\n"
4244 )
4244 )
4245 )
4245 )
4246 ret = 1
4246 ret = 1
4247 break
4247 break
4248
4248
4249 if not haspatch:
4249 if not haspatch:
4250 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4250 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4251
4251
4252 if msgs:
4252 if msgs:
4253 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4253 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4254 return ret
4254 return ret
4255
4255
4256
4256
4257 @command(
4257 @command(
4258 b'incoming|in',
4258 b'incoming|in',
4259 [
4259 [
4260 (
4260 (
4261 b'f',
4261 b'f',
4262 b'force',
4262 b'force',
4263 None,
4263 None,
4264 _(b'run even if remote repository is unrelated'),
4264 _(b'run even if remote repository is unrelated'),
4265 ),
4265 ),
4266 (b'n', b'newest-first', None, _(b'show newest record first')),
4266 (b'n', b'newest-first', None, _(b'show newest record first')),
4267 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4267 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4268 (
4268 (
4269 b'r',
4269 b'r',
4270 b'rev',
4270 b'rev',
4271 [],
4271 [],
4272 _(b'a remote changeset intended to be added'),
4272 _(b'a remote changeset intended to be added'),
4273 _(b'REV'),
4273 _(b'REV'),
4274 ),
4274 ),
4275 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4275 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4276 (
4276 (
4277 b'b',
4277 b'b',
4278 b'branch',
4278 b'branch',
4279 [],
4279 [],
4280 _(b'a specific branch you would like to pull'),
4280 _(b'a specific branch you would like to pull'),
4281 _(b'BRANCH'),
4281 _(b'BRANCH'),
4282 ),
4282 ),
4283 ]
4283 ]
4284 + logopts
4284 + logopts
4285 + remoteopts
4285 + remoteopts
4286 + subrepoopts,
4286 + subrepoopts,
4287 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4287 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4288 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4288 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4289 )
4289 )
4290 def incoming(ui, repo, source=b"default", **opts):
4290 def incoming(ui, repo, source=b"default", **opts):
4291 """show new changesets found in source
4291 """show new changesets found in source
4292
4292
4293 Show new changesets found in the specified path/URL or the default
4293 Show new changesets found in the specified path/URL or the default
4294 pull location. These are the changesets that would have been pulled
4294 pull location. These are the changesets that would have been pulled
4295 by :hg:`pull` at the time you issued this command.
4295 by :hg:`pull` at the time you issued this command.
4296
4296
4297 See pull for valid source format details.
4297 See pull for valid source format details.
4298
4298
4299 .. container:: verbose
4299 .. container:: verbose
4300
4300
4301 With -B/--bookmarks, the result of bookmark comparison between
4301 With -B/--bookmarks, the result of bookmark comparison between
4302 local and remote repositories is displayed. With -v/--verbose,
4302 local and remote repositories is displayed. With -v/--verbose,
4303 status is also displayed for each bookmark like below::
4303 status is also displayed for each bookmark like below::
4304
4304
4305 BM1 01234567890a added
4305 BM1 01234567890a added
4306 BM2 1234567890ab advanced
4306 BM2 1234567890ab advanced
4307 BM3 234567890abc diverged
4307 BM3 234567890abc diverged
4308 BM4 34567890abcd changed
4308 BM4 34567890abcd changed
4309
4309
4310 The action taken locally when pulling depends on the
4310 The action taken locally when pulling depends on the
4311 status of each bookmark:
4311 status of each bookmark:
4312
4312
4313 :``added``: pull will create it
4313 :``added``: pull will create it
4314 :``advanced``: pull will update it
4314 :``advanced``: pull will update it
4315 :``diverged``: pull will create a divergent bookmark
4315 :``diverged``: pull will create a divergent bookmark
4316 :``changed``: result depends on remote changesets
4316 :``changed``: result depends on remote changesets
4317
4317
4318 From the point of view of pulling behavior, bookmark
4318 From the point of view of pulling behavior, bookmark
4319 existing only in the remote repository are treated as ``added``,
4319 existing only in the remote repository are treated as ``added``,
4320 even if it is in fact locally deleted.
4320 even if it is in fact locally deleted.
4321
4321
4322 .. container:: verbose
4322 .. container:: verbose
4323
4323
4324 For remote repository, using --bundle avoids downloading the
4324 For remote repository, using --bundle avoids downloading the
4325 changesets twice if the incoming is followed by a pull.
4325 changesets twice if the incoming is followed by a pull.
4326
4326
4327 Examples:
4327 Examples:
4328
4328
4329 - show incoming changes with patches and full description::
4329 - show incoming changes with patches and full description::
4330
4330
4331 hg incoming -vp
4331 hg incoming -vp
4332
4332
4333 - show incoming changes excluding merges, store a bundle::
4333 - show incoming changes excluding merges, store a bundle::
4334
4334
4335 hg in -vpM --bundle incoming.hg
4335 hg in -vpM --bundle incoming.hg
4336 hg pull incoming.hg
4336 hg pull incoming.hg
4337
4337
4338 - briefly list changes inside a bundle::
4338 - briefly list changes inside a bundle::
4339
4339
4340 hg in changes.hg -T "{desc|firstline}\\n"
4340 hg in changes.hg -T "{desc|firstline}\\n"
4341
4341
4342 Returns 0 if there are incoming changes, 1 otherwise.
4342 Returns 0 if there are incoming changes, 1 otherwise.
4343 """
4343 """
4344 opts = pycompat.byteskwargs(opts)
4344 opts = pycompat.byteskwargs(opts)
4345 if opts.get(b'graph'):
4345 if opts.get(b'graph'):
4346 logcmdutil.checkunsupportedgraphflags([], opts)
4346 logcmdutil.checkunsupportedgraphflags([], opts)
4347
4347
4348 def display(other, chlist, displayer):
4348 def display(other, chlist, displayer):
4349 revdag = logcmdutil.graphrevs(other, chlist, opts)
4349 revdag = logcmdutil.graphrevs(other, chlist, opts)
4350 logcmdutil.displaygraph(
4350 logcmdutil.displaygraph(
4351 ui, repo, revdag, displayer, graphmod.asciiedges
4351 ui, repo, revdag, displayer, graphmod.asciiedges
4352 )
4352 )
4353
4353
4354 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4354 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4355 return 0
4355 return 0
4356
4356
4357 if opts.get(b'bundle') and opts.get(b'subrepos'):
4357 if opts.get(b'bundle') and opts.get(b'subrepos'):
4358 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4358 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4359
4359
4360 if opts.get(b'bookmarks'):
4360 if opts.get(b'bookmarks'):
4361 source, branches = hg.parseurl(
4361 source, branches = hg.parseurl(
4362 ui.expandpath(source), opts.get(b'branch')
4362 ui.expandpath(source), opts.get(b'branch')
4363 )
4363 )
4364 other = hg.peer(repo, opts, source)
4364 other = hg.peer(repo, opts, source)
4365 if b'bookmarks' not in other.listkeys(b'namespaces'):
4365 if b'bookmarks' not in other.listkeys(b'namespaces'):
4366 ui.warn(_(b"remote doesn't support bookmarks\n"))
4366 ui.warn(_(b"remote doesn't support bookmarks\n"))
4367 return 0
4367 return 0
4368 ui.pager(b'incoming')
4368 ui.pager(b'incoming')
4369 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4369 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4370 return bookmarks.incoming(ui, repo, other)
4370 return bookmarks.incoming(ui, repo, other)
4371
4371
4372 repo._subtoppath = ui.expandpath(source)
4372 repo._subtoppath = ui.expandpath(source)
4373 try:
4373 try:
4374 return hg.incoming(ui, repo, source, opts)
4374 return hg.incoming(ui, repo, source, opts)
4375 finally:
4375 finally:
4376 del repo._subtoppath
4376 del repo._subtoppath
4377
4377
4378
4378
4379 @command(
4379 @command(
4380 b'init',
4380 b'init',
4381 remoteopts,
4381 remoteopts,
4382 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4382 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4383 helpcategory=command.CATEGORY_REPO_CREATION,
4383 helpcategory=command.CATEGORY_REPO_CREATION,
4384 helpbasic=True,
4384 helpbasic=True,
4385 norepo=True,
4385 norepo=True,
4386 )
4386 )
4387 def init(ui, dest=b".", **opts):
4387 def init(ui, dest=b".", **opts):
4388 """create a new repository in the given directory
4388 """create a new repository in the given directory
4389
4389
4390 Initialize a new repository in the given directory. If the given
4390 Initialize a new repository in the given directory. If the given
4391 directory does not exist, it will be created.
4391 directory does not exist, it will be created.
4392
4392
4393 If no directory is given, the current directory is used.
4393 If no directory is given, the current directory is used.
4394
4394
4395 It is possible to specify an ``ssh://`` URL as the destination.
4395 It is possible to specify an ``ssh://`` URL as the destination.
4396 See :hg:`help urls` for more information.
4396 See :hg:`help urls` for more information.
4397
4397
4398 Returns 0 on success.
4398 Returns 0 on success.
4399 """
4399 """
4400 opts = pycompat.byteskwargs(opts)
4400 opts = pycompat.byteskwargs(opts)
4401 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4401 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4402
4402
4403
4403
4404 @command(
4404 @command(
4405 b'locate',
4405 b'locate',
4406 [
4406 [
4407 (
4407 (
4408 b'r',
4408 b'r',
4409 b'rev',
4409 b'rev',
4410 b'',
4410 b'',
4411 _(b'search the repository as it is in REV'),
4411 _(b'search the repository as it is in REV'),
4412 _(b'REV'),
4412 _(b'REV'),
4413 ),
4413 ),
4414 (
4414 (
4415 b'0',
4415 b'0',
4416 b'print0',
4416 b'print0',
4417 None,
4417 None,
4418 _(b'end filenames with NUL, for use with xargs'),
4418 _(b'end filenames with NUL, for use with xargs'),
4419 ),
4419 ),
4420 (
4420 (
4421 b'f',
4421 b'f',
4422 b'fullpath',
4422 b'fullpath',
4423 None,
4423 None,
4424 _(b'print complete paths from the filesystem root'),
4424 _(b'print complete paths from the filesystem root'),
4425 ),
4425 ),
4426 ]
4426 ]
4427 + walkopts,
4427 + walkopts,
4428 _(b'[OPTION]... [PATTERN]...'),
4428 _(b'[OPTION]... [PATTERN]...'),
4429 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4429 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4430 )
4430 )
4431 def locate(ui, repo, *pats, **opts):
4431 def locate(ui, repo, *pats, **opts):
4432 """locate files matching specific patterns (DEPRECATED)
4432 """locate files matching specific patterns (DEPRECATED)
4433
4433
4434 Print files under Mercurial control in the working directory whose
4434 Print files under Mercurial control in the working directory whose
4435 names match the given patterns.
4435 names match the given patterns.
4436
4436
4437 By default, this command searches all directories in the working
4437 By default, this command searches all directories in the working
4438 directory. To search just the current directory and its
4438 directory. To search just the current directory and its
4439 subdirectories, use "--include .".
4439 subdirectories, use "--include .".
4440
4440
4441 If no patterns are given to match, this command prints the names
4441 If no patterns are given to match, this command prints the names
4442 of all files under Mercurial control in the working directory.
4442 of all files under Mercurial control in the working directory.
4443
4443
4444 If you want to feed the output of this command into the "xargs"
4444 If you want to feed the output of this command into the "xargs"
4445 command, use the -0 option to both this command and "xargs". This
4445 command, use the -0 option to both this command and "xargs". This
4446 will avoid the problem of "xargs" treating single filenames that
4446 will avoid the problem of "xargs" treating single filenames that
4447 contain whitespace as multiple filenames.
4447 contain whitespace as multiple filenames.
4448
4448
4449 See :hg:`help files` for a more versatile command.
4449 See :hg:`help files` for a more versatile command.
4450
4450
4451 Returns 0 if a match is found, 1 otherwise.
4451 Returns 0 if a match is found, 1 otherwise.
4452 """
4452 """
4453 opts = pycompat.byteskwargs(opts)
4453 opts = pycompat.byteskwargs(opts)
4454 if opts.get(b'print0'):
4454 if opts.get(b'print0'):
4455 end = b'\0'
4455 end = b'\0'
4456 else:
4456 else:
4457 end = b'\n'
4457 end = b'\n'
4458 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4458 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4459
4459
4460 ret = 1
4460 ret = 1
4461 m = scmutil.match(
4461 m = scmutil.match(
4462 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4462 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4463 )
4463 )
4464
4464
4465 ui.pager(b'locate')
4465 ui.pager(b'locate')
4466 if ctx.rev() is None:
4466 if ctx.rev() is None:
4467 # When run on the working copy, "locate" includes removed files, so
4467 # When run on the working copy, "locate" includes removed files, so
4468 # we get the list of files from the dirstate.
4468 # we get the list of files from the dirstate.
4469 filesgen = sorted(repo.dirstate.matches(m))
4469 filesgen = sorted(repo.dirstate.matches(m))
4470 else:
4470 else:
4471 filesgen = ctx.matches(m)
4471 filesgen = ctx.matches(m)
4472 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4472 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4473 for abs in filesgen:
4473 for abs in filesgen:
4474 if opts.get(b'fullpath'):
4474 if opts.get(b'fullpath'):
4475 ui.write(repo.wjoin(abs), end)
4475 ui.write(repo.wjoin(abs), end)
4476 else:
4476 else:
4477 ui.write(uipathfn(abs), end)
4477 ui.write(uipathfn(abs), end)
4478 ret = 0
4478 ret = 0
4479
4479
4480 return ret
4480 return ret
4481
4481
4482
4482
4483 @command(
4483 @command(
4484 b'log|history',
4484 b'log|history',
4485 [
4485 [
4486 (
4486 (
4487 b'f',
4487 b'f',
4488 b'follow',
4488 b'follow',
4489 None,
4489 None,
4490 _(
4490 _(
4491 b'follow changeset history, or file history across copies and renames'
4491 b'follow changeset history, or file history across copies and renames'
4492 ),
4492 ),
4493 ),
4493 ),
4494 (
4494 (
4495 b'',
4495 b'',
4496 b'follow-first',
4496 b'follow-first',
4497 None,
4497 None,
4498 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4498 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4499 ),
4499 ),
4500 (
4500 (
4501 b'd',
4501 b'd',
4502 b'date',
4502 b'date',
4503 b'',
4503 b'',
4504 _(b'show revisions matching date spec'),
4504 _(b'show revisions matching date spec'),
4505 _(b'DATE'),
4505 _(b'DATE'),
4506 ),
4506 ),
4507 (b'C', b'copies', None, _(b'show copied files')),
4507 (b'C', b'copies', None, _(b'show copied files')),
4508 (
4508 (
4509 b'k',
4509 b'k',
4510 b'keyword',
4510 b'keyword',
4511 [],
4511 [],
4512 _(b'do case-insensitive search for a given text'),
4512 _(b'do case-insensitive search for a given text'),
4513 _(b'TEXT'),
4513 _(b'TEXT'),
4514 ),
4514 ),
4515 (
4515 (
4516 b'r',
4516 b'r',
4517 b'rev',
4517 b'rev',
4518 [],
4518 [],
4519 _(b'show the specified revision or revset'),
4519 _(b'show the specified revision or revset'),
4520 _(b'REV'),
4520 _(b'REV'),
4521 ),
4521 ),
4522 (
4522 (
4523 b'L',
4523 b'L',
4524 b'line-range',
4524 b'line-range',
4525 [],
4525 [],
4526 _(b'follow line range of specified file (EXPERIMENTAL)'),
4526 _(b'follow line range of specified file (EXPERIMENTAL)'),
4527 _(b'FILE,RANGE'),
4527 _(b'FILE,RANGE'),
4528 ),
4528 ),
4529 (
4529 (
4530 b'',
4530 b'',
4531 b'removed',
4531 b'removed',
4532 None,
4532 None,
4533 _(b'include revisions where files were removed'),
4533 _(b'include revisions where files were removed'),
4534 ),
4534 ),
4535 (
4535 (
4536 b'm',
4536 b'm',
4537 b'only-merges',
4537 b'only-merges',
4538 None,
4538 None,
4539 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4539 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4540 ),
4540 ),
4541 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4541 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4542 (
4542 (
4543 b'',
4543 b'',
4544 b'only-branch',
4544 b'only-branch',
4545 [],
4545 [],
4546 _(
4546 _(
4547 b'show only changesets within the given named branch (DEPRECATED)'
4547 b'show only changesets within the given named branch (DEPRECATED)'
4548 ),
4548 ),
4549 _(b'BRANCH'),
4549 _(b'BRANCH'),
4550 ),
4550 ),
4551 (
4551 (
4552 b'b',
4552 b'b',
4553 b'branch',
4553 b'branch',
4554 [],
4554 [],
4555 _(b'show changesets within the given named branch'),
4555 _(b'show changesets within the given named branch'),
4556 _(b'BRANCH'),
4556 _(b'BRANCH'),
4557 ),
4557 ),
4558 (
4558 (
4559 b'P',
4559 b'P',
4560 b'prune',
4560 b'prune',
4561 [],
4561 [],
4562 _(b'do not display revision or any of its ancestors'),
4562 _(b'do not display revision or any of its ancestors'),
4563 _(b'REV'),
4563 _(b'REV'),
4564 ),
4564 ),
4565 ]
4565 ]
4566 + logopts
4566 + logopts
4567 + walkopts,
4567 + walkopts,
4568 _(b'[OPTION]... [FILE]'),
4568 _(b'[OPTION]... [FILE]'),
4569 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4569 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4570 helpbasic=True,
4570 helpbasic=True,
4571 inferrepo=True,
4571 inferrepo=True,
4572 intents={INTENT_READONLY},
4572 intents={INTENT_READONLY},
4573 )
4573 )
4574 def log(ui, repo, *pats, **opts):
4574 def log(ui, repo, *pats, **opts):
4575 """show revision history of entire repository or files
4575 """show revision history of entire repository or files
4576
4576
4577 Print the revision history of the specified files or the entire
4577 Print the revision history of the specified files or the entire
4578 project.
4578 project.
4579
4579
4580 If no revision range is specified, the default is ``tip:0`` unless
4580 If no revision range is specified, the default is ``tip:0`` unless
4581 --follow is set, in which case the working directory parent is
4581 --follow is set, in which case the working directory parent is
4582 used as the starting revision.
4582 used as the starting revision.
4583
4583
4584 File history is shown without following rename or copy history of
4584 File history is shown without following rename or copy history of
4585 files. Use -f/--follow with a filename to follow history across
4585 files. Use -f/--follow with a filename to follow history across
4586 renames and copies. --follow without a filename will only show
4586 renames and copies. --follow without a filename will only show
4587 ancestors of the starting revision.
4587 ancestors of the starting revision.
4588
4588
4589 By default this command prints revision number and changeset id,
4589 By default this command prints revision number and changeset id,
4590 tags, non-trivial parents, user, date and time, and a summary for
4590 tags, non-trivial parents, user, date and time, and a summary for
4591 each commit. When the -v/--verbose switch is used, the list of
4591 each commit. When the -v/--verbose switch is used, the list of
4592 changed files and full commit message are shown.
4592 changed files and full commit message are shown.
4593
4593
4594 With --graph the revisions are shown as an ASCII art DAG with the most
4594 With --graph the revisions are shown as an ASCII art DAG with the most
4595 recent changeset at the top.
4595 recent changeset at the top.
4596 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4596 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4597 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4597 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4598 changeset from the lines below is a parent of the 'o' merge on the same
4598 changeset from the lines below is a parent of the 'o' merge on the same
4599 line.
4599 line.
4600 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4600 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4601 of a '|' indicates one or more revisions in a path are omitted.
4601 of a '|' indicates one or more revisions in a path are omitted.
4602
4602
4603 .. container:: verbose
4603 .. container:: verbose
4604
4604
4605 Use -L/--line-range FILE,M:N options to follow the history of lines
4605 Use -L/--line-range FILE,M:N options to follow the history of lines
4606 from M to N in FILE. With -p/--patch only diff hunks affecting
4606 from M to N in FILE. With -p/--patch only diff hunks affecting
4607 specified line range will be shown. This option requires --follow;
4607 specified line range will be shown. This option requires --follow;
4608 it can be specified multiple times. Currently, this option is not
4608 it can be specified multiple times. Currently, this option is not
4609 compatible with --graph. This option is experimental.
4609 compatible with --graph. This option is experimental.
4610
4610
4611 .. note::
4611 .. note::
4612
4612
4613 :hg:`log --patch` may generate unexpected diff output for merge
4613 :hg:`log --patch` may generate unexpected diff output for merge
4614 changesets, as it will only compare the merge changeset against
4614 changesets, as it will only compare the merge changeset against
4615 its first parent. Also, only files different from BOTH parents
4615 its first parent. Also, only files different from BOTH parents
4616 will appear in files:.
4616 will appear in files:.
4617
4617
4618 .. note::
4618 .. note::
4619
4619
4620 For performance reasons, :hg:`log FILE` may omit duplicate changes
4620 For performance reasons, :hg:`log FILE` may omit duplicate changes
4621 made on branches and will not show removals or mode changes. To
4621 made on branches and will not show removals or mode changes. To
4622 see all such changes, use the --removed switch.
4622 see all such changes, use the --removed switch.
4623
4623
4624 .. container:: verbose
4624 .. container:: verbose
4625
4625
4626 .. note::
4626 .. note::
4627
4627
4628 The history resulting from -L/--line-range options depends on diff
4628 The history resulting from -L/--line-range options depends on diff
4629 options; for instance if white-spaces are ignored, respective changes
4629 options; for instance if white-spaces are ignored, respective changes
4630 with only white-spaces in specified line range will not be listed.
4630 with only white-spaces in specified line range will not be listed.
4631
4631
4632 .. container:: verbose
4632 .. container:: verbose
4633
4633
4634 Some examples:
4634 Some examples:
4635
4635
4636 - changesets with full descriptions and file lists::
4636 - changesets with full descriptions and file lists::
4637
4637
4638 hg log -v
4638 hg log -v
4639
4639
4640 - changesets ancestral to the working directory::
4640 - changesets ancestral to the working directory::
4641
4641
4642 hg log -f
4642 hg log -f
4643
4643
4644 - last 10 commits on the current branch::
4644 - last 10 commits on the current branch::
4645
4645
4646 hg log -l 10 -b .
4646 hg log -l 10 -b .
4647
4647
4648 - changesets showing all modifications of a file, including removals::
4648 - changesets showing all modifications of a file, including removals::
4649
4649
4650 hg log --removed file.c
4650 hg log --removed file.c
4651
4651
4652 - all changesets that touch a directory, with diffs, excluding merges::
4652 - all changesets that touch a directory, with diffs, excluding merges::
4653
4653
4654 hg log -Mp lib/
4654 hg log -Mp lib/
4655
4655
4656 - all revision numbers that match a keyword::
4656 - all revision numbers that match a keyword::
4657
4657
4658 hg log -k bug --template "{rev}\\n"
4658 hg log -k bug --template "{rev}\\n"
4659
4659
4660 - the full hash identifier of the working directory parent::
4660 - the full hash identifier of the working directory parent::
4661
4661
4662 hg log -r . --template "{node}\\n"
4662 hg log -r . --template "{node}\\n"
4663
4663
4664 - list available log templates::
4664 - list available log templates::
4665
4665
4666 hg log -T list
4666 hg log -T list
4667
4667
4668 - check if a given changeset is included in a tagged release::
4668 - check if a given changeset is included in a tagged release::
4669
4669
4670 hg log -r "a21ccf and ancestor(1.9)"
4670 hg log -r "a21ccf and ancestor(1.9)"
4671
4671
4672 - find all changesets by some user in a date range::
4672 - find all changesets by some user in a date range::
4673
4673
4674 hg log -k alice -d "may 2008 to jul 2008"
4674 hg log -k alice -d "may 2008 to jul 2008"
4675
4675
4676 - summary of all changesets after the last tag::
4676 - summary of all changesets after the last tag::
4677
4677
4678 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4678 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4679
4679
4680 - changesets touching lines 13 to 23 for file.c::
4680 - changesets touching lines 13 to 23 for file.c::
4681
4681
4682 hg log -L file.c,13:23
4682 hg log -L file.c,13:23
4683
4683
4684 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4684 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4685 main.c with patch::
4685 main.c with patch::
4686
4686
4687 hg log -L file.c,13:23 -L main.c,2:6 -p
4687 hg log -L file.c,13:23 -L main.c,2:6 -p
4688
4688
4689 See :hg:`help dates` for a list of formats valid for -d/--date.
4689 See :hg:`help dates` for a list of formats valid for -d/--date.
4690
4690
4691 See :hg:`help revisions` for more about specifying and ordering
4691 See :hg:`help revisions` for more about specifying and ordering
4692 revisions.
4692 revisions.
4693
4693
4694 See :hg:`help templates` for more about pre-packaged styles and
4694 See :hg:`help templates` for more about pre-packaged styles and
4695 specifying custom templates. The default template used by the log
4695 specifying custom templates. The default template used by the log
4696 command can be customized via the ``ui.logtemplate`` configuration
4696 command can be customized via the ``ui.logtemplate`` configuration
4697 setting.
4697 setting.
4698
4698
4699 Returns 0 on success.
4699 Returns 0 on success.
4700
4700
4701 """
4701 """
4702 opts = pycompat.byteskwargs(opts)
4702 opts = pycompat.byteskwargs(opts)
4703 linerange = opts.get(b'line_range')
4703 linerange = opts.get(b'line_range')
4704
4704
4705 if linerange and not opts.get(b'follow'):
4705 if linerange and not opts.get(b'follow'):
4706 raise error.Abort(_(b'--line-range requires --follow'))
4706 raise error.Abort(_(b'--line-range requires --follow'))
4707
4707
4708 if linerange and pats:
4708 if linerange and pats:
4709 # TODO: take pats as patterns with no line-range filter
4709 # TODO: take pats as patterns with no line-range filter
4710 raise error.Abort(
4710 raise error.Abort(
4711 _(b'FILE arguments are not compatible with --line-range option')
4711 _(b'FILE arguments are not compatible with --line-range option')
4712 )
4712 )
4713
4713
4714 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4714 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4715 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4715 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4716 if linerange:
4716 if linerange:
4717 # TODO: should follow file history from logcmdutil._initialrevs(),
4717 # TODO: should follow file history from logcmdutil._initialrevs(),
4718 # then filter the result by logcmdutil._makerevset() and --limit
4718 # then filter the result by logcmdutil._makerevset() and --limit
4719 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4719 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4720
4720
4721 getcopies = None
4721 getcopies = None
4722 if opts.get(b'copies'):
4722 if opts.get(b'copies'):
4723 endrev = None
4723 endrev = None
4724 if revs:
4724 if revs:
4725 endrev = revs.max() + 1
4725 endrev = revs.max() + 1
4726 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4726 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4727
4727
4728 ui.pager(b'log')
4728 ui.pager(b'log')
4729 displayer = logcmdutil.changesetdisplayer(
4729 displayer = logcmdutil.changesetdisplayer(
4730 ui, repo, opts, differ, buffered=True
4730 ui, repo, opts, differ, buffered=True
4731 )
4731 )
4732 if opts.get(b'graph'):
4732 if opts.get(b'graph'):
4733 displayfn = logcmdutil.displaygraphrevs
4733 displayfn = logcmdutil.displaygraphrevs
4734 else:
4734 else:
4735 displayfn = logcmdutil.displayrevs
4735 displayfn = logcmdutil.displayrevs
4736 displayfn(ui, repo, revs, displayer, getcopies)
4736 displayfn(ui, repo, revs, displayer, getcopies)
4737
4737
4738
4738
4739 @command(
4739 @command(
4740 b'manifest',
4740 b'manifest',
4741 [
4741 [
4742 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4742 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4743 (b'', b'all', False, _(b"list files from all revisions")),
4743 (b'', b'all', False, _(b"list files from all revisions")),
4744 ]
4744 ]
4745 + formatteropts,
4745 + formatteropts,
4746 _(b'[-r REV]'),
4746 _(b'[-r REV]'),
4747 helpcategory=command.CATEGORY_MAINTENANCE,
4747 helpcategory=command.CATEGORY_MAINTENANCE,
4748 intents={INTENT_READONLY},
4748 intents={INTENT_READONLY},
4749 )
4749 )
4750 def manifest(ui, repo, node=None, rev=None, **opts):
4750 def manifest(ui, repo, node=None, rev=None, **opts):
4751 """output the current or given revision of the project manifest
4751 """output the current or given revision of the project manifest
4752
4752
4753 Print a list of version controlled files for the given revision.
4753 Print a list of version controlled files for the given revision.
4754 If no revision is given, the first parent of the working directory
4754 If no revision is given, the first parent of the working directory
4755 is used, or the null revision if no revision is checked out.
4755 is used, or the null revision if no revision is checked out.
4756
4756
4757 With -v, print file permissions, symlink and executable bits.
4757 With -v, print file permissions, symlink and executable bits.
4758 With --debug, print file revision hashes.
4758 With --debug, print file revision hashes.
4759
4759
4760 If option --all is specified, the list of all files from all revisions
4760 If option --all is specified, the list of all files from all revisions
4761 is printed. This includes deleted and renamed files.
4761 is printed. This includes deleted and renamed files.
4762
4762
4763 Returns 0 on success.
4763 Returns 0 on success.
4764 """
4764 """
4765 opts = pycompat.byteskwargs(opts)
4765 opts = pycompat.byteskwargs(opts)
4766 fm = ui.formatter(b'manifest', opts)
4766 fm = ui.formatter(b'manifest', opts)
4767
4767
4768 if opts.get(b'all'):
4768 if opts.get(b'all'):
4769 if rev or node:
4769 if rev or node:
4770 raise error.Abort(_(b"can't specify a revision with --all"))
4770 raise error.Abort(_(b"can't specify a revision with --all"))
4771
4771
4772 res = set()
4772 res = set()
4773 for rev in repo:
4773 for rev in repo:
4774 ctx = repo[rev]
4774 ctx = repo[rev]
4775 res |= set(ctx.files())
4775 res |= set(ctx.files())
4776
4776
4777 ui.pager(b'manifest')
4777 ui.pager(b'manifest')
4778 for f in sorted(res):
4778 for f in sorted(res):
4779 fm.startitem()
4779 fm.startitem()
4780 fm.write(b"path", b'%s\n', f)
4780 fm.write(b"path", b'%s\n', f)
4781 fm.end()
4781 fm.end()
4782 return
4782 return
4783
4783
4784 if rev and node:
4784 if rev and node:
4785 raise error.Abort(_(b"please specify just one revision"))
4785 raise error.Abort(_(b"please specify just one revision"))
4786
4786
4787 if not node:
4787 if not node:
4788 node = rev
4788 node = rev
4789
4789
4790 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4790 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4791 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4791 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4792 if node:
4792 if node:
4793 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4793 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4794 ctx = scmutil.revsingle(repo, node)
4794 ctx = scmutil.revsingle(repo, node)
4795 mf = ctx.manifest()
4795 mf = ctx.manifest()
4796 ui.pager(b'manifest')
4796 ui.pager(b'manifest')
4797 for f in ctx:
4797 for f in ctx:
4798 fm.startitem()
4798 fm.startitem()
4799 fm.context(ctx=ctx)
4799 fm.context(ctx=ctx)
4800 fl = ctx[f].flags()
4800 fl = ctx[f].flags()
4801 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4801 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4802 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4802 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4803 fm.write(b'path', b'%s\n', f)
4803 fm.write(b'path', b'%s\n', f)
4804 fm.end()
4804 fm.end()
4805
4805
4806
4806
4807 @command(
4807 @command(
4808 b'merge',
4808 b'merge',
4809 [
4809 [
4810 (
4810 (
4811 b'f',
4811 b'f',
4812 b'force',
4812 b'force',
4813 None,
4813 None,
4814 _(b'force a merge including outstanding changes (DEPRECATED)'),
4814 _(b'force a merge including outstanding changes (DEPRECATED)'),
4815 ),
4815 ),
4816 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4816 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4817 (
4817 (
4818 b'P',
4818 b'P',
4819 b'preview',
4819 b'preview',
4820 None,
4820 None,
4821 _(b'review revisions to merge (no merge is performed)'),
4821 _(b'review revisions to merge (no merge is performed)'),
4822 ),
4822 ),
4823 (b'', b'abort', None, _(b'abort the ongoing merge')),
4823 (b'', b'abort', None, _(b'abort the ongoing merge')),
4824 ]
4824 ]
4825 + mergetoolopts,
4825 + mergetoolopts,
4826 _(b'[-P] [[-r] REV]'),
4826 _(b'[-P] [[-r] REV]'),
4827 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4827 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4828 helpbasic=True,
4828 helpbasic=True,
4829 )
4829 )
4830 def merge(ui, repo, node=None, **opts):
4830 def merge(ui, repo, node=None, **opts):
4831 """merge another revision into working directory
4831 """merge another revision into working directory
4832
4832
4833 The current working directory is updated with all changes made in
4833 The current working directory is updated with all changes made in
4834 the requested revision since the last common predecessor revision.
4834 the requested revision since the last common predecessor revision.
4835
4835
4836 Files that changed between either parent are marked as changed for
4836 Files that changed between either parent are marked as changed for
4837 the next commit and a commit must be performed before any further
4837 the next commit and a commit must be performed before any further
4838 updates to the repository are allowed. The next commit will have
4838 updates to the repository are allowed. The next commit will have
4839 two parents.
4839 two parents.
4840
4840
4841 ``--tool`` can be used to specify the merge tool used for file
4841 ``--tool`` can be used to specify the merge tool used for file
4842 merges. It overrides the HGMERGE environment variable and your
4842 merges. It overrides the HGMERGE environment variable and your
4843 configuration files. See :hg:`help merge-tools` for options.
4843 configuration files. See :hg:`help merge-tools` for options.
4844
4844
4845 If no revision is specified, the working directory's parent is a
4845 If no revision is specified, the working directory's parent is a
4846 head revision, and the current branch contains exactly one other
4846 head revision, and the current branch contains exactly one other
4847 head, the other head is merged with by default. Otherwise, an
4847 head, the other head is merged with by default. Otherwise, an
4848 explicit revision with which to merge must be provided.
4848 explicit revision with which to merge must be provided.
4849
4849
4850 See :hg:`help resolve` for information on handling file conflicts.
4850 See :hg:`help resolve` for information on handling file conflicts.
4851
4851
4852 To undo an uncommitted merge, use :hg:`merge --abort` which
4852 To undo an uncommitted merge, use :hg:`merge --abort` which
4853 will check out a clean copy of the original merge parent, losing
4853 will check out a clean copy of the original merge parent, losing
4854 all changes.
4854 all changes.
4855
4855
4856 Returns 0 on success, 1 if there are unresolved files.
4856 Returns 0 on success, 1 if there are unresolved files.
4857 """
4857 """
4858
4858
4859 opts = pycompat.byteskwargs(opts)
4859 opts = pycompat.byteskwargs(opts)
4860 abort = opts.get(b'abort')
4860 abort = opts.get(b'abort')
4861 if abort and repo.dirstate.p2() == nullid:
4861 if abort and repo.dirstate.p2() == nullid:
4862 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4862 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4863 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4863 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4864 if abort:
4864 if abort:
4865 state = cmdutil.getunfinishedstate(repo)
4865 state = cmdutil.getunfinishedstate(repo)
4866 if state and state._opname != b'merge':
4866 if state and state._opname != b'merge':
4867 raise error.Abort(
4867 raise error.Abort(
4868 _(b'cannot abort merge with %s in progress') % (state._opname),
4868 _(b'cannot abort merge with %s in progress') % (state._opname),
4869 hint=state.hint(),
4869 hint=state.hint(),
4870 )
4870 )
4871 if node:
4871 if node:
4872 raise error.Abort(_(b"cannot specify a node with --abort"))
4872 raise error.Abort(_(b"cannot specify a node with --abort"))
4873 return hg.abortmerge(repo.ui, repo)
4873 return hg.abortmerge(repo.ui, repo)
4874
4874
4875 if opts.get(b'rev') and node:
4875 if opts.get(b'rev') and node:
4876 raise error.Abort(_(b"please specify just one revision"))
4876 raise error.Abort(_(b"please specify just one revision"))
4877 if not node:
4877 if not node:
4878 node = opts.get(b'rev')
4878 node = opts.get(b'rev')
4879
4879
4880 if node:
4880 if node:
4881 ctx = scmutil.revsingle(repo, node)
4881 ctx = scmutil.revsingle(repo, node)
4882 else:
4882 else:
4883 if ui.configbool(b'commands', b'merge.require-rev'):
4883 if ui.configbool(b'commands', b'merge.require-rev'):
4884 raise error.Abort(
4884 raise error.Abort(
4885 _(
4885 _(
4886 b'configuration requires specifying revision to merge '
4886 b'configuration requires specifying revision to merge '
4887 b'with'
4887 b'with'
4888 )
4888 )
4889 )
4889 )
4890 ctx = repo[destutil.destmerge(repo)]
4890 ctx = repo[destutil.destmerge(repo)]
4891
4891
4892 if ctx.node() is None:
4892 if ctx.node() is None:
4893 raise error.Abort(_(b'merging with the working copy has no effect'))
4893 raise error.Abort(_(b'merging with the working copy has no effect'))
4894
4894
4895 if opts.get(b'preview'):
4895 if opts.get(b'preview'):
4896 # find nodes that are ancestors of p2 but not of p1
4896 # find nodes that are ancestors of p2 but not of p1
4897 p1 = repo[b'.'].node()
4897 p1 = repo[b'.'].node()
4898 p2 = ctx.node()
4898 p2 = ctx.node()
4899 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4899 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4900
4900
4901 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4901 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4902 for node in nodes:
4902 for node in nodes:
4903 displayer.show(repo[node])
4903 displayer.show(repo[node])
4904 displayer.close()
4904 displayer.close()
4905 return 0
4905 return 0
4906
4906
4907 # ui.forcemerge is an internal variable, do not document
4907 # ui.forcemerge is an internal variable, do not document
4908 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4908 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4909 with ui.configoverride(overrides, b'merge'):
4909 with ui.configoverride(overrides, b'merge'):
4910 force = opts.get(b'force')
4910 force = opts.get(b'force')
4911 labels = [b'working copy', b'merge rev']
4911 labels = [b'working copy', b'merge rev']
4912 return hg.merge(ctx, force=force, labels=labels)
4912 return hg.merge(ctx, force=force, labels=labels)
4913
4913
4914
4914
4915 statemod.addunfinished(
4915 statemod.addunfinished(
4916 b'merge',
4916 b'merge',
4917 fname=None,
4917 fname=None,
4918 clearable=True,
4918 clearable=True,
4919 allowcommit=True,
4919 allowcommit=True,
4920 cmdmsg=_(b'outstanding uncommitted merge'),
4920 cmdmsg=_(b'outstanding uncommitted merge'),
4921 abortfunc=hg.abortmerge,
4921 abortfunc=hg.abortmerge,
4922 statushint=_(
4922 statushint=_(
4923 b'To continue: hg commit\nTo abort: hg merge --abort'
4923 b'To continue: hg commit\nTo abort: hg merge --abort'
4924 ),
4924 ),
4925 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4925 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4926 )
4926 )
4927
4927
4928
4928
4929 @command(
4929 @command(
4930 b'outgoing|out',
4930 b'outgoing|out',
4931 [
4931 [
4932 (
4932 (
4933 b'f',
4933 b'f',
4934 b'force',
4934 b'force',
4935 None,
4935 None,
4936 _(b'run even when the destination is unrelated'),
4936 _(b'run even when the destination is unrelated'),
4937 ),
4937 ),
4938 (
4938 (
4939 b'r',
4939 b'r',
4940 b'rev',
4940 b'rev',
4941 [],
4941 [],
4942 _(b'a changeset intended to be included in the destination'),
4942 _(b'a changeset intended to be included in the destination'),
4943 _(b'REV'),
4943 _(b'REV'),
4944 ),
4944 ),
4945 (b'n', b'newest-first', None, _(b'show newest record first')),
4945 (b'n', b'newest-first', None, _(b'show newest record first')),
4946 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4946 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4947 (
4947 (
4948 b'b',
4948 b'b',
4949 b'branch',
4949 b'branch',
4950 [],
4950 [],
4951 _(b'a specific branch you would like to push'),
4951 _(b'a specific branch you would like to push'),
4952 _(b'BRANCH'),
4952 _(b'BRANCH'),
4953 ),
4953 ),
4954 ]
4954 ]
4955 + logopts
4955 + logopts
4956 + remoteopts
4956 + remoteopts
4957 + subrepoopts,
4957 + subrepoopts,
4958 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4958 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4959 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4959 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4960 )
4960 )
4961 def outgoing(ui, repo, dest=None, **opts):
4961 def outgoing(ui, repo, dest=None, **opts):
4962 """show changesets not found in the destination
4962 """show changesets not found in the destination
4963
4963
4964 Show changesets not found in the specified destination repository
4964 Show changesets not found in the specified destination repository
4965 or the default push location. These are the changesets that would
4965 or the default push location. These are the changesets that would
4966 be pushed if a push was requested.
4966 be pushed if a push was requested.
4967
4967
4968 See pull for details of valid destination formats.
4968 See pull for details of valid destination formats.
4969
4969
4970 .. container:: verbose
4970 .. container:: verbose
4971
4971
4972 With -B/--bookmarks, the result of bookmark comparison between
4972 With -B/--bookmarks, the result of bookmark comparison between
4973 local and remote repositories is displayed. With -v/--verbose,
4973 local and remote repositories is displayed. With -v/--verbose,
4974 status is also displayed for each bookmark like below::
4974 status is also displayed for each bookmark like below::
4975
4975
4976 BM1 01234567890a added
4976 BM1 01234567890a added
4977 BM2 deleted
4977 BM2 deleted
4978 BM3 234567890abc advanced
4978 BM3 234567890abc advanced
4979 BM4 34567890abcd diverged
4979 BM4 34567890abcd diverged
4980 BM5 4567890abcde changed
4980 BM5 4567890abcde changed
4981
4981
4982 The action taken when pushing depends on the
4982 The action taken when pushing depends on the
4983 status of each bookmark:
4983 status of each bookmark:
4984
4984
4985 :``added``: push with ``-B`` will create it
4985 :``added``: push with ``-B`` will create it
4986 :``deleted``: push with ``-B`` will delete it
4986 :``deleted``: push with ``-B`` will delete it
4987 :``advanced``: push will update it
4987 :``advanced``: push will update it
4988 :``diverged``: push with ``-B`` will update it
4988 :``diverged``: push with ``-B`` will update it
4989 :``changed``: push with ``-B`` will update it
4989 :``changed``: push with ``-B`` will update it
4990
4990
4991 From the point of view of pushing behavior, bookmarks
4991 From the point of view of pushing behavior, bookmarks
4992 existing only in the remote repository are treated as
4992 existing only in the remote repository are treated as
4993 ``deleted``, even if it is in fact added remotely.
4993 ``deleted``, even if it is in fact added remotely.
4994
4994
4995 Returns 0 if there are outgoing changes, 1 otherwise.
4995 Returns 0 if there are outgoing changes, 1 otherwise.
4996 """
4996 """
4997 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4997 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4998 # style URLs, so don't overwrite dest.
4998 # style URLs, so don't overwrite dest.
4999 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4999 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5000 if not path:
5000 if not path:
5001 raise error.Abort(
5001 raise error.Abort(
5002 _(b'default repository not configured!'),
5002 _(b'default repository not configured!'),
5003 hint=_(b"see 'hg help config.paths'"),
5003 hint=_(b"see 'hg help config.paths'"),
5004 )
5004 )
5005
5005
5006 opts = pycompat.byteskwargs(opts)
5006 opts = pycompat.byteskwargs(opts)
5007 if opts.get(b'graph'):
5007 if opts.get(b'graph'):
5008 logcmdutil.checkunsupportedgraphflags([], opts)
5008 logcmdutil.checkunsupportedgraphflags([], opts)
5009 o, other = hg._outgoing(ui, repo, dest, opts)
5009 o, other = hg._outgoing(ui, repo, dest, opts)
5010 if not o:
5010 if not o:
5011 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5011 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5012 return
5012 return
5013
5013
5014 revdag = logcmdutil.graphrevs(repo, o, opts)
5014 revdag = logcmdutil.graphrevs(repo, o, opts)
5015 ui.pager(b'outgoing')
5015 ui.pager(b'outgoing')
5016 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5016 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5017 logcmdutil.displaygraph(
5017 logcmdutil.displaygraph(
5018 ui, repo, revdag, displayer, graphmod.asciiedges
5018 ui, repo, revdag, displayer, graphmod.asciiedges
5019 )
5019 )
5020 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5020 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5021 return 0
5021 return 0
5022
5022
5023 if opts.get(b'bookmarks'):
5023 if opts.get(b'bookmarks'):
5024 dest = path.pushloc or path.loc
5024 dest = path.pushloc or path.loc
5025 other = hg.peer(repo, opts, dest)
5025 other = hg.peer(repo, opts, dest)
5026 if b'bookmarks' not in other.listkeys(b'namespaces'):
5026 if b'bookmarks' not in other.listkeys(b'namespaces'):
5027 ui.warn(_(b"remote doesn't support bookmarks\n"))
5027 ui.warn(_(b"remote doesn't support bookmarks\n"))
5028 return 0
5028 return 0
5029 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5029 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5030 ui.pager(b'outgoing')
5030 ui.pager(b'outgoing')
5031 return bookmarks.outgoing(ui, repo, other)
5031 return bookmarks.outgoing(ui, repo, other)
5032
5032
5033 repo._subtoppath = path.pushloc or path.loc
5033 repo._subtoppath = path.pushloc or path.loc
5034 try:
5034 try:
5035 return hg.outgoing(ui, repo, dest, opts)
5035 return hg.outgoing(ui, repo, dest, opts)
5036 finally:
5036 finally:
5037 del repo._subtoppath
5037 del repo._subtoppath
5038
5038
5039
5039
5040 @command(
5040 @command(
5041 b'parents',
5041 b'parents',
5042 [
5042 [
5043 (
5043 (
5044 b'r',
5044 b'r',
5045 b'rev',
5045 b'rev',
5046 b'',
5046 b'',
5047 _(b'show parents of the specified revision'),
5047 _(b'show parents of the specified revision'),
5048 _(b'REV'),
5048 _(b'REV'),
5049 ),
5049 ),
5050 ]
5050 ]
5051 + templateopts,
5051 + templateopts,
5052 _(b'[-r REV] [FILE]'),
5052 _(b'[-r REV] [FILE]'),
5053 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5053 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5054 inferrepo=True,
5054 inferrepo=True,
5055 )
5055 )
5056 def parents(ui, repo, file_=None, **opts):
5056 def parents(ui, repo, file_=None, **opts):
5057 """show the parents of the working directory or revision (DEPRECATED)
5057 """show the parents of the working directory or revision (DEPRECATED)
5058
5058
5059 Print the working directory's parent revisions. If a revision is
5059 Print the working directory's parent revisions. If a revision is
5060 given via -r/--rev, the parent of that revision will be printed.
5060 given via -r/--rev, the parent of that revision will be printed.
5061 If a file argument is given, the revision in which the file was
5061 If a file argument is given, the revision in which the file was
5062 last changed (before the working directory revision or the
5062 last changed (before the working directory revision or the
5063 argument to --rev if given) is printed.
5063 argument to --rev if given) is printed.
5064
5064
5065 This command is equivalent to::
5065 This command is equivalent to::
5066
5066
5067 hg log -r "p1()+p2()" or
5067 hg log -r "p1()+p2()" or
5068 hg log -r "p1(REV)+p2(REV)" or
5068 hg log -r "p1(REV)+p2(REV)" or
5069 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5069 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5070 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5070 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5071
5071
5072 See :hg:`summary` and :hg:`help revsets` for related information.
5072 See :hg:`summary` and :hg:`help revsets` for related information.
5073
5073
5074 Returns 0 on success.
5074 Returns 0 on success.
5075 """
5075 """
5076
5076
5077 opts = pycompat.byteskwargs(opts)
5077 opts = pycompat.byteskwargs(opts)
5078 rev = opts.get(b'rev')
5078 rev = opts.get(b'rev')
5079 if rev:
5079 if rev:
5080 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5080 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5081 ctx = scmutil.revsingle(repo, rev, None)
5081 ctx = scmutil.revsingle(repo, rev, None)
5082
5082
5083 if file_:
5083 if file_:
5084 m = scmutil.match(ctx, (file_,), opts)
5084 m = scmutil.match(ctx, (file_,), opts)
5085 if m.anypats() or len(m.files()) != 1:
5085 if m.anypats() or len(m.files()) != 1:
5086 raise error.Abort(_(b'can only specify an explicit filename'))
5086 raise error.Abort(_(b'can only specify an explicit filename'))
5087 file_ = m.files()[0]
5087 file_ = m.files()[0]
5088 filenodes = []
5088 filenodes = []
5089 for cp in ctx.parents():
5089 for cp in ctx.parents():
5090 if not cp:
5090 if not cp:
5091 continue
5091 continue
5092 try:
5092 try:
5093 filenodes.append(cp.filenode(file_))
5093 filenodes.append(cp.filenode(file_))
5094 except error.LookupError:
5094 except error.LookupError:
5095 pass
5095 pass
5096 if not filenodes:
5096 if not filenodes:
5097 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5097 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5098 p = []
5098 p = []
5099 for fn in filenodes:
5099 for fn in filenodes:
5100 fctx = repo.filectx(file_, fileid=fn)
5100 fctx = repo.filectx(file_, fileid=fn)
5101 p.append(fctx.node())
5101 p.append(fctx.node())
5102 else:
5102 else:
5103 p = [cp.node() for cp in ctx.parents()]
5103 p = [cp.node() for cp in ctx.parents()]
5104
5104
5105 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5105 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5106 for n in p:
5106 for n in p:
5107 if n != nullid:
5107 if n != nullid:
5108 displayer.show(repo[n])
5108 displayer.show(repo[n])
5109 displayer.close()
5109 displayer.close()
5110
5110
5111
5111
5112 @command(
5112 @command(
5113 b'paths',
5113 b'paths',
5114 formatteropts,
5114 formatteropts,
5115 _(b'[NAME]'),
5115 _(b'[NAME]'),
5116 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5116 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5117 optionalrepo=True,
5117 optionalrepo=True,
5118 intents={INTENT_READONLY},
5118 intents={INTENT_READONLY},
5119 )
5119 )
5120 def paths(ui, repo, search=None, **opts):
5120 def paths(ui, repo, search=None, **opts):
5121 """show aliases for remote repositories
5121 """show aliases for remote repositories
5122
5122
5123 Show definition of symbolic path name NAME. If no name is given,
5123 Show definition of symbolic path name NAME. If no name is given,
5124 show definition of all available names.
5124 show definition of all available names.
5125
5125
5126 Option -q/--quiet suppresses all output when searching for NAME
5126 Option -q/--quiet suppresses all output when searching for NAME
5127 and shows only the path names when listing all definitions.
5127 and shows only the path names when listing all definitions.
5128
5128
5129 Path names are defined in the [paths] section of your
5129 Path names are defined in the [paths] section of your
5130 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5130 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5131 repository, ``.hg/hgrc`` is used, too.
5131 repository, ``.hg/hgrc`` is used, too.
5132
5132
5133 The path names ``default`` and ``default-push`` have a special
5133 The path names ``default`` and ``default-push`` have a special
5134 meaning. When performing a push or pull operation, they are used
5134 meaning. When performing a push or pull operation, they are used
5135 as fallbacks if no location is specified on the command-line.
5135 as fallbacks if no location is specified on the command-line.
5136 When ``default-push`` is set, it will be used for push and
5136 When ``default-push`` is set, it will be used for push and
5137 ``default`` will be used for pull; otherwise ``default`` is used
5137 ``default`` will be used for pull; otherwise ``default`` is used
5138 as the fallback for both. When cloning a repository, the clone
5138 as the fallback for both. When cloning a repository, the clone
5139 source is written as ``default`` in ``.hg/hgrc``.
5139 source is written as ``default`` in ``.hg/hgrc``.
5140
5140
5141 .. note::
5141 .. note::
5142
5142
5143 ``default`` and ``default-push`` apply to all inbound (e.g.
5143 ``default`` and ``default-push`` apply to all inbound (e.g.
5144 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5144 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5145 and :hg:`bundle`) operations.
5145 and :hg:`bundle`) operations.
5146
5146
5147 See :hg:`help urls` for more information.
5147 See :hg:`help urls` for more information.
5148
5148
5149 .. container:: verbose
5149 .. container:: verbose
5150
5150
5151 Template:
5151 Template:
5152
5152
5153 The following keywords are supported. See also :hg:`help templates`.
5153 The following keywords are supported. See also :hg:`help templates`.
5154
5154
5155 :name: String. Symbolic name of the path alias.
5155 :name: String. Symbolic name of the path alias.
5156 :pushurl: String. URL for push operations.
5156 :pushurl: String. URL for push operations.
5157 :url: String. URL or directory path for the other operations.
5157 :url: String. URL or directory path for the other operations.
5158
5158
5159 Returns 0 on success.
5159 Returns 0 on success.
5160 """
5160 """
5161
5161
5162 opts = pycompat.byteskwargs(opts)
5162 opts = pycompat.byteskwargs(opts)
5163 ui.pager(b'paths')
5163 ui.pager(b'paths')
5164 if search:
5164 if search:
5165 pathitems = [
5165 pathitems = [
5166 (name, path)
5166 (name, path)
5167 for name, path in pycompat.iteritems(ui.paths)
5167 for name, path in pycompat.iteritems(ui.paths)
5168 if name == search
5168 if name == search
5169 ]
5169 ]
5170 else:
5170 else:
5171 pathitems = sorted(pycompat.iteritems(ui.paths))
5171 pathitems = sorted(pycompat.iteritems(ui.paths))
5172
5172
5173 fm = ui.formatter(b'paths', opts)
5173 fm = ui.formatter(b'paths', opts)
5174 if fm.isplain():
5174 if fm.isplain():
5175 hidepassword = util.hidepassword
5175 hidepassword = util.hidepassword
5176 else:
5176 else:
5177 hidepassword = bytes
5177 hidepassword = bytes
5178 if ui.quiet:
5178 if ui.quiet:
5179 namefmt = b'%s\n'
5179 namefmt = b'%s\n'
5180 else:
5180 else:
5181 namefmt = b'%s = '
5181 namefmt = b'%s = '
5182 showsubopts = not search and not ui.quiet
5182 showsubopts = not search and not ui.quiet
5183
5183
5184 for name, path in pathitems:
5184 for name, path in pathitems:
5185 fm.startitem()
5185 fm.startitem()
5186 fm.condwrite(not search, b'name', namefmt, name)
5186 fm.condwrite(not search, b'name', namefmt, name)
5187 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5187 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5188 for subopt, value in sorted(path.suboptions.items()):
5188 for subopt, value in sorted(path.suboptions.items()):
5189 assert subopt not in (b'name', b'url')
5189 assert subopt not in (b'name', b'url')
5190 if showsubopts:
5190 if showsubopts:
5191 fm.plain(b'%s:%s = ' % (name, subopt))
5191 fm.plain(b'%s:%s = ' % (name, subopt))
5192 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5192 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5193
5193
5194 fm.end()
5194 fm.end()
5195
5195
5196 if search and not pathitems:
5196 if search and not pathitems:
5197 if not ui.quiet:
5197 if not ui.quiet:
5198 ui.warn(_(b"not found!\n"))
5198 ui.warn(_(b"not found!\n"))
5199 return 1
5199 return 1
5200 else:
5200 else:
5201 return 0
5201 return 0
5202
5202
5203
5203
5204 @command(
5204 @command(
5205 b'phase',
5205 b'phase',
5206 [
5206 [
5207 (b'p', b'public', False, _(b'set changeset phase to public')),
5207 (b'p', b'public', False, _(b'set changeset phase to public')),
5208 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5208 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5209 (b's', b'secret', False, _(b'set changeset phase to secret')),
5209 (b's', b'secret', False, _(b'set changeset phase to secret')),
5210 (b'f', b'force', False, _(b'allow to move boundary backward')),
5210 (b'f', b'force', False, _(b'allow to move boundary backward')),
5211 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5211 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5212 ],
5212 ],
5213 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5213 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5214 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5214 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5215 )
5215 )
5216 def phase(ui, repo, *revs, **opts):
5216 def phase(ui, repo, *revs, **opts):
5217 """set or show the current phase name
5217 """set or show the current phase name
5218
5218
5219 With no argument, show the phase name of the current revision(s).
5219 With no argument, show the phase name of the current revision(s).
5220
5220
5221 With one of -p/--public, -d/--draft or -s/--secret, change the
5221 With one of -p/--public, -d/--draft or -s/--secret, change the
5222 phase value of the specified revisions.
5222 phase value of the specified revisions.
5223
5223
5224 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5224 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5225 lower phase to a higher phase. Phases are ordered as follows::
5225 lower phase to a higher phase. Phases are ordered as follows::
5226
5226
5227 public < draft < secret
5227 public < draft < secret
5228
5228
5229 Returns 0 on success, 1 if some phases could not be changed.
5229 Returns 0 on success, 1 if some phases could not be changed.
5230
5230
5231 (For more information about the phases concept, see :hg:`help phases`.)
5231 (For more information about the phases concept, see :hg:`help phases`.)
5232 """
5232 """
5233 opts = pycompat.byteskwargs(opts)
5233 opts = pycompat.byteskwargs(opts)
5234 # search for a unique phase argument
5234 # search for a unique phase argument
5235 targetphase = None
5235 targetphase = None
5236 for idx, name in enumerate(phases.cmdphasenames):
5236 for idx, name in enumerate(phases.cmdphasenames):
5237 if opts[name]:
5237 if opts[name]:
5238 if targetphase is not None:
5238 if targetphase is not None:
5239 raise error.Abort(_(b'only one phase can be specified'))
5239 raise error.Abort(_(b'only one phase can be specified'))
5240 targetphase = idx
5240 targetphase = idx
5241
5241
5242 # look for specified revision
5242 # look for specified revision
5243 revs = list(revs)
5243 revs = list(revs)
5244 revs.extend(opts[b'rev'])
5244 revs.extend(opts[b'rev'])
5245 if not revs:
5245 if not revs:
5246 # display both parents as the second parent phase can influence
5246 # display both parents as the second parent phase can influence
5247 # the phase of a merge commit
5247 # the phase of a merge commit
5248 revs = [c.rev() for c in repo[None].parents()]
5248 revs = [c.rev() for c in repo[None].parents()]
5249
5249
5250 revs = scmutil.revrange(repo, revs)
5250 revs = scmutil.revrange(repo, revs)
5251
5251
5252 ret = 0
5252 ret = 0
5253 if targetphase is None:
5253 if targetphase is None:
5254 # display
5254 # display
5255 for r in revs:
5255 for r in revs:
5256 ctx = repo[r]
5256 ctx = repo[r]
5257 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5257 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5258 else:
5258 else:
5259 with repo.lock(), repo.transaction(b"phase") as tr:
5259 with repo.lock(), repo.transaction(b"phase") as tr:
5260 # set phase
5260 # set phase
5261 if not revs:
5261 if not revs:
5262 raise error.Abort(_(b'empty revision set'))
5262 raise error.Abort(_(b'empty revision set'))
5263 nodes = [repo[r].node() for r in revs]
5263 nodes = [repo[r].node() for r in revs]
5264 # moving revision from public to draft may hide them
5264 # moving revision from public to draft may hide them
5265 # We have to check result on an unfiltered repository
5265 # We have to check result on an unfiltered repository
5266 unfi = repo.unfiltered()
5266 unfi = repo.unfiltered()
5267 getphase = unfi._phasecache.phase
5267 getphase = unfi._phasecache.phase
5268 olddata = [getphase(unfi, r) for r in unfi]
5268 olddata = [getphase(unfi, r) for r in unfi]
5269 phases.advanceboundary(repo, tr, targetphase, nodes)
5269 phases.advanceboundary(repo, tr, targetphase, nodes)
5270 if opts[b'force']:
5270 if opts[b'force']:
5271 phases.retractboundary(repo, tr, targetphase, nodes)
5271 phases.retractboundary(repo, tr, targetphase, nodes)
5272 getphase = unfi._phasecache.phase
5272 getphase = unfi._phasecache.phase
5273 newdata = [getphase(unfi, r) for r in unfi]
5273 newdata = [getphase(unfi, r) for r in unfi]
5274 changes = sum(newdata[r] != olddata[r] for r in unfi)
5274 changes = sum(newdata[r] != olddata[r] for r in unfi)
5275 cl = unfi.changelog
5275 cl = unfi.changelog
5276 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5276 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5277 if rejected:
5277 if rejected:
5278 ui.warn(
5278 ui.warn(
5279 _(
5279 _(
5280 b'cannot move %i changesets to a higher '
5280 b'cannot move %i changesets to a higher '
5281 b'phase, use --force\n'
5281 b'phase, use --force\n'
5282 )
5282 )
5283 % len(rejected)
5283 % len(rejected)
5284 )
5284 )
5285 ret = 1
5285 ret = 1
5286 if changes:
5286 if changes:
5287 msg = _(b'phase changed for %i changesets\n') % changes
5287 msg = _(b'phase changed for %i changesets\n') % changes
5288 if ret:
5288 if ret:
5289 ui.status(msg)
5289 ui.status(msg)
5290 else:
5290 else:
5291 ui.note(msg)
5291 ui.note(msg)
5292 else:
5292 else:
5293 ui.warn(_(b'no phases changed\n'))
5293 ui.warn(_(b'no phases changed\n'))
5294 return ret
5294 return ret
5295
5295
5296
5296
5297 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5297 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5298 """Run after a changegroup has been added via pull/unbundle
5298 """Run after a changegroup has been added via pull/unbundle
5299
5299
5300 This takes arguments below:
5300 This takes arguments below:
5301
5301
5302 :modheads: change of heads by pull/unbundle
5302 :modheads: change of heads by pull/unbundle
5303 :optupdate: updating working directory is needed or not
5303 :optupdate: updating working directory is needed or not
5304 :checkout: update destination revision (or None to default destination)
5304 :checkout: update destination revision (or None to default destination)
5305 :brev: a name, which might be a bookmark to be activated after updating
5305 :brev: a name, which might be a bookmark to be activated after updating
5306 """
5306 """
5307 if modheads == 0:
5307 if modheads == 0:
5308 return
5308 return
5309 if optupdate:
5309 if optupdate:
5310 try:
5310 try:
5311 return hg.updatetotally(ui, repo, checkout, brev)
5311 return hg.updatetotally(ui, repo, checkout, brev)
5312 except error.UpdateAbort as inst:
5312 except error.UpdateAbort as inst:
5313 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5313 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5314 hint = inst.hint
5314 hint = inst.hint
5315 raise error.UpdateAbort(msg, hint=hint)
5315 raise error.UpdateAbort(msg, hint=hint)
5316 if modheads is not None and modheads > 1:
5316 if modheads is not None and modheads > 1:
5317 currentbranchheads = len(repo.branchheads())
5317 currentbranchheads = len(repo.branchheads())
5318 if currentbranchheads == modheads:
5318 if currentbranchheads == modheads:
5319 ui.status(
5319 ui.status(
5320 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5320 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5321 )
5321 )
5322 elif currentbranchheads > 1:
5322 elif currentbranchheads > 1:
5323 ui.status(
5323 ui.status(
5324 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5324 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5325 )
5325 )
5326 else:
5326 else:
5327 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5327 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5328 elif not ui.configbool(b'commands', b'update.requiredest'):
5328 elif not ui.configbool(b'commands', b'update.requiredest'):
5329 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5329 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5330
5330
5331
5331
5332 @command(
5332 @command(
5333 b'pull',
5333 b'pull',
5334 [
5334 [
5335 (
5335 (
5336 b'u',
5336 b'u',
5337 b'update',
5337 b'update',
5338 None,
5338 None,
5339 _(b'update to new branch head if new descendants were pulled'),
5339 _(b'update to new branch head if new descendants were pulled'),
5340 ),
5340 ),
5341 (
5341 (
5342 b'f',
5342 b'f',
5343 b'force',
5343 b'force',
5344 None,
5344 None,
5345 _(b'run even when remote repository is unrelated'),
5345 _(b'run even when remote repository is unrelated'),
5346 ),
5346 ),
5347 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5347 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5348 (
5348 (
5349 b'r',
5349 b'r',
5350 b'rev',
5350 b'rev',
5351 [],
5351 [],
5352 _(b'a remote changeset intended to be added'),
5352 _(b'a remote changeset intended to be added'),
5353 _(b'REV'),
5353 _(b'REV'),
5354 ),
5354 ),
5355 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5355 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5356 (
5356 (
5357 b'b',
5357 b'b',
5358 b'branch',
5358 b'branch',
5359 [],
5359 [],
5360 _(b'a specific branch you would like to pull'),
5360 _(b'a specific branch you would like to pull'),
5361 _(b'BRANCH'),
5361 _(b'BRANCH'),
5362 ),
5362 ),
5363 ]
5363 ]
5364 + remoteopts,
5364 + remoteopts,
5365 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5365 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5366 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5366 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5367 helpbasic=True,
5367 helpbasic=True,
5368 )
5368 )
5369 def pull(ui, repo, source=b"default", **opts):
5369 def pull(ui, repo, source=b"default", **opts):
5370 """pull changes from the specified source
5370 """pull changes from the specified source
5371
5371
5372 Pull changes from a remote repository to a local one.
5372 Pull changes from a remote repository to a local one.
5373
5373
5374 This finds all changes from the repository at the specified path
5374 This finds all changes from the repository at the specified path
5375 or URL and adds them to a local repository (the current one unless
5375 or URL and adds them to a local repository (the current one unless
5376 -R is specified). By default, this does not update the copy of the
5376 -R is specified). By default, this does not update the copy of the
5377 project in the working directory.
5377 project in the working directory.
5378
5378
5379 When cloning from servers that support it, Mercurial may fetch
5379 When cloning from servers that support it, Mercurial may fetch
5380 pre-generated data. When this is done, hooks operating on incoming
5380 pre-generated data. When this is done, hooks operating on incoming
5381 changesets and changegroups may fire more than once, once for each
5381 changesets and changegroups may fire more than once, once for each
5382 pre-generated bundle and as well as for any additional remaining
5382 pre-generated bundle and as well as for any additional remaining
5383 data. See :hg:`help -e clonebundles` for more.
5383 data. See :hg:`help -e clonebundles` for more.
5384
5384
5385 Use :hg:`incoming` if you want to see what would have been added
5385 Use :hg:`incoming` if you want to see what would have been added
5386 by a pull at the time you issued this command. If you then decide
5386 by a pull at the time you issued this command. If you then decide
5387 to add those changes to the repository, you should use :hg:`pull
5387 to add those changes to the repository, you should use :hg:`pull
5388 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5388 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5389
5389
5390 If SOURCE is omitted, the 'default' path will be used.
5390 If SOURCE is omitted, the 'default' path will be used.
5391 See :hg:`help urls` for more information.
5391 See :hg:`help urls` for more information.
5392
5392
5393 Specifying bookmark as ``.`` is equivalent to specifying the active
5393 Specifying bookmark as ``.`` is equivalent to specifying the active
5394 bookmark's name.
5394 bookmark's name.
5395
5395
5396 Returns 0 on success, 1 if an update had unresolved files.
5396 Returns 0 on success, 1 if an update had unresolved files.
5397 """
5397 """
5398
5398
5399 opts = pycompat.byteskwargs(opts)
5399 opts = pycompat.byteskwargs(opts)
5400 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5400 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5401 b'update'
5401 b'update'
5402 ):
5402 ):
5403 msg = _(b'update destination required by configuration')
5403 msg = _(b'update destination required by configuration')
5404 hint = _(b'use hg pull followed by hg update DEST')
5404 hint = _(b'use hg pull followed by hg update DEST')
5405 raise error.Abort(msg, hint=hint)
5405 raise error.Abort(msg, hint=hint)
5406
5406
5407 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5407 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5408 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5408 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5409 other = hg.peer(repo, opts, source)
5409 other = hg.peer(repo, opts, source)
5410 try:
5410 try:
5411 revs, checkout = hg.addbranchrevs(
5411 revs, checkout = hg.addbranchrevs(
5412 repo, other, branches, opts.get(b'rev')
5412 repo, other, branches, opts.get(b'rev')
5413 )
5413 )
5414
5414
5415 pullopargs = {}
5415 pullopargs = {}
5416
5416
5417 nodes = None
5417 nodes = None
5418 if opts.get(b'bookmark') or revs:
5418 if opts.get(b'bookmark') or revs:
5419 # The list of bookmark used here is the same used to actually update
5419 # The list of bookmark used here is the same used to actually update
5420 # the bookmark names, to avoid the race from issue 4689 and we do
5420 # the bookmark names, to avoid the race from issue 4689 and we do
5421 # all lookup and bookmark queries in one go so they see the same
5421 # all lookup and bookmark queries in one go so they see the same
5422 # version of the server state (issue 4700).
5422 # version of the server state (issue 4700).
5423 nodes = []
5423 nodes = []
5424 fnodes = []
5424 fnodes = []
5425 revs = revs or []
5425 revs = revs or []
5426 if revs and not other.capable(b'lookup'):
5426 if revs and not other.capable(b'lookup'):
5427 err = _(
5427 err = _(
5428 b"other repository doesn't support revision lookup, "
5428 b"other repository doesn't support revision lookup, "
5429 b"so a rev cannot be specified."
5429 b"so a rev cannot be specified."
5430 )
5430 )
5431 raise error.Abort(err)
5431 raise error.Abort(err)
5432 with other.commandexecutor() as e:
5432 with other.commandexecutor() as e:
5433 fremotebookmarks = e.callcommand(
5433 fremotebookmarks = e.callcommand(
5434 b'listkeys', {b'namespace': b'bookmarks'}
5434 b'listkeys', {b'namespace': b'bookmarks'}
5435 )
5435 )
5436 for r in revs:
5436 for r in revs:
5437 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5437 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5438 remotebookmarks = fremotebookmarks.result()
5438 remotebookmarks = fremotebookmarks.result()
5439 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5439 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5440 pullopargs[b'remotebookmarks'] = remotebookmarks
5440 pullopargs[b'remotebookmarks'] = remotebookmarks
5441 for b in opts.get(b'bookmark', []):
5441 for b in opts.get(b'bookmark', []):
5442 b = repo._bookmarks.expandname(b)
5442 b = repo._bookmarks.expandname(b)
5443 if b not in remotebookmarks:
5443 if b not in remotebookmarks:
5444 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5444 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5445 nodes.append(remotebookmarks[b])
5445 nodes.append(remotebookmarks[b])
5446 for i, rev in enumerate(revs):
5446 for i, rev in enumerate(revs):
5447 node = fnodes[i].result()
5447 node = fnodes[i].result()
5448 nodes.append(node)
5448 nodes.append(node)
5449 if rev == checkout:
5449 if rev == checkout:
5450 checkout = node
5450 checkout = node
5451
5451
5452 wlock = util.nullcontextmanager()
5452 wlock = util.nullcontextmanager()
5453 if opts.get(b'update'):
5453 if opts.get(b'update'):
5454 wlock = repo.wlock()
5454 wlock = repo.wlock()
5455 with wlock:
5455 with wlock:
5456 pullopargs.update(opts.get(b'opargs', {}))
5456 pullopargs.update(opts.get(b'opargs', {}))
5457 modheads = exchange.pull(
5457 modheads = exchange.pull(
5458 repo,
5458 repo,
5459 other,
5459 other,
5460 heads=nodes,
5460 heads=nodes,
5461 force=opts.get(b'force'),
5461 force=opts.get(b'force'),
5462 bookmarks=opts.get(b'bookmark', ()),
5462 bookmarks=opts.get(b'bookmark', ()),
5463 opargs=pullopargs,
5463 opargs=pullopargs,
5464 confirm=opts.get(b'confirm'),
5464 confirm=opts.get(b'confirm'),
5465 ).cgresult
5465 ).cgresult
5466
5466
5467 # brev is a name, which might be a bookmark to be activated at
5467 # brev is a name, which might be a bookmark to be activated at
5468 # the end of the update. In other words, it is an explicit
5468 # the end of the update. In other words, it is an explicit
5469 # destination of the update
5469 # destination of the update
5470 brev = None
5470 brev = None
5471
5471
5472 if checkout:
5472 if checkout:
5473 checkout = repo.unfiltered().changelog.rev(checkout)
5473 checkout = repo.unfiltered().changelog.rev(checkout)
5474
5474
5475 # order below depends on implementation of
5475 # order below depends on implementation of
5476 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5476 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5477 # because 'checkout' is determined without it.
5477 # because 'checkout' is determined without it.
5478 if opts.get(b'rev'):
5478 if opts.get(b'rev'):
5479 brev = opts[b'rev'][0]
5479 brev = opts[b'rev'][0]
5480 elif opts.get(b'branch'):
5480 elif opts.get(b'branch'):
5481 brev = opts[b'branch'][0]
5481 brev = opts[b'branch'][0]
5482 else:
5482 else:
5483 brev = branches[0]
5483 brev = branches[0]
5484 repo._subtoppath = source
5484 repo._subtoppath = source
5485 try:
5485 try:
5486 ret = postincoming(
5486 ret = postincoming(
5487 ui, repo, modheads, opts.get(b'update'), checkout, brev
5487 ui, repo, modheads, opts.get(b'update'), checkout, brev
5488 )
5488 )
5489 except error.FilteredRepoLookupError as exc:
5489 except error.FilteredRepoLookupError as exc:
5490 msg = _(b'cannot update to target: %s') % exc.args[0]
5490 msg = _(b'cannot update to target: %s') % exc.args[0]
5491 exc.args = (msg,) + exc.args[1:]
5491 exc.args = (msg,) + exc.args[1:]
5492 raise
5492 raise
5493 finally:
5493 finally:
5494 del repo._subtoppath
5494 del repo._subtoppath
5495
5495
5496 finally:
5496 finally:
5497 other.close()
5497 other.close()
5498 return ret
5498 return ret
5499
5499
5500
5500
5501 @command(
5501 @command(
5502 b'push',
5502 b'push',
5503 [
5503 [
5504 (b'f', b'force', None, _(b'force push')),
5504 (b'f', b'force', None, _(b'force push')),
5505 (
5505 (
5506 b'r',
5506 b'r',
5507 b'rev',
5507 b'rev',
5508 [],
5508 [],
5509 _(b'a changeset intended to be included in the destination'),
5509 _(b'a changeset intended to be included in the destination'),
5510 _(b'REV'),
5510 _(b'REV'),
5511 ),
5511 ),
5512 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5512 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5513 (
5513 (
5514 b'b',
5514 b'b',
5515 b'branch',
5515 b'branch',
5516 [],
5516 [],
5517 _(b'a specific branch you would like to push'),
5517 _(b'a specific branch you would like to push'),
5518 _(b'BRANCH'),
5518 _(b'BRANCH'),
5519 ),
5519 ),
5520 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5520 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5521 (
5521 (
5522 b'',
5522 b'',
5523 b'pushvars',
5523 b'pushvars',
5524 [],
5524 [],
5525 _(b'variables that can be sent to server (ADVANCED)'),
5525 _(b'variables that can be sent to server (ADVANCED)'),
5526 ),
5526 ),
5527 (
5527 (
5528 b'',
5528 b'',
5529 b'publish',
5529 b'publish',
5530 False,
5530 False,
5531 _(b'push the changeset as public (EXPERIMENTAL)'),
5531 _(b'push the changeset as public (EXPERIMENTAL)'),
5532 ),
5532 ),
5533 ]
5533 ]
5534 + remoteopts,
5534 + remoteopts,
5535 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5535 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5536 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5536 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5537 helpbasic=True,
5537 helpbasic=True,
5538 )
5538 )
5539 def push(ui, repo, dest=None, **opts):
5539 def push(ui, repo, dest=None, **opts):
5540 """push changes to the specified destination
5540 """push changes to the specified destination
5541
5541
5542 Push changesets from the local repository to the specified
5542 Push changesets from the local repository to the specified
5543 destination.
5543 destination.
5544
5544
5545 This operation is symmetrical to pull: it is identical to a pull
5545 This operation is symmetrical to pull: it is identical to a pull
5546 in the destination repository from the current one.
5546 in the destination repository from the current one.
5547
5547
5548 By default, push will not allow creation of new heads at the
5548 By default, push will not allow creation of new heads at the
5549 destination, since multiple heads would make it unclear which head
5549 destination, since multiple heads would make it unclear which head
5550 to use. In this situation, it is recommended to pull and merge
5550 to use. In this situation, it is recommended to pull and merge
5551 before pushing.
5551 before pushing.
5552
5552
5553 Use --new-branch if you want to allow push to create a new named
5553 Use --new-branch if you want to allow push to create a new named
5554 branch that is not present at the destination. This allows you to
5554 branch that is not present at the destination. This allows you to
5555 only create a new branch without forcing other changes.
5555 only create a new branch without forcing other changes.
5556
5556
5557 .. note::
5557 .. note::
5558
5558
5559 Extra care should be taken with the -f/--force option,
5559 Extra care should be taken with the -f/--force option,
5560 which will push all new heads on all branches, an action which will
5560 which will push all new heads on all branches, an action which will
5561 almost always cause confusion for collaborators.
5561 almost always cause confusion for collaborators.
5562
5562
5563 If -r/--rev is used, the specified revision and all its ancestors
5563 If -r/--rev is used, the specified revision and all its ancestors
5564 will be pushed to the remote repository.
5564 will be pushed to the remote repository.
5565
5565
5566 If -B/--bookmark is used, the specified bookmarked revision, its
5566 If -B/--bookmark is used, the specified bookmarked revision, its
5567 ancestors, and the bookmark will be pushed to the remote
5567 ancestors, and the bookmark will be pushed to the remote
5568 repository. Specifying ``.`` is equivalent to specifying the active
5568 repository. Specifying ``.`` is equivalent to specifying the active
5569 bookmark's name.
5569 bookmark's name.
5570
5570
5571 Please see :hg:`help urls` for important details about ``ssh://``
5571 Please see :hg:`help urls` for important details about ``ssh://``
5572 URLs. If DESTINATION is omitted, a default path will be used.
5572 URLs. If DESTINATION is omitted, a default path will be used.
5573
5573
5574 .. container:: verbose
5574 .. container:: verbose
5575
5575
5576 The --pushvars option sends strings to the server that become
5576 The --pushvars option sends strings to the server that become
5577 environment variables prepended with ``HG_USERVAR_``. For example,
5577 environment variables prepended with ``HG_USERVAR_``. For example,
5578 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5578 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5579 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5579 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5580
5580
5581 pushvars can provide for user-overridable hooks as well as set debug
5581 pushvars can provide for user-overridable hooks as well as set debug
5582 levels. One example is having a hook that blocks commits containing
5582 levels. One example is having a hook that blocks commits containing
5583 conflict markers, but enables the user to override the hook if the file
5583 conflict markers, but enables the user to override the hook if the file
5584 is using conflict markers for testing purposes or the file format has
5584 is using conflict markers for testing purposes or the file format has
5585 strings that look like conflict markers.
5585 strings that look like conflict markers.
5586
5586
5587 By default, servers will ignore `--pushvars`. To enable it add the
5587 By default, servers will ignore `--pushvars`. To enable it add the
5588 following to your configuration file::
5588 following to your configuration file::
5589
5589
5590 [push]
5590 [push]
5591 pushvars.server = true
5591 pushvars.server = true
5592
5592
5593 Returns 0 if push was successful, 1 if nothing to push.
5593 Returns 0 if push was successful, 1 if nothing to push.
5594 """
5594 """
5595
5595
5596 opts = pycompat.byteskwargs(opts)
5596 opts = pycompat.byteskwargs(opts)
5597 if opts.get(b'bookmark'):
5597 if opts.get(b'bookmark'):
5598 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5598 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5599 for b in opts[b'bookmark']:
5599 for b in opts[b'bookmark']:
5600 # translate -B options to -r so changesets get pushed
5600 # translate -B options to -r so changesets get pushed
5601 b = repo._bookmarks.expandname(b)
5601 b = repo._bookmarks.expandname(b)
5602 if b in repo._bookmarks:
5602 if b in repo._bookmarks:
5603 opts.setdefault(b'rev', []).append(b)
5603 opts.setdefault(b'rev', []).append(b)
5604 else:
5604 else:
5605 # if we try to push a deleted bookmark, translate it to null
5605 # if we try to push a deleted bookmark, translate it to null
5606 # this lets simultaneous -r, -b options continue working
5606 # this lets simultaneous -r, -b options continue working
5607 opts.setdefault(b'rev', []).append(b"null")
5607 opts.setdefault(b'rev', []).append(b"null")
5608
5608
5609 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5609 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5610 if not path:
5610 if not path:
5611 raise error.Abort(
5611 raise error.Abort(
5612 _(b'default repository not configured!'),
5612 _(b'default repository not configured!'),
5613 hint=_(b"see 'hg help config.paths'"),
5613 hint=_(b"see 'hg help config.paths'"),
5614 )
5614 )
5615 dest = path.pushloc or path.loc
5615 dest = path.pushloc or path.loc
5616 branches = (path.branch, opts.get(b'branch') or [])
5616 branches = (path.branch, opts.get(b'branch') or [])
5617 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5617 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5618 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5618 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5619 other = hg.peer(repo, opts, dest)
5619 other = hg.peer(repo, opts, dest)
5620
5620
5621 if revs:
5621 if revs:
5622 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5622 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5623 if not revs:
5623 if not revs:
5624 raise error.Abort(
5624 raise error.Abort(
5625 _(b"specified revisions evaluate to an empty set"),
5625 _(b"specified revisions evaluate to an empty set"),
5626 hint=_(b"use different revision arguments"),
5626 hint=_(b"use different revision arguments"),
5627 )
5627 )
5628 elif path.pushrev:
5628 elif path.pushrev:
5629 # It doesn't make any sense to specify ancestor revisions. So limit
5629 # It doesn't make any sense to specify ancestor revisions. So limit
5630 # to DAG heads to make discovery simpler.
5630 # to DAG heads to make discovery simpler.
5631 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5631 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5632 revs = scmutil.revrange(repo, [expr])
5632 revs = scmutil.revrange(repo, [expr])
5633 revs = [repo[rev].node() for rev in revs]
5633 revs = [repo[rev].node() for rev in revs]
5634 if not revs:
5634 if not revs:
5635 raise error.Abort(
5635 raise error.Abort(
5636 _(b'default push revset for path evaluates to an empty set')
5636 _(b'default push revset for path evaluates to an empty set')
5637 )
5637 )
5638 elif ui.configbool(b'commands', b'push.require-revs'):
5638 elif ui.configbool(b'commands', b'push.require-revs'):
5639 raise error.Abort(
5639 raise error.Abort(
5640 _(b'no revisions specified to push'),
5640 _(b'no revisions specified to push'),
5641 hint=_(b'did you mean "hg push -r ."?'),
5641 hint=_(b'did you mean "hg push -r ."?'),
5642 )
5642 )
5643
5643
5644 repo._subtoppath = dest
5644 repo._subtoppath = dest
5645 try:
5645 try:
5646 # push subrepos depth-first for coherent ordering
5646 # push subrepos depth-first for coherent ordering
5647 c = repo[b'.']
5647 c = repo[b'.']
5648 subs = c.substate # only repos that are committed
5648 subs = c.substate # only repos that are committed
5649 for s in sorted(subs):
5649 for s in sorted(subs):
5650 result = c.sub(s).push(opts)
5650 result = c.sub(s).push(opts)
5651 if result == 0:
5651 if result == 0:
5652 return not result
5652 return not result
5653 finally:
5653 finally:
5654 del repo._subtoppath
5654 del repo._subtoppath
5655
5655
5656 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5656 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5657 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5657 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5658
5658
5659 pushop = exchange.push(
5659 pushop = exchange.push(
5660 repo,
5660 repo,
5661 other,
5661 other,
5662 opts.get(b'force'),
5662 opts.get(b'force'),
5663 revs=revs,
5663 revs=revs,
5664 newbranch=opts.get(b'new_branch'),
5664 newbranch=opts.get(b'new_branch'),
5665 bookmarks=opts.get(b'bookmark', ()),
5665 bookmarks=opts.get(b'bookmark', ()),
5666 publish=opts.get(b'publish'),
5666 publish=opts.get(b'publish'),
5667 opargs=opargs,
5667 opargs=opargs,
5668 )
5668 )
5669
5669
5670 result = not pushop.cgresult
5670 result = not pushop.cgresult
5671
5671
5672 if pushop.bkresult is not None:
5672 if pushop.bkresult is not None:
5673 if pushop.bkresult == 2:
5673 if pushop.bkresult == 2:
5674 result = 2
5674 result = 2
5675 elif not result and pushop.bkresult:
5675 elif not result and pushop.bkresult:
5676 result = 2
5676 result = 2
5677
5677
5678 return result
5678 return result
5679
5679
5680
5680
5681 @command(
5681 @command(
5682 b'recover',
5682 b'recover',
5683 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5683 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5684 helpcategory=command.CATEGORY_MAINTENANCE,
5684 helpcategory=command.CATEGORY_MAINTENANCE,
5685 )
5685 )
5686 def recover(ui, repo, **opts):
5686 def recover(ui, repo, **opts):
5687 """roll back an interrupted transaction
5687 """roll back an interrupted transaction
5688
5688
5689 Recover from an interrupted commit or pull.
5689 Recover from an interrupted commit or pull.
5690
5690
5691 This command tries to fix the repository status after an
5691 This command tries to fix the repository status after an
5692 interrupted operation. It should only be necessary when Mercurial
5692 interrupted operation. It should only be necessary when Mercurial
5693 suggests it.
5693 suggests it.
5694
5694
5695 Returns 0 if successful, 1 if nothing to recover or verify fails.
5695 Returns 0 if successful, 1 if nothing to recover or verify fails.
5696 """
5696 """
5697 ret = repo.recover()
5697 ret = repo.recover()
5698 if ret:
5698 if ret:
5699 if opts['verify']:
5699 if opts['verify']:
5700 return hg.verify(repo)
5700 return hg.verify(repo)
5701 else:
5701 else:
5702 msg = _(
5702 msg = _(
5703 b"(verify step skipped, run `hg verify` to check your "
5703 b"(verify step skipped, run `hg verify` to check your "
5704 b"repository content)\n"
5704 b"repository content)\n"
5705 )
5705 )
5706 ui.warn(msg)
5706 ui.warn(msg)
5707 return 0
5707 return 0
5708 return 1
5708 return 1
5709
5709
5710
5710
5711 @command(
5711 @command(
5712 b'remove|rm',
5712 b'remove|rm',
5713 [
5713 [
5714 (b'A', b'after', None, _(b'record delete for missing files')),
5714 (b'A', b'after', None, _(b'record delete for missing files')),
5715 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5715 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5716 ]
5716 ]
5717 + subrepoopts
5717 + subrepoopts
5718 + walkopts
5718 + walkopts
5719 + dryrunopts,
5719 + dryrunopts,
5720 _(b'[OPTION]... FILE...'),
5720 _(b'[OPTION]... FILE...'),
5721 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5721 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5722 helpbasic=True,
5722 helpbasic=True,
5723 inferrepo=True,
5723 inferrepo=True,
5724 )
5724 )
5725 def remove(ui, repo, *pats, **opts):
5725 def remove(ui, repo, *pats, **opts):
5726 """remove the specified files on the next commit
5726 """remove the specified files on the next commit
5727
5727
5728 Schedule the indicated files for removal from the current branch.
5728 Schedule the indicated files for removal from the current branch.
5729
5729
5730 This command schedules the files to be removed at the next commit.
5730 This command schedules the files to be removed at the next commit.
5731 To undo a remove before that, see :hg:`revert`. To undo added
5731 To undo a remove before that, see :hg:`revert`. To undo added
5732 files, see :hg:`forget`.
5732 files, see :hg:`forget`.
5733
5733
5734 .. container:: verbose
5734 .. container:: verbose
5735
5735
5736 -A/--after can be used to remove only files that have already
5736 -A/--after can be used to remove only files that have already
5737 been deleted, -f/--force can be used to force deletion, and -Af
5737 been deleted, -f/--force can be used to force deletion, and -Af
5738 can be used to remove files from the next revision without
5738 can be used to remove files from the next revision without
5739 deleting them from the working directory.
5739 deleting them from the working directory.
5740
5740
5741 The following table details the behavior of remove for different
5741 The following table details the behavior of remove for different
5742 file states (columns) and option combinations (rows). The file
5742 file states (columns) and option combinations (rows). The file
5743 states are Added [A], Clean [C], Modified [M] and Missing [!]
5743 states are Added [A], Clean [C], Modified [M] and Missing [!]
5744 (as reported by :hg:`status`). The actions are Warn, Remove
5744 (as reported by :hg:`status`). The actions are Warn, Remove
5745 (from branch) and Delete (from disk):
5745 (from branch) and Delete (from disk):
5746
5746
5747 ========= == == == ==
5747 ========= == == == ==
5748 opt/state A C M !
5748 opt/state A C M !
5749 ========= == == == ==
5749 ========= == == == ==
5750 none W RD W R
5750 none W RD W R
5751 -f R RD RD R
5751 -f R RD RD R
5752 -A W W W R
5752 -A W W W R
5753 -Af R R R R
5753 -Af R R R R
5754 ========= == == == ==
5754 ========= == == == ==
5755
5755
5756 .. note::
5756 .. note::
5757
5757
5758 :hg:`remove` never deletes files in Added [A] state from the
5758 :hg:`remove` never deletes files in Added [A] state from the
5759 working directory, not even if ``--force`` is specified.
5759 working directory, not even if ``--force`` is specified.
5760
5760
5761 Returns 0 on success, 1 if any warnings encountered.
5761 Returns 0 on success, 1 if any warnings encountered.
5762 """
5762 """
5763
5763
5764 opts = pycompat.byteskwargs(opts)
5764 opts = pycompat.byteskwargs(opts)
5765 after, force = opts.get(b'after'), opts.get(b'force')
5765 after, force = opts.get(b'after'), opts.get(b'force')
5766 dryrun = opts.get(b'dry_run')
5766 dryrun = opts.get(b'dry_run')
5767 if not pats and not after:
5767 if not pats and not after:
5768 raise error.Abort(_(b'no files specified'))
5768 raise error.Abort(_(b'no files specified'))
5769
5769
5770 m = scmutil.match(repo[None], pats, opts)
5770 m = scmutil.match(repo[None], pats, opts)
5771 subrepos = opts.get(b'subrepos')
5771 subrepos = opts.get(b'subrepos')
5772 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5772 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5773 return cmdutil.remove(
5773 return cmdutil.remove(
5774 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5774 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5775 )
5775 )
5776
5776
5777
5777
5778 @command(
5778 @command(
5779 b'rename|move|mv',
5779 b'rename|move|mv',
5780 [
5780 [
5781 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5781 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5782 (
5782 (
5783 b'f',
5783 b'f',
5784 b'force',
5784 b'force',
5785 None,
5785 None,
5786 _(b'forcibly move over an existing managed file'),
5786 _(b'forcibly move over an existing managed file'),
5787 ),
5787 ),
5788 ]
5788 ]
5789 + walkopts
5789 + walkopts
5790 + dryrunopts,
5790 + dryrunopts,
5791 _(b'[OPTION]... SOURCE... DEST'),
5791 _(b'[OPTION]... SOURCE... DEST'),
5792 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5792 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5793 )
5793 )
5794 def rename(ui, repo, *pats, **opts):
5794 def rename(ui, repo, *pats, **opts):
5795 """rename files; equivalent of copy + remove
5795 """rename files; equivalent of copy + remove
5796
5796
5797 Mark dest as copies of sources; mark sources for deletion. If dest
5797 Mark dest as copies of sources; mark sources for deletion. If dest
5798 is a directory, copies are put in that directory. If dest is a
5798 is a directory, copies are put in that directory. If dest is a
5799 file, there can only be one source.
5799 file, there can only be one source.
5800
5800
5801 By default, this command copies the contents of files as they
5801 By default, this command copies the contents of files as they
5802 exist in the working directory. If invoked with -A/--after, the
5802 exist in the working directory. If invoked with -A/--after, the
5803 operation is recorded, but no copying is performed.
5803 operation is recorded, but no copying is performed.
5804
5804
5805 This command takes effect at the next commit. To undo a rename
5805 This command takes effect at the next commit. To undo a rename
5806 before that, see :hg:`revert`.
5806 before that, see :hg:`revert`.
5807
5807
5808 Returns 0 on success, 1 if errors are encountered.
5808 Returns 0 on success, 1 if errors are encountered.
5809 """
5809 """
5810 opts = pycompat.byteskwargs(opts)
5810 opts = pycompat.byteskwargs(opts)
5811 with repo.wlock(False):
5811 with repo.wlock(False):
5812 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5812 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5813
5813
5814
5814
5815 @command(
5815 @command(
5816 b'resolve',
5816 b'resolve',
5817 [
5817 [
5818 (b'a', b'all', None, _(b'select all unresolved files')),
5818 (b'a', b'all', None, _(b'select all unresolved files')),
5819 (b'l', b'list', None, _(b'list state of files needing merge')),
5819 (b'l', b'list', None, _(b'list state of files needing merge')),
5820 (b'm', b'mark', None, _(b'mark files as resolved')),
5820 (b'm', b'mark', None, _(b'mark files as resolved')),
5821 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5821 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5822 (b'n', b'no-status', None, _(b'hide status prefix')),
5822 (b'n', b'no-status', None, _(b'hide status prefix')),
5823 (b'', b're-merge', None, _(b're-merge files')),
5823 (b'', b're-merge', None, _(b're-merge files')),
5824 ]
5824 ]
5825 + mergetoolopts
5825 + mergetoolopts
5826 + walkopts
5826 + walkopts
5827 + formatteropts,
5827 + formatteropts,
5828 _(b'[OPTION]... [FILE]...'),
5828 _(b'[OPTION]... [FILE]...'),
5829 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5829 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5830 inferrepo=True,
5830 inferrepo=True,
5831 )
5831 )
5832 def resolve(ui, repo, *pats, **opts):
5832 def resolve(ui, repo, *pats, **opts):
5833 """redo merges or set/view the merge status of files
5833 """redo merges or set/view the merge status of files
5834
5834
5835 Merges with unresolved conflicts are often the result of
5835 Merges with unresolved conflicts are often the result of
5836 non-interactive merging using the ``internal:merge`` configuration
5836 non-interactive merging using the ``internal:merge`` configuration
5837 setting, or a command-line merge tool like ``diff3``. The resolve
5837 setting, or a command-line merge tool like ``diff3``. The resolve
5838 command is used to manage the files involved in a merge, after
5838 command is used to manage the files involved in a merge, after
5839 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5839 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5840 working directory must have two parents). See :hg:`help
5840 working directory must have two parents). See :hg:`help
5841 merge-tools` for information on configuring merge tools.
5841 merge-tools` for information on configuring merge tools.
5842
5842
5843 The resolve command can be used in the following ways:
5843 The resolve command can be used in the following ways:
5844
5844
5845 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5845 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5846 the specified files, discarding any previous merge attempts. Re-merging
5846 the specified files, discarding any previous merge attempts. Re-merging
5847 is not performed for files already marked as resolved. Use ``--all/-a``
5847 is not performed for files already marked as resolved. Use ``--all/-a``
5848 to select all unresolved files. ``--tool`` can be used to specify
5848 to select all unresolved files. ``--tool`` can be used to specify
5849 the merge tool used for the given files. It overrides the HGMERGE
5849 the merge tool used for the given files. It overrides the HGMERGE
5850 environment variable and your configuration files. Previous file
5850 environment variable and your configuration files. Previous file
5851 contents are saved with a ``.orig`` suffix.
5851 contents are saved with a ``.orig`` suffix.
5852
5852
5853 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5853 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5854 (e.g. after having manually fixed-up the files). The default is
5854 (e.g. after having manually fixed-up the files). The default is
5855 to mark all unresolved files.
5855 to mark all unresolved files.
5856
5856
5857 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5857 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5858 default is to mark all resolved files.
5858 default is to mark all resolved files.
5859
5859
5860 - :hg:`resolve -l`: list files which had or still have conflicts.
5860 - :hg:`resolve -l`: list files which had or still have conflicts.
5861 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5861 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5862 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5862 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5863 the list. See :hg:`help filesets` for details.
5863 the list. See :hg:`help filesets` for details.
5864
5864
5865 .. note::
5865 .. note::
5866
5866
5867 Mercurial will not let you commit files with unresolved merge
5867 Mercurial will not let you commit files with unresolved merge
5868 conflicts. You must use :hg:`resolve -m ...` before you can
5868 conflicts. You must use :hg:`resolve -m ...` before you can
5869 commit after a conflicting merge.
5869 commit after a conflicting merge.
5870
5870
5871 .. container:: verbose
5871 .. container:: verbose
5872
5872
5873 Template:
5873 Template:
5874
5874
5875 The following keywords are supported in addition to the common template
5875 The following keywords are supported in addition to the common template
5876 keywords and functions. See also :hg:`help templates`.
5876 keywords and functions. See also :hg:`help templates`.
5877
5877
5878 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5878 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5879 :path: String. Repository-absolute path of the file.
5879 :path: String. Repository-absolute path of the file.
5880
5880
5881 Returns 0 on success, 1 if any files fail a resolve attempt.
5881 Returns 0 on success, 1 if any files fail a resolve attempt.
5882 """
5882 """
5883
5883
5884 opts = pycompat.byteskwargs(opts)
5884 opts = pycompat.byteskwargs(opts)
5885 confirm = ui.configbool(b'commands', b'resolve.confirm')
5885 confirm = ui.configbool(b'commands', b'resolve.confirm')
5886 flaglist = b'all mark unmark list no_status re_merge'.split()
5886 flaglist = b'all mark unmark list no_status re_merge'.split()
5887 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5887 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5888
5888
5889 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5889 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5890 if actioncount > 1:
5890 if actioncount > 1:
5891 raise error.Abort(_(b"too many actions specified"))
5891 raise error.Abort(_(b"too many actions specified"))
5892 elif actioncount == 0 and ui.configbool(
5892 elif actioncount == 0 and ui.configbool(
5893 b'commands', b'resolve.explicit-re-merge'
5893 b'commands', b'resolve.explicit-re-merge'
5894 ):
5894 ):
5895 hint = _(b'use --mark, --unmark, --list or --re-merge')
5895 hint = _(b'use --mark, --unmark, --list or --re-merge')
5896 raise error.Abort(_(b'no action specified'), hint=hint)
5896 raise error.Abort(_(b'no action specified'), hint=hint)
5897 if pats and all:
5897 if pats and all:
5898 raise error.Abort(_(b"can't specify --all and patterns"))
5898 raise error.Abort(_(b"can't specify --all and patterns"))
5899 if not (all or pats or show or mark or unmark):
5899 if not (all or pats or show or mark or unmark):
5900 raise error.Abort(
5900 raise error.Abort(
5901 _(b'no files or directories specified'),
5901 _(b'no files or directories specified'),
5902 hint=b'use --all to re-merge all unresolved files',
5902 hint=b'use --all to re-merge all unresolved files',
5903 )
5903 )
5904
5904
5905 if confirm:
5905 if confirm:
5906 if all:
5906 if all:
5907 if ui.promptchoice(
5907 if ui.promptchoice(
5908 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5908 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5909 ):
5909 ):
5910 raise error.Abort(_(b'user quit'))
5910 raise error.Abort(_(b'user quit'))
5911 if mark and not pats:
5911 if mark and not pats:
5912 if ui.promptchoice(
5912 if ui.promptchoice(
5913 _(
5913 _(
5914 b'mark all unresolved files as resolved (yn)?'
5914 b'mark all unresolved files as resolved (yn)?'
5915 b'$$ &Yes $$ &No'
5915 b'$$ &Yes $$ &No'
5916 )
5916 )
5917 ):
5917 ):
5918 raise error.Abort(_(b'user quit'))
5918 raise error.Abort(_(b'user quit'))
5919 if unmark and not pats:
5919 if unmark and not pats:
5920 if ui.promptchoice(
5920 if ui.promptchoice(
5921 _(
5921 _(
5922 b'mark all resolved files as unresolved (yn)?'
5922 b'mark all resolved files as unresolved (yn)?'
5923 b'$$ &Yes $$ &No'
5923 b'$$ &Yes $$ &No'
5924 )
5924 )
5925 ):
5925 ):
5926 raise error.Abort(_(b'user quit'))
5926 raise error.Abort(_(b'user quit'))
5927
5927
5928 uipathfn = scmutil.getuipathfn(repo)
5928 uipathfn = scmutil.getuipathfn(repo)
5929
5929
5930 if show:
5930 if show:
5931 ui.pager(b'resolve')
5931 ui.pager(b'resolve')
5932 fm = ui.formatter(b'resolve', opts)
5932 fm = ui.formatter(b'resolve', opts)
5933 ms = mergemod.mergestate.read(repo)
5933 ms = mergemod.mergestate.read(repo)
5934 wctx = repo[None]
5934 wctx = repo[None]
5935 m = scmutil.match(wctx, pats, opts)
5935 m = scmutil.match(wctx, pats, opts)
5936
5936
5937 # Labels and keys based on merge state. Unresolved path conflicts show
5937 # Labels and keys based on merge state. Unresolved path conflicts show
5938 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5938 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5939 # resolved conflicts.
5939 # resolved conflicts.
5940 mergestateinfo = {
5940 mergestateinfo = {
5941 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5941 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5942 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5942 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5943 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5943 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5944 b'resolve.unresolved',
5944 b'resolve.unresolved',
5945 b'P',
5945 b'P',
5946 ),
5946 ),
5947 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5947 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5948 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5948 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5949 b'resolve.driverresolved',
5949 b'resolve.driverresolved',
5950 b'D',
5950 b'D',
5951 ),
5951 ),
5952 }
5952 }
5953
5953
5954 for f in ms:
5954 for f in ms:
5955 if not m(f):
5955 if not m(f):
5956 continue
5956 continue
5957
5957
5958 if ms[f] == mergemod.MERGE_RECORD_MERGED_OTHER:
5959 continue
5958 label, key = mergestateinfo[ms[f]]
5960 label, key = mergestateinfo[ms[f]]
5959 fm.startitem()
5961 fm.startitem()
5960 fm.context(ctx=wctx)
5962 fm.context(ctx=wctx)
5961 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5963 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5962 fm.data(path=f)
5964 fm.data(path=f)
5963 fm.plain(b'%s\n' % uipathfn(f), label=label)
5965 fm.plain(b'%s\n' % uipathfn(f), label=label)
5964 fm.end()
5966 fm.end()
5965 return 0
5967 return 0
5966
5968
5967 with repo.wlock():
5969 with repo.wlock():
5968 ms = mergemod.mergestate.read(repo)
5970 ms = mergemod.mergestate.read(repo)
5969
5971
5970 if not (ms.active() or repo.dirstate.p2() != nullid):
5972 if not (ms.active() or repo.dirstate.p2() != nullid):
5971 raise error.Abort(
5973 raise error.Abort(
5972 _(b'resolve command not applicable when not merging')
5974 _(b'resolve command not applicable when not merging')
5973 )
5975 )
5974
5976
5975 wctx = repo[None]
5977 wctx = repo[None]
5976
5978
5977 if (
5979 if (
5978 ms.mergedriver
5980 ms.mergedriver
5979 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5981 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5980 ):
5982 ):
5981 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5983 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5982 ms.commit()
5984 ms.commit()
5983 # allow mark and unmark to go through
5985 # allow mark and unmark to go through
5984 if not mark and not unmark and not proceed:
5986 if not mark and not unmark and not proceed:
5985 return 1
5987 return 1
5986
5988
5987 m = scmutil.match(wctx, pats, opts)
5989 m = scmutil.match(wctx, pats, opts)
5988 ret = 0
5990 ret = 0
5989 didwork = False
5991 didwork = False
5990 runconclude = False
5992 runconclude = False
5991
5993
5992 tocomplete = []
5994 tocomplete = []
5993 hasconflictmarkers = []
5995 hasconflictmarkers = []
5994 if mark:
5996 if mark:
5995 markcheck = ui.config(b'commands', b'resolve.mark-check')
5997 markcheck = ui.config(b'commands', b'resolve.mark-check')
5996 if markcheck not in [b'warn', b'abort']:
5998 if markcheck not in [b'warn', b'abort']:
5997 # Treat all invalid / unrecognized values as 'none'.
5999 # Treat all invalid / unrecognized values as 'none'.
5998 markcheck = False
6000 markcheck = False
5999 for f in ms:
6001 for f in ms:
6000 if not m(f):
6002 if not m(f):
6001 continue
6003 continue
6002
6004
6003 didwork = True
6005 didwork = True
6004
6006
6007 if ms[f] == mergemod.MERGE_RECORD_MERGED_OTHER:
6008 continue
6009
6005 # don't let driver-resolved files be marked, and run the conclude
6010 # don't let driver-resolved files be marked, and run the conclude
6006 # step if asked to resolve
6011 # step if asked to resolve
6007 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6012 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6008 exact = m.exact(f)
6013 exact = m.exact(f)
6009 if mark:
6014 if mark:
6010 if exact:
6015 if exact:
6011 ui.warn(
6016 ui.warn(
6012 _(b'not marking %s as it is driver-resolved\n')
6017 _(b'not marking %s as it is driver-resolved\n')
6013 % uipathfn(f)
6018 % uipathfn(f)
6014 )
6019 )
6015 elif unmark:
6020 elif unmark:
6016 if exact:
6021 if exact:
6017 ui.warn(
6022 ui.warn(
6018 _(b'not unmarking %s as it is driver-resolved\n')
6023 _(b'not unmarking %s as it is driver-resolved\n')
6019 % uipathfn(f)
6024 % uipathfn(f)
6020 )
6025 )
6021 else:
6026 else:
6022 runconclude = True
6027 runconclude = True
6023 continue
6028 continue
6024
6029
6025 # path conflicts must be resolved manually
6030 # path conflicts must be resolved manually
6026 if ms[f] in (
6031 if ms[f] in (
6027 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6032 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6028 mergemod.MERGE_RECORD_RESOLVED_PATH,
6033 mergemod.MERGE_RECORD_RESOLVED_PATH,
6029 ):
6034 ):
6030 if mark:
6035 if mark:
6031 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6036 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6032 elif unmark:
6037 elif unmark:
6033 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6038 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6034 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6039 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6035 ui.warn(
6040 ui.warn(
6036 _(b'%s: path conflict must be resolved manually\n')
6041 _(b'%s: path conflict must be resolved manually\n')
6037 % uipathfn(f)
6042 % uipathfn(f)
6038 )
6043 )
6039 continue
6044 continue
6040
6045
6041 if mark:
6046 if mark:
6042 if markcheck:
6047 if markcheck:
6043 fdata = repo.wvfs.tryread(f)
6048 fdata = repo.wvfs.tryread(f)
6044 if (
6049 if (
6045 filemerge.hasconflictmarkers(fdata)
6050 filemerge.hasconflictmarkers(fdata)
6046 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6051 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6047 ):
6052 ):
6048 hasconflictmarkers.append(f)
6053 hasconflictmarkers.append(f)
6049 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6054 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6050 elif unmark:
6055 elif unmark:
6051 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6056 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6052 else:
6057 else:
6053 # backup pre-resolve (merge uses .orig for its own purposes)
6058 # backup pre-resolve (merge uses .orig for its own purposes)
6054 a = repo.wjoin(f)
6059 a = repo.wjoin(f)
6055 try:
6060 try:
6056 util.copyfile(a, a + b".resolve")
6061 util.copyfile(a, a + b".resolve")
6057 except (IOError, OSError) as inst:
6062 except (IOError, OSError) as inst:
6058 if inst.errno != errno.ENOENT:
6063 if inst.errno != errno.ENOENT:
6059 raise
6064 raise
6060
6065
6061 try:
6066 try:
6062 # preresolve file
6067 # preresolve file
6063 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6068 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6064 with ui.configoverride(overrides, b'resolve'):
6069 with ui.configoverride(overrides, b'resolve'):
6065 complete, r = ms.preresolve(f, wctx)
6070 complete, r = ms.preresolve(f, wctx)
6066 if not complete:
6071 if not complete:
6067 tocomplete.append(f)
6072 tocomplete.append(f)
6068 elif r:
6073 elif r:
6069 ret = 1
6074 ret = 1
6070 finally:
6075 finally:
6071 ms.commit()
6076 ms.commit()
6072
6077
6073 # replace filemerge's .orig file with our resolve file, but only
6078 # replace filemerge's .orig file with our resolve file, but only
6074 # for merges that are complete
6079 # for merges that are complete
6075 if complete:
6080 if complete:
6076 try:
6081 try:
6077 util.rename(
6082 util.rename(
6078 a + b".resolve", scmutil.backuppath(ui, repo, f)
6083 a + b".resolve", scmutil.backuppath(ui, repo, f)
6079 )
6084 )
6080 except OSError as inst:
6085 except OSError as inst:
6081 if inst.errno != errno.ENOENT:
6086 if inst.errno != errno.ENOENT:
6082 raise
6087 raise
6083
6088
6084 if hasconflictmarkers:
6089 if hasconflictmarkers:
6085 ui.warn(
6090 ui.warn(
6086 _(
6091 _(
6087 b'warning: the following files still have conflict '
6092 b'warning: the following files still have conflict '
6088 b'markers:\n'
6093 b'markers:\n'
6089 )
6094 )
6090 + b''.join(
6095 + b''.join(
6091 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6096 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6092 )
6097 )
6093 )
6098 )
6094 if markcheck == b'abort' and not all and not pats:
6099 if markcheck == b'abort' and not all and not pats:
6095 raise error.Abort(
6100 raise error.Abort(
6096 _(b'conflict markers detected'),
6101 _(b'conflict markers detected'),
6097 hint=_(b'use --all to mark anyway'),
6102 hint=_(b'use --all to mark anyway'),
6098 )
6103 )
6099
6104
6100 for f in tocomplete:
6105 for f in tocomplete:
6101 try:
6106 try:
6102 # resolve file
6107 # resolve file
6103 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6108 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6104 with ui.configoverride(overrides, b'resolve'):
6109 with ui.configoverride(overrides, b'resolve'):
6105 r = ms.resolve(f, wctx)
6110 r = ms.resolve(f, wctx)
6106 if r:
6111 if r:
6107 ret = 1
6112 ret = 1
6108 finally:
6113 finally:
6109 ms.commit()
6114 ms.commit()
6110
6115
6111 # replace filemerge's .orig file with our resolve file
6116 # replace filemerge's .orig file with our resolve file
6112 a = repo.wjoin(f)
6117 a = repo.wjoin(f)
6113 try:
6118 try:
6114 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6119 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6115 except OSError as inst:
6120 except OSError as inst:
6116 if inst.errno != errno.ENOENT:
6121 if inst.errno != errno.ENOENT:
6117 raise
6122 raise
6118
6123
6119 ms.commit()
6124 ms.commit()
6120 ms.recordactions()
6125 ms.recordactions()
6121
6126
6122 if not didwork and pats:
6127 if not didwork and pats:
6123 hint = None
6128 hint = None
6124 if not any([p for p in pats if p.find(b':') >= 0]):
6129 if not any([p for p in pats if p.find(b':') >= 0]):
6125 pats = [b'path:%s' % p for p in pats]
6130 pats = [b'path:%s' % p for p in pats]
6126 m = scmutil.match(wctx, pats, opts)
6131 m = scmutil.match(wctx, pats, opts)
6127 for f in ms:
6132 for f in ms:
6128 if not m(f):
6133 if not m(f):
6129 continue
6134 continue
6130
6135
6131 def flag(o):
6136 def flag(o):
6132 if o == b're_merge':
6137 if o == b're_merge':
6133 return b'--re-merge '
6138 return b'--re-merge '
6134 return b'-%s ' % o[0:1]
6139 return b'-%s ' % o[0:1]
6135
6140
6136 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6141 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6137 hint = _(b"(try: hg resolve %s%s)\n") % (
6142 hint = _(b"(try: hg resolve %s%s)\n") % (
6138 flags,
6143 flags,
6139 b' '.join(pats),
6144 b' '.join(pats),
6140 )
6145 )
6141 break
6146 break
6142 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6147 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6143 if hint:
6148 if hint:
6144 ui.warn(hint)
6149 ui.warn(hint)
6145 elif ms.mergedriver and ms.mdstate() != b's':
6150 elif ms.mergedriver and ms.mdstate() != b's':
6146 # run conclude step when either a driver-resolved file is requested
6151 # run conclude step when either a driver-resolved file is requested
6147 # or there are no driver-resolved files
6152 # or there are no driver-resolved files
6148 # we can't use 'ret' to determine whether any files are unresolved
6153 # we can't use 'ret' to determine whether any files are unresolved
6149 # because we might not have tried to resolve some
6154 # because we might not have tried to resolve some
6150 if (runconclude or not list(ms.driverresolved())) and not list(
6155 if (runconclude or not list(ms.driverresolved())) and not list(
6151 ms.unresolved()
6156 ms.unresolved()
6152 ):
6157 ):
6153 proceed = mergemod.driverconclude(repo, ms, wctx)
6158 proceed = mergemod.driverconclude(repo, ms, wctx)
6154 ms.commit()
6159 ms.commit()
6155 if not proceed:
6160 if not proceed:
6156 return 1
6161 return 1
6157
6162
6158 # Nudge users into finishing an unfinished operation
6163 # Nudge users into finishing an unfinished operation
6159 unresolvedf = list(ms.unresolved())
6164 unresolvedf = list(ms.unresolved())
6160 driverresolvedf = list(ms.driverresolved())
6165 driverresolvedf = list(ms.driverresolved())
6161 if not unresolvedf and not driverresolvedf:
6166 if not unresolvedf and not driverresolvedf:
6162 ui.status(_(b'(no more unresolved files)\n'))
6167 ui.status(_(b'(no more unresolved files)\n'))
6163 cmdutil.checkafterresolved(repo)
6168 cmdutil.checkafterresolved(repo)
6164 elif not unresolvedf:
6169 elif not unresolvedf:
6165 ui.status(
6170 ui.status(
6166 _(
6171 _(
6167 b'(no more unresolved files -- '
6172 b'(no more unresolved files -- '
6168 b'run "hg resolve --all" to conclude)\n'
6173 b'run "hg resolve --all" to conclude)\n'
6169 )
6174 )
6170 )
6175 )
6171
6176
6172 return ret
6177 return ret
6173
6178
6174
6179
6175 @command(
6180 @command(
6176 b'revert',
6181 b'revert',
6177 [
6182 [
6178 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6183 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6179 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6184 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6180 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6185 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6181 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6186 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6182 (b'i', b'interactive', None, _(b'interactively select the changes')),
6187 (b'i', b'interactive', None, _(b'interactively select the changes')),
6183 ]
6188 ]
6184 + walkopts
6189 + walkopts
6185 + dryrunopts,
6190 + dryrunopts,
6186 _(b'[OPTION]... [-r REV] [NAME]...'),
6191 _(b'[OPTION]... [-r REV] [NAME]...'),
6187 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6192 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6188 )
6193 )
6189 def revert(ui, repo, *pats, **opts):
6194 def revert(ui, repo, *pats, **opts):
6190 """restore files to their checkout state
6195 """restore files to their checkout state
6191
6196
6192 .. note::
6197 .. note::
6193
6198
6194 To check out earlier revisions, you should use :hg:`update REV`.
6199 To check out earlier revisions, you should use :hg:`update REV`.
6195 To cancel an uncommitted merge (and lose your changes),
6200 To cancel an uncommitted merge (and lose your changes),
6196 use :hg:`merge --abort`.
6201 use :hg:`merge --abort`.
6197
6202
6198 With no revision specified, revert the specified files or directories
6203 With no revision specified, revert the specified files or directories
6199 to the contents they had in the parent of the working directory.
6204 to the contents they had in the parent of the working directory.
6200 This restores the contents of files to an unmodified
6205 This restores the contents of files to an unmodified
6201 state and unschedules adds, removes, copies, and renames. If the
6206 state and unschedules adds, removes, copies, and renames. If the
6202 working directory has two parents, you must explicitly specify a
6207 working directory has two parents, you must explicitly specify a
6203 revision.
6208 revision.
6204
6209
6205 Using the -r/--rev or -d/--date options, revert the given files or
6210 Using the -r/--rev or -d/--date options, revert the given files or
6206 directories to their states as of a specific revision. Because
6211 directories to their states as of a specific revision. Because
6207 revert does not change the working directory parents, this will
6212 revert does not change the working directory parents, this will
6208 cause these files to appear modified. This can be helpful to "back
6213 cause these files to appear modified. This can be helpful to "back
6209 out" some or all of an earlier change. See :hg:`backout` for a
6214 out" some or all of an earlier change. See :hg:`backout` for a
6210 related method.
6215 related method.
6211
6216
6212 Modified files are saved with a .orig suffix before reverting.
6217 Modified files are saved with a .orig suffix before reverting.
6213 To disable these backups, use --no-backup. It is possible to store
6218 To disable these backups, use --no-backup. It is possible to store
6214 the backup files in a custom directory relative to the root of the
6219 the backup files in a custom directory relative to the root of the
6215 repository by setting the ``ui.origbackuppath`` configuration
6220 repository by setting the ``ui.origbackuppath`` configuration
6216 option.
6221 option.
6217
6222
6218 See :hg:`help dates` for a list of formats valid for -d/--date.
6223 See :hg:`help dates` for a list of formats valid for -d/--date.
6219
6224
6220 See :hg:`help backout` for a way to reverse the effect of an
6225 See :hg:`help backout` for a way to reverse the effect of an
6221 earlier changeset.
6226 earlier changeset.
6222
6227
6223 Returns 0 on success.
6228 Returns 0 on success.
6224 """
6229 """
6225
6230
6226 opts = pycompat.byteskwargs(opts)
6231 opts = pycompat.byteskwargs(opts)
6227 if opts.get(b"date"):
6232 if opts.get(b"date"):
6228 if opts.get(b"rev"):
6233 if opts.get(b"rev"):
6229 raise error.Abort(_(b"you can't specify a revision and a date"))
6234 raise error.Abort(_(b"you can't specify a revision and a date"))
6230 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6235 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6231
6236
6232 parent, p2 = repo.dirstate.parents()
6237 parent, p2 = repo.dirstate.parents()
6233 if not opts.get(b'rev') and p2 != nullid:
6238 if not opts.get(b'rev') and p2 != nullid:
6234 # revert after merge is a trap for new users (issue2915)
6239 # revert after merge is a trap for new users (issue2915)
6235 raise error.Abort(
6240 raise error.Abort(
6236 _(b'uncommitted merge with no revision specified'),
6241 _(b'uncommitted merge with no revision specified'),
6237 hint=_(b"use 'hg update' or see 'hg help revert'"),
6242 hint=_(b"use 'hg update' or see 'hg help revert'"),
6238 )
6243 )
6239
6244
6240 rev = opts.get(b'rev')
6245 rev = opts.get(b'rev')
6241 if rev:
6246 if rev:
6242 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6247 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6243 ctx = scmutil.revsingle(repo, rev)
6248 ctx = scmutil.revsingle(repo, rev)
6244
6249
6245 if not (
6250 if not (
6246 pats
6251 pats
6247 or opts.get(b'include')
6252 or opts.get(b'include')
6248 or opts.get(b'exclude')
6253 or opts.get(b'exclude')
6249 or opts.get(b'all')
6254 or opts.get(b'all')
6250 or opts.get(b'interactive')
6255 or opts.get(b'interactive')
6251 ):
6256 ):
6252 msg = _(b"no files or directories specified")
6257 msg = _(b"no files or directories specified")
6253 if p2 != nullid:
6258 if p2 != nullid:
6254 hint = _(
6259 hint = _(
6255 b"uncommitted merge, use --all to discard all changes,"
6260 b"uncommitted merge, use --all to discard all changes,"
6256 b" or 'hg update -C .' to abort the merge"
6261 b" or 'hg update -C .' to abort the merge"
6257 )
6262 )
6258 raise error.Abort(msg, hint=hint)
6263 raise error.Abort(msg, hint=hint)
6259 dirty = any(repo.status())
6264 dirty = any(repo.status())
6260 node = ctx.node()
6265 node = ctx.node()
6261 if node != parent:
6266 if node != parent:
6262 if dirty:
6267 if dirty:
6263 hint = (
6268 hint = (
6264 _(
6269 _(
6265 b"uncommitted changes, use --all to discard all"
6270 b"uncommitted changes, use --all to discard all"
6266 b" changes, or 'hg update %d' to update"
6271 b" changes, or 'hg update %d' to update"
6267 )
6272 )
6268 % ctx.rev()
6273 % ctx.rev()
6269 )
6274 )
6270 else:
6275 else:
6271 hint = (
6276 hint = (
6272 _(
6277 _(
6273 b"use --all to revert all files,"
6278 b"use --all to revert all files,"
6274 b" or 'hg update %d' to update"
6279 b" or 'hg update %d' to update"
6275 )
6280 )
6276 % ctx.rev()
6281 % ctx.rev()
6277 )
6282 )
6278 elif dirty:
6283 elif dirty:
6279 hint = _(b"uncommitted changes, use --all to discard all changes")
6284 hint = _(b"uncommitted changes, use --all to discard all changes")
6280 else:
6285 else:
6281 hint = _(b"use --all to revert all files")
6286 hint = _(b"use --all to revert all files")
6282 raise error.Abort(msg, hint=hint)
6287 raise error.Abort(msg, hint=hint)
6283
6288
6284 return cmdutil.revert(
6289 return cmdutil.revert(
6285 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6290 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6286 )
6291 )
6287
6292
6288
6293
6289 @command(
6294 @command(
6290 b'rollback',
6295 b'rollback',
6291 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6296 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6292 helpcategory=command.CATEGORY_MAINTENANCE,
6297 helpcategory=command.CATEGORY_MAINTENANCE,
6293 )
6298 )
6294 def rollback(ui, repo, **opts):
6299 def rollback(ui, repo, **opts):
6295 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6300 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6296
6301
6297 Please use :hg:`commit --amend` instead of rollback to correct
6302 Please use :hg:`commit --amend` instead of rollback to correct
6298 mistakes in the last commit.
6303 mistakes in the last commit.
6299
6304
6300 This command should be used with care. There is only one level of
6305 This command should be used with care. There is only one level of
6301 rollback, and there is no way to undo a rollback. It will also
6306 rollback, and there is no way to undo a rollback. It will also
6302 restore the dirstate at the time of the last transaction, losing
6307 restore the dirstate at the time of the last transaction, losing
6303 any dirstate changes since that time. This command does not alter
6308 any dirstate changes since that time. This command does not alter
6304 the working directory.
6309 the working directory.
6305
6310
6306 Transactions are used to encapsulate the effects of all commands
6311 Transactions are used to encapsulate the effects of all commands
6307 that create new changesets or propagate existing changesets into a
6312 that create new changesets or propagate existing changesets into a
6308 repository.
6313 repository.
6309
6314
6310 .. container:: verbose
6315 .. container:: verbose
6311
6316
6312 For example, the following commands are transactional, and their
6317 For example, the following commands are transactional, and their
6313 effects can be rolled back:
6318 effects can be rolled back:
6314
6319
6315 - commit
6320 - commit
6316 - import
6321 - import
6317 - pull
6322 - pull
6318 - push (with this repository as the destination)
6323 - push (with this repository as the destination)
6319 - unbundle
6324 - unbundle
6320
6325
6321 To avoid permanent data loss, rollback will refuse to rollback a
6326 To avoid permanent data loss, rollback will refuse to rollback a
6322 commit transaction if it isn't checked out. Use --force to
6327 commit transaction if it isn't checked out. Use --force to
6323 override this protection.
6328 override this protection.
6324
6329
6325 The rollback command can be entirely disabled by setting the
6330 The rollback command can be entirely disabled by setting the
6326 ``ui.rollback`` configuration setting to false. If you're here
6331 ``ui.rollback`` configuration setting to false. If you're here
6327 because you want to use rollback and it's disabled, you can
6332 because you want to use rollback and it's disabled, you can
6328 re-enable the command by setting ``ui.rollback`` to true.
6333 re-enable the command by setting ``ui.rollback`` to true.
6329
6334
6330 This command is not intended for use on public repositories. Once
6335 This command is not intended for use on public repositories. Once
6331 changes are visible for pull by other users, rolling a transaction
6336 changes are visible for pull by other users, rolling a transaction
6332 back locally is ineffective (someone else may already have pulled
6337 back locally is ineffective (someone else may already have pulled
6333 the changes). Furthermore, a race is possible with readers of the
6338 the changes). Furthermore, a race is possible with readers of the
6334 repository; for example an in-progress pull from the repository
6339 repository; for example an in-progress pull from the repository
6335 may fail if a rollback is performed.
6340 may fail if a rollback is performed.
6336
6341
6337 Returns 0 on success, 1 if no rollback data is available.
6342 Returns 0 on success, 1 if no rollback data is available.
6338 """
6343 """
6339 if not ui.configbool(b'ui', b'rollback'):
6344 if not ui.configbool(b'ui', b'rollback'):
6340 raise error.Abort(
6345 raise error.Abort(
6341 _(b'rollback is disabled because it is unsafe'),
6346 _(b'rollback is disabled because it is unsafe'),
6342 hint=b'see `hg help -v rollback` for information',
6347 hint=b'see `hg help -v rollback` for information',
6343 )
6348 )
6344 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6349 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6345
6350
6346
6351
6347 @command(
6352 @command(
6348 b'root',
6353 b'root',
6349 [] + formatteropts,
6354 [] + formatteropts,
6350 intents={INTENT_READONLY},
6355 intents={INTENT_READONLY},
6351 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6356 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6352 )
6357 )
6353 def root(ui, repo, **opts):
6358 def root(ui, repo, **opts):
6354 """print the root (top) of the current working directory
6359 """print the root (top) of the current working directory
6355
6360
6356 Print the root directory of the current repository.
6361 Print the root directory of the current repository.
6357
6362
6358 .. container:: verbose
6363 .. container:: verbose
6359
6364
6360 Template:
6365 Template:
6361
6366
6362 The following keywords are supported in addition to the common template
6367 The following keywords are supported in addition to the common template
6363 keywords and functions. See also :hg:`help templates`.
6368 keywords and functions. See also :hg:`help templates`.
6364
6369
6365 :hgpath: String. Path to the .hg directory.
6370 :hgpath: String. Path to the .hg directory.
6366 :storepath: String. Path to the directory holding versioned data.
6371 :storepath: String. Path to the directory holding versioned data.
6367
6372
6368 Returns 0 on success.
6373 Returns 0 on success.
6369 """
6374 """
6370 opts = pycompat.byteskwargs(opts)
6375 opts = pycompat.byteskwargs(opts)
6371 with ui.formatter(b'root', opts) as fm:
6376 with ui.formatter(b'root', opts) as fm:
6372 fm.startitem()
6377 fm.startitem()
6373 fm.write(b'reporoot', b'%s\n', repo.root)
6378 fm.write(b'reporoot', b'%s\n', repo.root)
6374 fm.data(hgpath=repo.path, storepath=repo.spath)
6379 fm.data(hgpath=repo.path, storepath=repo.spath)
6375
6380
6376
6381
6377 @command(
6382 @command(
6378 b'serve',
6383 b'serve',
6379 [
6384 [
6380 (
6385 (
6381 b'A',
6386 b'A',
6382 b'accesslog',
6387 b'accesslog',
6383 b'',
6388 b'',
6384 _(b'name of access log file to write to'),
6389 _(b'name of access log file to write to'),
6385 _(b'FILE'),
6390 _(b'FILE'),
6386 ),
6391 ),
6387 (b'd', b'daemon', None, _(b'run server in background')),
6392 (b'd', b'daemon', None, _(b'run server in background')),
6388 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6393 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6389 (
6394 (
6390 b'E',
6395 b'E',
6391 b'errorlog',
6396 b'errorlog',
6392 b'',
6397 b'',
6393 _(b'name of error log file to write to'),
6398 _(b'name of error log file to write to'),
6394 _(b'FILE'),
6399 _(b'FILE'),
6395 ),
6400 ),
6396 # use string type, then we can check if something was passed
6401 # use string type, then we can check if something was passed
6397 (
6402 (
6398 b'p',
6403 b'p',
6399 b'port',
6404 b'port',
6400 b'',
6405 b'',
6401 _(b'port to listen on (default: 8000)'),
6406 _(b'port to listen on (default: 8000)'),
6402 _(b'PORT'),
6407 _(b'PORT'),
6403 ),
6408 ),
6404 (
6409 (
6405 b'a',
6410 b'a',
6406 b'address',
6411 b'address',
6407 b'',
6412 b'',
6408 _(b'address to listen on (default: all interfaces)'),
6413 _(b'address to listen on (default: all interfaces)'),
6409 _(b'ADDR'),
6414 _(b'ADDR'),
6410 ),
6415 ),
6411 (
6416 (
6412 b'',
6417 b'',
6413 b'prefix',
6418 b'prefix',
6414 b'',
6419 b'',
6415 _(b'prefix path to serve from (default: server root)'),
6420 _(b'prefix path to serve from (default: server root)'),
6416 _(b'PREFIX'),
6421 _(b'PREFIX'),
6417 ),
6422 ),
6418 (
6423 (
6419 b'n',
6424 b'n',
6420 b'name',
6425 b'name',
6421 b'',
6426 b'',
6422 _(b'name to show in web pages (default: working directory)'),
6427 _(b'name to show in web pages (default: working directory)'),
6423 _(b'NAME'),
6428 _(b'NAME'),
6424 ),
6429 ),
6425 (
6430 (
6426 b'',
6431 b'',
6427 b'web-conf',
6432 b'web-conf',
6428 b'',
6433 b'',
6429 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6434 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6430 _(b'FILE'),
6435 _(b'FILE'),
6431 ),
6436 ),
6432 (
6437 (
6433 b'',
6438 b'',
6434 b'webdir-conf',
6439 b'webdir-conf',
6435 b'',
6440 b'',
6436 _(b'name of the hgweb config file (DEPRECATED)'),
6441 _(b'name of the hgweb config file (DEPRECATED)'),
6437 _(b'FILE'),
6442 _(b'FILE'),
6438 ),
6443 ),
6439 (
6444 (
6440 b'',
6445 b'',
6441 b'pid-file',
6446 b'pid-file',
6442 b'',
6447 b'',
6443 _(b'name of file to write process ID to'),
6448 _(b'name of file to write process ID to'),
6444 _(b'FILE'),
6449 _(b'FILE'),
6445 ),
6450 ),
6446 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6451 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6447 (
6452 (
6448 b'',
6453 b'',
6449 b'cmdserver',
6454 b'cmdserver',
6450 b'',
6455 b'',
6451 _(b'for remote clients (ADVANCED)'),
6456 _(b'for remote clients (ADVANCED)'),
6452 _(b'MODE'),
6457 _(b'MODE'),
6453 ),
6458 ),
6454 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6459 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6455 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6460 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6456 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6461 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6457 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6462 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6458 (b'', b'print-url', None, _(b'start and print only the URL')),
6463 (b'', b'print-url', None, _(b'start and print only the URL')),
6459 ]
6464 ]
6460 + subrepoopts,
6465 + subrepoopts,
6461 _(b'[OPTION]...'),
6466 _(b'[OPTION]...'),
6462 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6467 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6463 helpbasic=True,
6468 helpbasic=True,
6464 optionalrepo=True,
6469 optionalrepo=True,
6465 )
6470 )
6466 def serve(ui, repo, **opts):
6471 def serve(ui, repo, **opts):
6467 """start stand-alone webserver
6472 """start stand-alone webserver
6468
6473
6469 Start a local HTTP repository browser and pull server. You can use
6474 Start a local HTTP repository browser and pull server. You can use
6470 this for ad-hoc sharing and browsing of repositories. It is
6475 this for ad-hoc sharing and browsing of repositories. It is
6471 recommended to use a real web server to serve a repository for
6476 recommended to use a real web server to serve a repository for
6472 longer periods of time.
6477 longer periods of time.
6473
6478
6474 Please note that the server does not implement access control.
6479 Please note that the server does not implement access control.
6475 This means that, by default, anybody can read from the server and
6480 This means that, by default, anybody can read from the server and
6476 nobody can write to it by default. Set the ``web.allow-push``
6481 nobody can write to it by default. Set the ``web.allow-push``
6477 option to ``*`` to allow everybody to push to the server. You
6482 option to ``*`` to allow everybody to push to the server. You
6478 should use a real web server if you need to authenticate users.
6483 should use a real web server if you need to authenticate users.
6479
6484
6480 By default, the server logs accesses to stdout and errors to
6485 By default, the server logs accesses to stdout and errors to
6481 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6486 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6482 files.
6487 files.
6483
6488
6484 To have the server choose a free port number to listen on, specify
6489 To have the server choose a free port number to listen on, specify
6485 a port number of 0; in this case, the server will print the port
6490 a port number of 0; in this case, the server will print the port
6486 number it uses.
6491 number it uses.
6487
6492
6488 Returns 0 on success.
6493 Returns 0 on success.
6489 """
6494 """
6490
6495
6491 opts = pycompat.byteskwargs(opts)
6496 opts = pycompat.byteskwargs(opts)
6492 if opts[b"stdio"] and opts[b"cmdserver"]:
6497 if opts[b"stdio"] and opts[b"cmdserver"]:
6493 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6498 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6494 if opts[b"print_url"] and ui.verbose:
6499 if opts[b"print_url"] and ui.verbose:
6495 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6500 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6496
6501
6497 if opts[b"stdio"]:
6502 if opts[b"stdio"]:
6498 if repo is None:
6503 if repo is None:
6499 raise error.RepoError(
6504 raise error.RepoError(
6500 _(b"there is no Mercurial repository here (.hg not found)")
6505 _(b"there is no Mercurial repository here (.hg not found)")
6501 )
6506 )
6502 s = wireprotoserver.sshserver(ui, repo)
6507 s = wireprotoserver.sshserver(ui, repo)
6503 s.serve_forever()
6508 s.serve_forever()
6504
6509
6505 service = server.createservice(ui, repo, opts)
6510 service = server.createservice(ui, repo, opts)
6506 return server.runservice(opts, initfn=service.init, runfn=service.run)
6511 return server.runservice(opts, initfn=service.init, runfn=service.run)
6507
6512
6508
6513
6509 @command(
6514 @command(
6510 b'shelve',
6515 b'shelve',
6511 [
6516 [
6512 (
6517 (
6513 b'A',
6518 b'A',
6514 b'addremove',
6519 b'addremove',
6515 None,
6520 None,
6516 _(b'mark new/missing files as added/removed before shelving'),
6521 _(b'mark new/missing files as added/removed before shelving'),
6517 ),
6522 ),
6518 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6523 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6519 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6524 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6520 (
6525 (
6521 b'',
6526 b'',
6522 b'date',
6527 b'date',
6523 b'',
6528 b'',
6524 _(b'shelve with the specified commit date'),
6529 _(b'shelve with the specified commit date'),
6525 _(b'DATE'),
6530 _(b'DATE'),
6526 ),
6531 ),
6527 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6532 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6528 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6533 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6529 (
6534 (
6530 b'k',
6535 b'k',
6531 b'keep',
6536 b'keep',
6532 False,
6537 False,
6533 _(b'shelve, but keep changes in the working directory'),
6538 _(b'shelve, but keep changes in the working directory'),
6534 ),
6539 ),
6535 (b'l', b'list', None, _(b'list current shelves')),
6540 (b'l', b'list', None, _(b'list current shelves')),
6536 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6541 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6537 (
6542 (
6538 b'n',
6543 b'n',
6539 b'name',
6544 b'name',
6540 b'',
6545 b'',
6541 _(b'use the given name for the shelved commit'),
6546 _(b'use the given name for the shelved commit'),
6542 _(b'NAME'),
6547 _(b'NAME'),
6543 ),
6548 ),
6544 (
6549 (
6545 b'p',
6550 b'p',
6546 b'patch',
6551 b'patch',
6547 None,
6552 None,
6548 _(
6553 _(
6549 b'output patches for changes (provide the names of the shelved '
6554 b'output patches for changes (provide the names of the shelved '
6550 b'changes as positional arguments)'
6555 b'changes as positional arguments)'
6551 ),
6556 ),
6552 ),
6557 ),
6553 (b'i', b'interactive', None, _(b'interactive mode')),
6558 (b'i', b'interactive', None, _(b'interactive mode')),
6554 (
6559 (
6555 b'',
6560 b'',
6556 b'stat',
6561 b'stat',
6557 None,
6562 None,
6558 _(
6563 _(
6559 b'output diffstat-style summary of changes (provide the names of '
6564 b'output diffstat-style summary of changes (provide the names of '
6560 b'the shelved changes as positional arguments)'
6565 b'the shelved changes as positional arguments)'
6561 ),
6566 ),
6562 ),
6567 ),
6563 ]
6568 ]
6564 + cmdutil.walkopts,
6569 + cmdutil.walkopts,
6565 _(b'hg shelve [OPTION]... [FILE]...'),
6570 _(b'hg shelve [OPTION]... [FILE]...'),
6566 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6571 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6567 )
6572 )
6568 def shelve(ui, repo, *pats, **opts):
6573 def shelve(ui, repo, *pats, **opts):
6569 '''save and set aside changes from the working directory
6574 '''save and set aside changes from the working directory
6570
6575
6571 Shelving takes files that "hg status" reports as not clean, saves
6576 Shelving takes files that "hg status" reports as not clean, saves
6572 the modifications to a bundle (a shelved change), and reverts the
6577 the modifications to a bundle (a shelved change), and reverts the
6573 files so that their state in the working directory becomes clean.
6578 files so that their state in the working directory becomes clean.
6574
6579
6575 To restore these changes to the working directory, using "hg
6580 To restore these changes to the working directory, using "hg
6576 unshelve"; this will work even if you switch to a different
6581 unshelve"; this will work even if you switch to a different
6577 commit.
6582 commit.
6578
6583
6579 When no files are specified, "hg shelve" saves all not-clean
6584 When no files are specified, "hg shelve" saves all not-clean
6580 files. If specific files or directories are named, only changes to
6585 files. If specific files or directories are named, only changes to
6581 those files are shelved.
6586 those files are shelved.
6582
6587
6583 In bare shelve (when no files are specified, without interactive,
6588 In bare shelve (when no files are specified, without interactive,
6584 include and exclude option), shelving remembers information if the
6589 include and exclude option), shelving remembers information if the
6585 working directory was on newly created branch, in other words working
6590 working directory was on newly created branch, in other words working
6586 directory was on different branch than its first parent. In this
6591 directory was on different branch than its first parent. In this
6587 situation unshelving restores branch information to the working directory.
6592 situation unshelving restores branch information to the working directory.
6588
6593
6589 Each shelved change has a name that makes it easier to find later.
6594 Each shelved change has a name that makes it easier to find later.
6590 The name of a shelved change defaults to being based on the active
6595 The name of a shelved change defaults to being based on the active
6591 bookmark, or if there is no active bookmark, the current named
6596 bookmark, or if there is no active bookmark, the current named
6592 branch. To specify a different name, use ``--name``.
6597 branch. To specify a different name, use ``--name``.
6593
6598
6594 To see a list of existing shelved changes, use the ``--list``
6599 To see a list of existing shelved changes, use the ``--list``
6595 option. For each shelved change, this will print its name, age,
6600 option. For each shelved change, this will print its name, age,
6596 and description; use ``--patch`` or ``--stat`` for more details.
6601 and description; use ``--patch`` or ``--stat`` for more details.
6597
6602
6598 To delete specific shelved changes, use ``--delete``. To delete
6603 To delete specific shelved changes, use ``--delete``. To delete
6599 all shelved changes, use ``--cleanup``.
6604 all shelved changes, use ``--cleanup``.
6600 '''
6605 '''
6601 opts = pycompat.byteskwargs(opts)
6606 opts = pycompat.byteskwargs(opts)
6602 allowables = [
6607 allowables = [
6603 (b'addremove', {b'create'}), # 'create' is pseudo action
6608 (b'addremove', {b'create'}), # 'create' is pseudo action
6604 (b'unknown', {b'create'}),
6609 (b'unknown', {b'create'}),
6605 (b'cleanup', {b'cleanup'}),
6610 (b'cleanup', {b'cleanup'}),
6606 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6611 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6607 (b'delete', {b'delete'}),
6612 (b'delete', {b'delete'}),
6608 (b'edit', {b'create'}),
6613 (b'edit', {b'create'}),
6609 (b'keep', {b'create'}),
6614 (b'keep', {b'create'}),
6610 (b'list', {b'list'}),
6615 (b'list', {b'list'}),
6611 (b'message', {b'create'}),
6616 (b'message', {b'create'}),
6612 (b'name', {b'create'}),
6617 (b'name', {b'create'}),
6613 (b'patch', {b'patch', b'list'}),
6618 (b'patch', {b'patch', b'list'}),
6614 (b'stat', {b'stat', b'list'}),
6619 (b'stat', {b'stat', b'list'}),
6615 ]
6620 ]
6616
6621
6617 def checkopt(opt):
6622 def checkopt(opt):
6618 if opts.get(opt):
6623 if opts.get(opt):
6619 for i, allowable in allowables:
6624 for i, allowable in allowables:
6620 if opts[i] and opt not in allowable:
6625 if opts[i] and opt not in allowable:
6621 raise error.Abort(
6626 raise error.Abort(
6622 _(
6627 _(
6623 b"options '--%s' and '--%s' may not be "
6628 b"options '--%s' and '--%s' may not be "
6624 b"used together"
6629 b"used together"
6625 )
6630 )
6626 % (opt, i)
6631 % (opt, i)
6627 )
6632 )
6628 return True
6633 return True
6629
6634
6630 if checkopt(b'cleanup'):
6635 if checkopt(b'cleanup'):
6631 if pats:
6636 if pats:
6632 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6637 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6633 return shelvemod.cleanupcmd(ui, repo)
6638 return shelvemod.cleanupcmd(ui, repo)
6634 elif checkopt(b'delete'):
6639 elif checkopt(b'delete'):
6635 return shelvemod.deletecmd(ui, repo, pats)
6640 return shelvemod.deletecmd(ui, repo, pats)
6636 elif checkopt(b'list'):
6641 elif checkopt(b'list'):
6637 return shelvemod.listcmd(ui, repo, pats, opts)
6642 return shelvemod.listcmd(ui, repo, pats, opts)
6638 elif checkopt(b'patch') or checkopt(b'stat'):
6643 elif checkopt(b'patch') or checkopt(b'stat'):
6639 return shelvemod.patchcmds(ui, repo, pats, opts)
6644 return shelvemod.patchcmds(ui, repo, pats, opts)
6640 else:
6645 else:
6641 return shelvemod.createcmd(ui, repo, pats, opts)
6646 return shelvemod.createcmd(ui, repo, pats, opts)
6642
6647
6643
6648
6644 _NOTTERSE = b'nothing'
6649 _NOTTERSE = b'nothing'
6645
6650
6646
6651
6647 @command(
6652 @command(
6648 b'status|st',
6653 b'status|st',
6649 [
6654 [
6650 (b'A', b'all', None, _(b'show status of all files')),
6655 (b'A', b'all', None, _(b'show status of all files')),
6651 (b'm', b'modified', None, _(b'show only modified files')),
6656 (b'm', b'modified', None, _(b'show only modified files')),
6652 (b'a', b'added', None, _(b'show only added files')),
6657 (b'a', b'added', None, _(b'show only added files')),
6653 (b'r', b'removed', None, _(b'show only removed files')),
6658 (b'r', b'removed', None, _(b'show only removed files')),
6654 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6659 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6655 (b'c', b'clean', None, _(b'show only files without changes')),
6660 (b'c', b'clean', None, _(b'show only files without changes')),
6656 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6661 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6657 (b'i', b'ignored', None, _(b'show only ignored files')),
6662 (b'i', b'ignored', None, _(b'show only ignored files')),
6658 (b'n', b'no-status', None, _(b'hide status prefix')),
6663 (b'n', b'no-status', None, _(b'hide status prefix')),
6659 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6664 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6660 (
6665 (
6661 b'C',
6666 b'C',
6662 b'copies',
6667 b'copies',
6663 None,
6668 None,
6664 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6669 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6665 ),
6670 ),
6666 (
6671 (
6667 b'0',
6672 b'0',
6668 b'print0',
6673 b'print0',
6669 None,
6674 None,
6670 _(b'end filenames with NUL, for use with xargs'),
6675 _(b'end filenames with NUL, for use with xargs'),
6671 ),
6676 ),
6672 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6677 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6673 (
6678 (
6674 b'',
6679 b'',
6675 b'change',
6680 b'change',
6676 b'',
6681 b'',
6677 _(b'list the changed files of a revision'),
6682 _(b'list the changed files of a revision'),
6678 _(b'REV'),
6683 _(b'REV'),
6679 ),
6684 ),
6680 ]
6685 ]
6681 + walkopts
6686 + walkopts
6682 + subrepoopts
6687 + subrepoopts
6683 + formatteropts,
6688 + formatteropts,
6684 _(b'[OPTION]... [FILE]...'),
6689 _(b'[OPTION]... [FILE]...'),
6685 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6690 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6686 helpbasic=True,
6691 helpbasic=True,
6687 inferrepo=True,
6692 inferrepo=True,
6688 intents={INTENT_READONLY},
6693 intents={INTENT_READONLY},
6689 )
6694 )
6690 def status(ui, repo, *pats, **opts):
6695 def status(ui, repo, *pats, **opts):
6691 """show changed files in the working directory
6696 """show changed files in the working directory
6692
6697
6693 Show status of files in the repository. If names are given, only
6698 Show status of files in the repository. If names are given, only
6694 files that match are shown. Files that are clean or ignored or
6699 files that match are shown. Files that are clean or ignored or
6695 the source of a copy/move operation, are not listed unless
6700 the source of a copy/move operation, are not listed unless
6696 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6701 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6697 Unless options described with "show only ..." are given, the
6702 Unless options described with "show only ..." are given, the
6698 options -mardu are used.
6703 options -mardu are used.
6699
6704
6700 Option -q/--quiet hides untracked (unknown and ignored) files
6705 Option -q/--quiet hides untracked (unknown and ignored) files
6701 unless explicitly requested with -u/--unknown or -i/--ignored.
6706 unless explicitly requested with -u/--unknown or -i/--ignored.
6702
6707
6703 .. note::
6708 .. note::
6704
6709
6705 :hg:`status` may appear to disagree with diff if permissions have
6710 :hg:`status` may appear to disagree with diff if permissions have
6706 changed or a merge has occurred. The standard diff format does
6711 changed or a merge has occurred. The standard diff format does
6707 not report permission changes and diff only reports changes
6712 not report permission changes and diff only reports changes
6708 relative to one merge parent.
6713 relative to one merge parent.
6709
6714
6710 If one revision is given, it is used as the base revision.
6715 If one revision is given, it is used as the base revision.
6711 If two revisions are given, the differences between them are
6716 If two revisions are given, the differences between them are
6712 shown. The --change option can also be used as a shortcut to list
6717 shown. The --change option can also be used as a shortcut to list
6713 the changed files of a revision from its first parent.
6718 the changed files of a revision from its first parent.
6714
6719
6715 The codes used to show the status of files are::
6720 The codes used to show the status of files are::
6716
6721
6717 M = modified
6722 M = modified
6718 A = added
6723 A = added
6719 R = removed
6724 R = removed
6720 C = clean
6725 C = clean
6721 ! = missing (deleted by non-hg command, but still tracked)
6726 ! = missing (deleted by non-hg command, but still tracked)
6722 ? = not tracked
6727 ? = not tracked
6723 I = ignored
6728 I = ignored
6724 = origin of the previous file (with --copies)
6729 = origin of the previous file (with --copies)
6725
6730
6726 .. container:: verbose
6731 .. container:: verbose
6727
6732
6728 The -t/--terse option abbreviates the output by showing only the directory
6733 The -t/--terse option abbreviates the output by showing only the directory
6729 name if all the files in it share the same status. The option takes an
6734 name if all the files in it share the same status. The option takes an
6730 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6735 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6731 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6736 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6732 for 'ignored' and 'c' for clean.
6737 for 'ignored' and 'c' for clean.
6733
6738
6734 It abbreviates only those statuses which are passed. Note that clean and
6739 It abbreviates only those statuses which are passed. Note that clean and
6735 ignored files are not displayed with '--terse ic' unless the -c/--clean
6740 ignored files are not displayed with '--terse ic' unless the -c/--clean
6736 and -i/--ignored options are also used.
6741 and -i/--ignored options are also used.
6737
6742
6738 The -v/--verbose option shows information when the repository is in an
6743 The -v/--verbose option shows information when the repository is in an
6739 unfinished merge, shelve, rebase state etc. You can have this behavior
6744 unfinished merge, shelve, rebase state etc. You can have this behavior
6740 turned on by default by enabling the ``commands.status.verbose`` option.
6745 turned on by default by enabling the ``commands.status.verbose`` option.
6741
6746
6742 You can skip displaying some of these states by setting
6747 You can skip displaying some of these states by setting
6743 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6748 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6744 'histedit', 'merge', 'rebase', or 'unshelve'.
6749 'histedit', 'merge', 'rebase', or 'unshelve'.
6745
6750
6746 Template:
6751 Template:
6747
6752
6748 The following keywords are supported in addition to the common template
6753 The following keywords are supported in addition to the common template
6749 keywords and functions. See also :hg:`help templates`.
6754 keywords and functions. See also :hg:`help templates`.
6750
6755
6751 :path: String. Repository-absolute path of the file.
6756 :path: String. Repository-absolute path of the file.
6752 :source: String. Repository-absolute path of the file originated from.
6757 :source: String. Repository-absolute path of the file originated from.
6753 Available if ``--copies`` is specified.
6758 Available if ``--copies`` is specified.
6754 :status: String. Character denoting file's status.
6759 :status: String. Character denoting file's status.
6755
6760
6756 Examples:
6761 Examples:
6757
6762
6758 - show changes in the working directory relative to a
6763 - show changes in the working directory relative to a
6759 changeset::
6764 changeset::
6760
6765
6761 hg status --rev 9353
6766 hg status --rev 9353
6762
6767
6763 - show changes in the working directory relative to the
6768 - show changes in the working directory relative to the
6764 current directory (see :hg:`help patterns` for more information)::
6769 current directory (see :hg:`help patterns` for more information)::
6765
6770
6766 hg status re:
6771 hg status re:
6767
6772
6768 - show all changes including copies in an existing changeset::
6773 - show all changes including copies in an existing changeset::
6769
6774
6770 hg status --copies --change 9353
6775 hg status --copies --change 9353
6771
6776
6772 - get a NUL separated list of added files, suitable for xargs::
6777 - get a NUL separated list of added files, suitable for xargs::
6773
6778
6774 hg status -an0
6779 hg status -an0
6775
6780
6776 - show more information about the repository status, abbreviating
6781 - show more information about the repository status, abbreviating
6777 added, removed, modified, deleted, and untracked paths::
6782 added, removed, modified, deleted, and untracked paths::
6778
6783
6779 hg status -v -t mardu
6784 hg status -v -t mardu
6780
6785
6781 Returns 0 on success.
6786 Returns 0 on success.
6782
6787
6783 """
6788 """
6784
6789
6785 opts = pycompat.byteskwargs(opts)
6790 opts = pycompat.byteskwargs(opts)
6786 revs = opts.get(b'rev')
6791 revs = opts.get(b'rev')
6787 change = opts.get(b'change')
6792 change = opts.get(b'change')
6788 terse = opts.get(b'terse')
6793 terse = opts.get(b'terse')
6789 if terse is _NOTTERSE:
6794 if terse is _NOTTERSE:
6790 if revs:
6795 if revs:
6791 terse = b''
6796 terse = b''
6792 else:
6797 else:
6793 terse = ui.config(b'commands', b'status.terse')
6798 terse = ui.config(b'commands', b'status.terse')
6794
6799
6795 if revs and change:
6800 if revs and change:
6796 msg = _(b'cannot specify --rev and --change at the same time')
6801 msg = _(b'cannot specify --rev and --change at the same time')
6797 raise error.Abort(msg)
6802 raise error.Abort(msg)
6798 elif revs and terse:
6803 elif revs and terse:
6799 msg = _(b'cannot use --terse with --rev')
6804 msg = _(b'cannot use --terse with --rev')
6800 raise error.Abort(msg)
6805 raise error.Abort(msg)
6801 elif change:
6806 elif change:
6802 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6807 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6803 ctx2 = scmutil.revsingle(repo, change, None)
6808 ctx2 = scmutil.revsingle(repo, change, None)
6804 ctx1 = ctx2.p1()
6809 ctx1 = ctx2.p1()
6805 else:
6810 else:
6806 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6811 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6807 ctx1, ctx2 = scmutil.revpair(repo, revs)
6812 ctx1, ctx2 = scmutil.revpair(repo, revs)
6808
6813
6809 forcerelativevalue = None
6814 forcerelativevalue = None
6810 if ui.hasconfig(b'commands', b'status.relative'):
6815 if ui.hasconfig(b'commands', b'status.relative'):
6811 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6816 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6812 uipathfn = scmutil.getuipathfn(
6817 uipathfn = scmutil.getuipathfn(
6813 repo,
6818 repo,
6814 legacyrelativevalue=bool(pats),
6819 legacyrelativevalue=bool(pats),
6815 forcerelativevalue=forcerelativevalue,
6820 forcerelativevalue=forcerelativevalue,
6816 )
6821 )
6817
6822
6818 if opts.get(b'print0'):
6823 if opts.get(b'print0'):
6819 end = b'\0'
6824 end = b'\0'
6820 else:
6825 else:
6821 end = b'\n'
6826 end = b'\n'
6822 states = b'modified added removed deleted unknown ignored clean'.split()
6827 states = b'modified added removed deleted unknown ignored clean'.split()
6823 show = [k for k in states if opts.get(k)]
6828 show = [k for k in states if opts.get(k)]
6824 if opts.get(b'all'):
6829 if opts.get(b'all'):
6825 show += ui.quiet and (states[:4] + [b'clean']) or states
6830 show += ui.quiet and (states[:4] + [b'clean']) or states
6826
6831
6827 if not show:
6832 if not show:
6828 if ui.quiet:
6833 if ui.quiet:
6829 show = states[:4]
6834 show = states[:4]
6830 else:
6835 else:
6831 show = states[:5]
6836 show = states[:5]
6832
6837
6833 m = scmutil.match(ctx2, pats, opts)
6838 m = scmutil.match(ctx2, pats, opts)
6834 if terse:
6839 if terse:
6835 # we need to compute clean and unknown to terse
6840 # we need to compute clean and unknown to terse
6836 stat = repo.status(
6841 stat = repo.status(
6837 ctx1.node(),
6842 ctx1.node(),
6838 ctx2.node(),
6843 ctx2.node(),
6839 m,
6844 m,
6840 b'ignored' in show or b'i' in terse,
6845 b'ignored' in show or b'i' in terse,
6841 clean=True,
6846 clean=True,
6842 unknown=True,
6847 unknown=True,
6843 listsubrepos=opts.get(b'subrepos'),
6848 listsubrepos=opts.get(b'subrepos'),
6844 )
6849 )
6845
6850
6846 stat = cmdutil.tersedir(stat, terse)
6851 stat = cmdutil.tersedir(stat, terse)
6847 else:
6852 else:
6848 stat = repo.status(
6853 stat = repo.status(
6849 ctx1.node(),
6854 ctx1.node(),
6850 ctx2.node(),
6855 ctx2.node(),
6851 m,
6856 m,
6852 b'ignored' in show,
6857 b'ignored' in show,
6853 b'clean' in show,
6858 b'clean' in show,
6854 b'unknown' in show,
6859 b'unknown' in show,
6855 opts.get(b'subrepos'),
6860 opts.get(b'subrepos'),
6856 )
6861 )
6857
6862
6858 changestates = zip(
6863 changestates = zip(
6859 states,
6864 states,
6860 pycompat.iterbytestr(b'MAR!?IC'),
6865 pycompat.iterbytestr(b'MAR!?IC'),
6861 [getattr(stat, s.decode('utf8')) for s in states],
6866 [getattr(stat, s.decode('utf8')) for s in states],
6862 )
6867 )
6863
6868
6864 copy = {}
6869 copy = {}
6865 if (
6870 if (
6866 opts.get(b'all')
6871 opts.get(b'all')
6867 or opts.get(b'copies')
6872 or opts.get(b'copies')
6868 or ui.configbool(b'ui', b'statuscopies')
6873 or ui.configbool(b'ui', b'statuscopies')
6869 ) and not opts.get(b'no_status'):
6874 ) and not opts.get(b'no_status'):
6870 copy = copies.pathcopies(ctx1, ctx2, m)
6875 copy = copies.pathcopies(ctx1, ctx2, m)
6871
6876
6872 morestatus = None
6877 morestatus = None
6873 if (
6878 if (
6874 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6879 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6875 ) and not ui.plain():
6880 ) and not ui.plain():
6876 morestatus = cmdutil.readmorestatus(repo)
6881 morestatus = cmdutil.readmorestatus(repo)
6877
6882
6878 ui.pager(b'status')
6883 ui.pager(b'status')
6879 fm = ui.formatter(b'status', opts)
6884 fm = ui.formatter(b'status', opts)
6880 fmt = b'%s' + end
6885 fmt = b'%s' + end
6881 showchar = not opts.get(b'no_status')
6886 showchar = not opts.get(b'no_status')
6882
6887
6883 for state, char, files in changestates:
6888 for state, char, files in changestates:
6884 if state in show:
6889 if state in show:
6885 label = b'status.' + state
6890 label = b'status.' + state
6886 for f in files:
6891 for f in files:
6887 fm.startitem()
6892 fm.startitem()
6888 fm.context(ctx=ctx2)
6893 fm.context(ctx=ctx2)
6889 fm.data(itemtype=b'file', path=f)
6894 fm.data(itemtype=b'file', path=f)
6890 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6895 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6891 fm.plain(fmt % uipathfn(f), label=label)
6896 fm.plain(fmt % uipathfn(f), label=label)
6892 if f in copy:
6897 if f in copy:
6893 fm.data(source=copy[f])
6898 fm.data(source=copy[f])
6894 fm.plain(
6899 fm.plain(
6895 (b' %s' + end) % uipathfn(copy[f]),
6900 (b' %s' + end) % uipathfn(copy[f]),
6896 label=b'status.copied',
6901 label=b'status.copied',
6897 )
6902 )
6898 if morestatus:
6903 if morestatus:
6899 morestatus.formatfile(f, fm)
6904 morestatus.formatfile(f, fm)
6900
6905
6901 if morestatus:
6906 if morestatus:
6902 morestatus.formatfooter(fm)
6907 morestatus.formatfooter(fm)
6903 fm.end()
6908 fm.end()
6904
6909
6905
6910
6906 @command(
6911 @command(
6907 b'summary|sum',
6912 b'summary|sum',
6908 [(b'', b'remote', None, _(b'check for push and pull'))],
6913 [(b'', b'remote', None, _(b'check for push and pull'))],
6909 b'[--remote]',
6914 b'[--remote]',
6910 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6915 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6911 helpbasic=True,
6916 helpbasic=True,
6912 intents={INTENT_READONLY},
6917 intents={INTENT_READONLY},
6913 )
6918 )
6914 def summary(ui, repo, **opts):
6919 def summary(ui, repo, **opts):
6915 """summarize working directory state
6920 """summarize working directory state
6916
6921
6917 This generates a brief summary of the working directory state,
6922 This generates a brief summary of the working directory state,
6918 including parents, branch, commit status, phase and available updates.
6923 including parents, branch, commit status, phase and available updates.
6919
6924
6920 With the --remote option, this will check the default paths for
6925 With the --remote option, this will check the default paths for
6921 incoming and outgoing changes. This can be time-consuming.
6926 incoming and outgoing changes. This can be time-consuming.
6922
6927
6923 Returns 0 on success.
6928 Returns 0 on success.
6924 """
6929 """
6925
6930
6926 opts = pycompat.byteskwargs(opts)
6931 opts = pycompat.byteskwargs(opts)
6927 ui.pager(b'summary')
6932 ui.pager(b'summary')
6928 ctx = repo[None]
6933 ctx = repo[None]
6929 parents = ctx.parents()
6934 parents = ctx.parents()
6930 pnode = parents[0].node()
6935 pnode = parents[0].node()
6931 marks = []
6936 marks = []
6932
6937
6933 try:
6938 try:
6934 ms = mergemod.mergestate.read(repo)
6939 ms = mergemod.mergestate.read(repo)
6935 except error.UnsupportedMergeRecords as e:
6940 except error.UnsupportedMergeRecords as e:
6936 s = b' '.join(e.recordtypes)
6941 s = b' '.join(e.recordtypes)
6937 ui.warn(
6942 ui.warn(
6938 _(b'warning: merge state has unsupported record types: %s\n') % s
6943 _(b'warning: merge state has unsupported record types: %s\n') % s
6939 )
6944 )
6940 unresolved = []
6945 unresolved = []
6941 else:
6946 else:
6942 unresolved = list(ms.unresolved())
6947 unresolved = list(ms.unresolved())
6943
6948
6944 for p in parents:
6949 for p in parents:
6945 # label with log.changeset (instead of log.parent) since this
6950 # label with log.changeset (instead of log.parent) since this
6946 # shows a working directory parent *changeset*:
6951 # shows a working directory parent *changeset*:
6947 # i18n: column positioning for "hg summary"
6952 # i18n: column positioning for "hg summary"
6948 ui.write(
6953 ui.write(
6949 _(b'parent: %d:%s ') % (p.rev(), p),
6954 _(b'parent: %d:%s ') % (p.rev(), p),
6950 label=logcmdutil.changesetlabels(p),
6955 label=logcmdutil.changesetlabels(p),
6951 )
6956 )
6952 ui.write(b' '.join(p.tags()), label=b'log.tag')
6957 ui.write(b' '.join(p.tags()), label=b'log.tag')
6953 if p.bookmarks():
6958 if p.bookmarks():
6954 marks.extend(p.bookmarks())
6959 marks.extend(p.bookmarks())
6955 if p.rev() == -1:
6960 if p.rev() == -1:
6956 if not len(repo):
6961 if not len(repo):
6957 ui.write(_(b' (empty repository)'))
6962 ui.write(_(b' (empty repository)'))
6958 else:
6963 else:
6959 ui.write(_(b' (no revision checked out)'))
6964 ui.write(_(b' (no revision checked out)'))
6960 if p.obsolete():
6965 if p.obsolete():
6961 ui.write(_(b' (obsolete)'))
6966 ui.write(_(b' (obsolete)'))
6962 if p.isunstable():
6967 if p.isunstable():
6963 instabilities = (
6968 instabilities = (
6964 ui.label(instability, b'trouble.%s' % instability)
6969 ui.label(instability, b'trouble.%s' % instability)
6965 for instability in p.instabilities()
6970 for instability in p.instabilities()
6966 )
6971 )
6967 ui.write(b' (' + b', '.join(instabilities) + b')')
6972 ui.write(b' (' + b', '.join(instabilities) + b')')
6968 ui.write(b'\n')
6973 ui.write(b'\n')
6969 if p.description():
6974 if p.description():
6970 ui.status(
6975 ui.status(
6971 b' ' + p.description().splitlines()[0].strip() + b'\n',
6976 b' ' + p.description().splitlines()[0].strip() + b'\n',
6972 label=b'log.summary',
6977 label=b'log.summary',
6973 )
6978 )
6974
6979
6975 branch = ctx.branch()
6980 branch = ctx.branch()
6976 bheads = repo.branchheads(branch)
6981 bheads = repo.branchheads(branch)
6977 # i18n: column positioning for "hg summary"
6982 # i18n: column positioning for "hg summary"
6978 m = _(b'branch: %s\n') % branch
6983 m = _(b'branch: %s\n') % branch
6979 if branch != b'default':
6984 if branch != b'default':
6980 ui.write(m, label=b'log.branch')
6985 ui.write(m, label=b'log.branch')
6981 else:
6986 else:
6982 ui.status(m, label=b'log.branch')
6987 ui.status(m, label=b'log.branch')
6983
6988
6984 if marks:
6989 if marks:
6985 active = repo._activebookmark
6990 active = repo._activebookmark
6986 # i18n: column positioning for "hg summary"
6991 # i18n: column positioning for "hg summary"
6987 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6992 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6988 if active is not None:
6993 if active is not None:
6989 if active in marks:
6994 if active in marks:
6990 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6995 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6991 marks.remove(active)
6996 marks.remove(active)
6992 else:
6997 else:
6993 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6998 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6994 for m in marks:
6999 for m in marks:
6995 ui.write(b' ' + m, label=b'log.bookmark')
7000 ui.write(b' ' + m, label=b'log.bookmark')
6996 ui.write(b'\n', label=b'log.bookmark')
7001 ui.write(b'\n', label=b'log.bookmark')
6997
7002
6998 status = repo.status(unknown=True)
7003 status = repo.status(unknown=True)
6999
7004
7000 c = repo.dirstate.copies()
7005 c = repo.dirstate.copies()
7001 copied, renamed = [], []
7006 copied, renamed = [], []
7002 for d, s in pycompat.iteritems(c):
7007 for d, s in pycompat.iteritems(c):
7003 if s in status.removed:
7008 if s in status.removed:
7004 status.removed.remove(s)
7009 status.removed.remove(s)
7005 renamed.append(d)
7010 renamed.append(d)
7006 else:
7011 else:
7007 copied.append(d)
7012 copied.append(d)
7008 if d in status.added:
7013 if d in status.added:
7009 status.added.remove(d)
7014 status.added.remove(d)
7010
7015
7011 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7016 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7012
7017
7013 labels = [
7018 labels = [
7014 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7019 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7015 (ui.label(_(b'%d added'), b'status.added'), status.added),
7020 (ui.label(_(b'%d added'), b'status.added'), status.added),
7016 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7021 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7017 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7022 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7018 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7023 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7019 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7024 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7020 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7025 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7021 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7026 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7022 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7027 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7023 ]
7028 ]
7024 t = []
7029 t = []
7025 for l, s in labels:
7030 for l, s in labels:
7026 if s:
7031 if s:
7027 t.append(l % len(s))
7032 t.append(l % len(s))
7028
7033
7029 t = b', '.join(t)
7034 t = b', '.join(t)
7030 cleanworkdir = False
7035 cleanworkdir = False
7031
7036
7032 if repo.vfs.exists(b'graftstate'):
7037 if repo.vfs.exists(b'graftstate'):
7033 t += _(b' (graft in progress)')
7038 t += _(b' (graft in progress)')
7034 if repo.vfs.exists(b'updatestate'):
7039 if repo.vfs.exists(b'updatestate'):
7035 t += _(b' (interrupted update)')
7040 t += _(b' (interrupted update)')
7036 elif len(parents) > 1:
7041 elif len(parents) > 1:
7037 t += _(b' (merge)')
7042 t += _(b' (merge)')
7038 elif branch != parents[0].branch():
7043 elif branch != parents[0].branch():
7039 t += _(b' (new branch)')
7044 t += _(b' (new branch)')
7040 elif parents[0].closesbranch() and pnode in repo.branchheads(
7045 elif parents[0].closesbranch() and pnode in repo.branchheads(
7041 branch, closed=True
7046 branch, closed=True
7042 ):
7047 ):
7043 t += _(b' (head closed)')
7048 t += _(b' (head closed)')
7044 elif not (
7049 elif not (
7045 status.modified
7050 status.modified
7046 or status.added
7051 or status.added
7047 or status.removed
7052 or status.removed
7048 or renamed
7053 or renamed
7049 or copied
7054 or copied
7050 or subs
7055 or subs
7051 ):
7056 ):
7052 t += _(b' (clean)')
7057 t += _(b' (clean)')
7053 cleanworkdir = True
7058 cleanworkdir = True
7054 elif pnode not in bheads:
7059 elif pnode not in bheads:
7055 t += _(b' (new branch head)')
7060 t += _(b' (new branch head)')
7056
7061
7057 if parents:
7062 if parents:
7058 pendingphase = max(p.phase() for p in parents)
7063 pendingphase = max(p.phase() for p in parents)
7059 else:
7064 else:
7060 pendingphase = phases.public
7065 pendingphase = phases.public
7061
7066
7062 if pendingphase > phases.newcommitphase(ui):
7067 if pendingphase > phases.newcommitphase(ui):
7063 t += b' (%s)' % phases.phasenames[pendingphase]
7068 t += b' (%s)' % phases.phasenames[pendingphase]
7064
7069
7065 if cleanworkdir:
7070 if cleanworkdir:
7066 # i18n: column positioning for "hg summary"
7071 # i18n: column positioning for "hg summary"
7067 ui.status(_(b'commit: %s\n') % t.strip())
7072 ui.status(_(b'commit: %s\n') % t.strip())
7068 else:
7073 else:
7069 # i18n: column positioning for "hg summary"
7074 # i18n: column positioning for "hg summary"
7070 ui.write(_(b'commit: %s\n') % t.strip())
7075 ui.write(_(b'commit: %s\n') % t.strip())
7071
7076
7072 # all ancestors of branch heads - all ancestors of parent = new csets
7077 # all ancestors of branch heads - all ancestors of parent = new csets
7073 new = len(
7078 new = len(
7074 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7079 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7075 )
7080 )
7076
7081
7077 if new == 0:
7082 if new == 0:
7078 # i18n: column positioning for "hg summary"
7083 # i18n: column positioning for "hg summary"
7079 ui.status(_(b'update: (current)\n'))
7084 ui.status(_(b'update: (current)\n'))
7080 elif pnode not in bheads:
7085 elif pnode not in bheads:
7081 # i18n: column positioning for "hg summary"
7086 # i18n: column positioning for "hg summary"
7082 ui.write(_(b'update: %d new changesets (update)\n') % new)
7087 ui.write(_(b'update: %d new changesets (update)\n') % new)
7083 else:
7088 else:
7084 # i18n: column positioning for "hg summary"
7089 # i18n: column positioning for "hg summary"
7085 ui.write(
7090 ui.write(
7086 _(b'update: %d new changesets, %d branch heads (merge)\n')
7091 _(b'update: %d new changesets, %d branch heads (merge)\n')
7087 % (new, len(bheads))
7092 % (new, len(bheads))
7088 )
7093 )
7089
7094
7090 t = []
7095 t = []
7091 draft = len(repo.revs(b'draft()'))
7096 draft = len(repo.revs(b'draft()'))
7092 if draft:
7097 if draft:
7093 t.append(_(b'%d draft') % draft)
7098 t.append(_(b'%d draft') % draft)
7094 secret = len(repo.revs(b'secret()'))
7099 secret = len(repo.revs(b'secret()'))
7095 if secret:
7100 if secret:
7096 t.append(_(b'%d secret') % secret)
7101 t.append(_(b'%d secret') % secret)
7097
7102
7098 if draft or secret:
7103 if draft or secret:
7099 ui.status(_(b'phases: %s\n') % b', '.join(t))
7104 ui.status(_(b'phases: %s\n') % b', '.join(t))
7100
7105
7101 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7106 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7102 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7107 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7103 numtrouble = len(repo.revs(trouble + b"()"))
7108 numtrouble = len(repo.revs(trouble + b"()"))
7104 # We write all the possibilities to ease translation
7109 # We write all the possibilities to ease translation
7105 troublemsg = {
7110 troublemsg = {
7106 b"orphan": _(b"orphan: %d changesets"),
7111 b"orphan": _(b"orphan: %d changesets"),
7107 b"contentdivergent": _(b"content-divergent: %d changesets"),
7112 b"contentdivergent": _(b"content-divergent: %d changesets"),
7108 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7113 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7109 }
7114 }
7110 if numtrouble > 0:
7115 if numtrouble > 0:
7111 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7116 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7112
7117
7113 cmdutil.summaryhooks(ui, repo)
7118 cmdutil.summaryhooks(ui, repo)
7114
7119
7115 if opts.get(b'remote'):
7120 if opts.get(b'remote'):
7116 needsincoming, needsoutgoing = True, True
7121 needsincoming, needsoutgoing = True, True
7117 else:
7122 else:
7118 needsincoming, needsoutgoing = False, False
7123 needsincoming, needsoutgoing = False, False
7119 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7124 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7120 if i:
7125 if i:
7121 needsincoming = True
7126 needsincoming = True
7122 if o:
7127 if o:
7123 needsoutgoing = True
7128 needsoutgoing = True
7124 if not needsincoming and not needsoutgoing:
7129 if not needsincoming and not needsoutgoing:
7125 return
7130 return
7126
7131
7127 def getincoming():
7132 def getincoming():
7128 source, branches = hg.parseurl(ui.expandpath(b'default'))
7133 source, branches = hg.parseurl(ui.expandpath(b'default'))
7129 sbranch = branches[0]
7134 sbranch = branches[0]
7130 try:
7135 try:
7131 other = hg.peer(repo, {}, source)
7136 other = hg.peer(repo, {}, source)
7132 except error.RepoError:
7137 except error.RepoError:
7133 if opts.get(b'remote'):
7138 if opts.get(b'remote'):
7134 raise
7139 raise
7135 return source, sbranch, None, None, None
7140 return source, sbranch, None, None, None
7136 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7141 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7137 if revs:
7142 if revs:
7138 revs = [other.lookup(rev) for rev in revs]
7143 revs = [other.lookup(rev) for rev in revs]
7139 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7144 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7140 repo.ui.pushbuffer()
7145 repo.ui.pushbuffer()
7141 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7146 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7142 repo.ui.popbuffer()
7147 repo.ui.popbuffer()
7143 return source, sbranch, other, commoninc, commoninc[1]
7148 return source, sbranch, other, commoninc, commoninc[1]
7144
7149
7145 if needsincoming:
7150 if needsincoming:
7146 source, sbranch, sother, commoninc, incoming = getincoming()
7151 source, sbranch, sother, commoninc, incoming = getincoming()
7147 else:
7152 else:
7148 source = sbranch = sother = commoninc = incoming = None
7153 source = sbranch = sother = commoninc = incoming = None
7149
7154
7150 def getoutgoing():
7155 def getoutgoing():
7151 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7156 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7152 dbranch = branches[0]
7157 dbranch = branches[0]
7153 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7158 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7154 if source != dest:
7159 if source != dest:
7155 try:
7160 try:
7156 dother = hg.peer(repo, {}, dest)
7161 dother = hg.peer(repo, {}, dest)
7157 except error.RepoError:
7162 except error.RepoError:
7158 if opts.get(b'remote'):
7163 if opts.get(b'remote'):
7159 raise
7164 raise
7160 return dest, dbranch, None, None
7165 return dest, dbranch, None, None
7161 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7166 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7162 elif sother is None:
7167 elif sother is None:
7163 # there is no explicit destination peer, but source one is invalid
7168 # there is no explicit destination peer, but source one is invalid
7164 return dest, dbranch, None, None
7169 return dest, dbranch, None, None
7165 else:
7170 else:
7166 dother = sother
7171 dother = sother
7167 if source != dest or (sbranch is not None and sbranch != dbranch):
7172 if source != dest or (sbranch is not None and sbranch != dbranch):
7168 common = None
7173 common = None
7169 else:
7174 else:
7170 common = commoninc
7175 common = commoninc
7171 if revs:
7176 if revs:
7172 revs = [repo.lookup(rev) for rev in revs]
7177 revs = [repo.lookup(rev) for rev in revs]
7173 repo.ui.pushbuffer()
7178 repo.ui.pushbuffer()
7174 outgoing = discovery.findcommonoutgoing(
7179 outgoing = discovery.findcommonoutgoing(
7175 repo, dother, onlyheads=revs, commoninc=common
7180 repo, dother, onlyheads=revs, commoninc=common
7176 )
7181 )
7177 repo.ui.popbuffer()
7182 repo.ui.popbuffer()
7178 return dest, dbranch, dother, outgoing
7183 return dest, dbranch, dother, outgoing
7179
7184
7180 if needsoutgoing:
7185 if needsoutgoing:
7181 dest, dbranch, dother, outgoing = getoutgoing()
7186 dest, dbranch, dother, outgoing = getoutgoing()
7182 else:
7187 else:
7183 dest = dbranch = dother = outgoing = None
7188 dest = dbranch = dother = outgoing = None
7184
7189
7185 if opts.get(b'remote'):
7190 if opts.get(b'remote'):
7186 t = []
7191 t = []
7187 if incoming:
7192 if incoming:
7188 t.append(_(b'1 or more incoming'))
7193 t.append(_(b'1 or more incoming'))
7189 o = outgoing.missing
7194 o = outgoing.missing
7190 if o:
7195 if o:
7191 t.append(_(b'%d outgoing') % len(o))
7196 t.append(_(b'%d outgoing') % len(o))
7192 other = dother or sother
7197 other = dother or sother
7193 if b'bookmarks' in other.listkeys(b'namespaces'):
7198 if b'bookmarks' in other.listkeys(b'namespaces'):
7194 counts = bookmarks.summary(repo, other)
7199 counts = bookmarks.summary(repo, other)
7195 if counts[0] > 0:
7200 if counts[0] > 0:
7196 t.append(_(b'%d incoming bookmarks') % counts[0])
7201 t.append(_(b'%d incoming bookmarks') % counts[0])
7197 if counts[1] > 0:
7202 if counts[1] > 0:
7198 t.append(_(b'%d outgoing bookmarks') % counts[1])
7203 t.append(_(b'%d outgoing bookmarks') % counts[1])
7199
7204
7200 if t:
7205 if t:
7201 # i18n: column positioning for "hg summary"
7206 # i18n: column positioning for "hg summary"
7202 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7207 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7203 else:
7208 else:
7204 # i18n: column positioning for "hg summary"
7209 # i18n: column positioning for "hg summary"
7205 ui.status(_(b'remote: (synced)\n'))
7210 ui.status(_(b'remote: (synced)\n'))
7206
7211
7207 cmdutil.summaryremotehooks(
7212 cmdutil.summaryremotehooks(
7208 ui,
7213 ui,
7209 repo,
7214 repo,
7210 opts,
7215 opts,
7211 (
7216 (
7212 (source, sbranch, sother, commoninc),
7217 (source, sbranch, sother, commoninc),
7213 (dest, dbranch, dother, outgoing),
7218 (dest, dbranch, dother, outgoing),
7214 ),
7219 ),
7215 )
7220 )
7216
7221
7217
7222
7218 @command(
7223 @command(
7219 b'tag',
7224 b'tag',
7220 [
7225 [
7221 (b'f', b'force', None, _(b'force tag')),
7226 (b'f', b'force', None, _(b'force tag')),
7222 (b'l', b'local', None, _(b'make the tag local')),
7227 (b'l', b'local', None, _(b'make the tag local')),
7223 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7228 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7224 (b'', b'remove', None, _(b'remove a tag')),
7229 (b'', b'remove', None, _(b'remove a tag')),
7225 # -l/--local is already there, commitopts cannot be used
7230 # -l/--local is already there, commitopts cannot be used
7226 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7231 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7227 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7232 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7228 ]
7233 ]
7229 + commitopts2,
7234 + commitopts2,
7230 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7235 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7231 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7236 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7232 )
7237 )
7233 def tag(ui, repo, name1, *names, **opts):
7238 def tag(ui, repo, name1, *names, **opts):
7234 """add one or more tags for the current or given revision
7239 """add one or more tags for the current or given revision
7235
7240
7236 Name a particular revision using <name>.
7241 Name a particular revision using <name>.
7237
7242
7238 Tags are used to name particular revisions of the repository and are
7243 Tags are used to name particular revisions of the repository and are
7239 very useful to compare different revisions, to go back to significant
7244 very useful to compare different revisions, to go back to significant
7240 earlier versions or to mark branch points as releases, etc. Changing
7245 earlier versions or to mark branch points as releases, etc. Changing
7241 an existing tag is normally disallowed; use -f/--force to override.
7246 an existing tag is normally disallowed; use -f/--force to override.
7242
7247
7243 If no revision is given, the parent of the working directory is
7248 If no revision is given, the parent of the working directory is
7244 used.
7249 used.
7245
7250
7246 To facilitate version control, distribution, and merging of tags,
7251 To facilitate version control, distribution, and merging of tags,
7247 they are stored as a file named ".hgtags" which is managed similarly
7252 they are stored as a file named ".hgtags" which is managed similarly
7248 to other project files and can be hand-edited if necessary. This
7253 to other project files and can be hand-edited if necessary. This
7249 also means that tagging creates a new commit. The file
7254 also means that tagging creates a new commit. The file
7250 ".hg/localtags" is used for local tags (not shared among
7255 ".hg/localtags" is used for local tags (not shared among
7251 repositories).
7256 repositories).
7252
7257
7253 Tag commits are usually made at the head of a branch. If the parent
7258 Tag commits are usually made at the head of a branch. If the parent
7254 of the working directory is not a branch head, :hg:`tag` aborts; use
7259 of the working directory is not a branch head, :hg:`tag` aborts; use
7255 -f/--force to force the tag commit to be based on a non-head
7260 -f/--force to force the tag commit to be based on a non-head
7256 changeset.
7261 changeset.
7257
7262
7258 See :hg:`help dates` for a list of formats valid for -d/--date.
7263 See :hg:`help dates` for a list of formats valid for -d/--date.
7259
7264
7260 Since tag names have priority over branch names during revision
7265 Since tag names have priority over branch names during revision
7261 lookup, using an existing branch name as a tag name is discouraged.
7266 lookup, using an existing branch name as a tag name is discouraged.
7262
7267
7263 Returns 0 on success.
7268 Returns 0 on success.
7264 """
7269 """
7265 opts = pycompat.byteskwargs(opts)
7270 opts = pycompat.byteskwargs(opts)
7266 with repo.wlock(), repo.lock():
7271 with repo.wlock(), repo.lock():
7267 rev_ = b"."
7272 rev_ = b"."
7268 names = [t.strip() for t in (name1,) + names]
7273 names = [t.strip() for t in (name1,) + names]
7269 if len(names) != len(set(names)):
7274 if len(names) != len(set(names)):
7270 raise error.Abort(_(b'tag names must be unique'))
7275 raise error.Abort(_(b'tag names must be unique'))
7271 for n in names:
7276 for n in names:
7272 scmutil.checknewlabel(repo, n, b'tag')
7277 scmutil.checknewlabel(repo, n, b'tag')
7273 if not n:
7278 if not n:
7274 raise error.Abort(
7279 raise error.Abort(
7275 _(b'tag names cannot consist entirely of whitespace')
7280 _(b'tag names cannot consist entirely of whitespace')
7276 )
7281 )
7277 if opts.get(b'rev') and opts.get(b'remove'):
7282 if opts.get(b'rev') and opts.get(b'remove'):
7278 raise error.Abort(_(b"--rev and --remove are incompatible"))
7283 raise error.Abort(_(b"--rev and --remove are incompatible"))
7279 if opts.get(b'rev'):
7284 if opts.get(b'rev'):
7280 rev_ = opts[b'rev']
7285 rev_ = opts[b'rev']
7281 message = opts.get(b'message')
7286 message = opts.get(b'message')
7282 if opts.get(b'remove'):
7287 if opts.get(b'remove'):
7283 if opts.get(b'local'):
7288 if opts.get(b'local'):
7284 expectedtype = b'local'
7289 expectedtype = b'local'
7285 else:
7290 else:
7286 expectedtype = b'global'
7291 expectedtype = b'global'
7287
7292
7288 for n in names:
7293 for n in names:
7289 if repo.tagtype(n) == b'global':
7294 if repo.tagtype(n) == b'global':
7290 alltags = tagsmod.findglobaltags(ui, repo)
7295 alltags = tagsmod.findglobaltags(ui, repo)
7291 if alltags[n][0] == nullid:
7296 if alltags[n][0] == nullid:
7292 raise error.Abort(_(b"tag '%s' is already removed") % n)
7297 raise error.Abort(_(b"tag '%s' is already removed") % n)
7293 if not repo.tagtype(n):
7298 if not repo.tagtype(n):
7294 raise error.Abort(_(b"tag '%s' does not exist") % n)
7299 raise error.Abort(_(b"tag '%s' does not exist") % n)
7295 if repo.tagtype(n) != expectedtype:
7300 if repo.tagtype(n) != expectedtype:
7296 if expectedtype == b'global':
7301 if expectedtype == b'global':
7297 raise error.Abort(
7302 raise error.Abort(
7298 _(b"tag '%s' is not a global tag") % n
7303 _(b"tag '%s' is not a global tag") % n
7299 )
7304 )
7300 else:
7305 else:
7301 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7306 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7302 rev_ = b'null'
7307 rev_ = b'null'
7303 if not message:
7308 if not message:
7304 # we don't translate commit messages
7309 # we don't translate commit messages
7305 message = b'Removed tag %s' % b', '.join(names)
7310 message = b'Removed tag %s' % b', '.join(names)
7306 elif not opts.get(b'force'):
7311 elif not opts.get(b'force'):
7307 for n in names:
7312 for n in names:
7308 if n in repo.tags():
7313 if n in repo.tags():
7309 raise error.Abort(
7314 raise error.Abort(
7310 _(b"tag '%s' already exists (use -f to force)") % n
7315 _(b"tag '%s' already exists (use -f to force)") % n
7311 )
7316 )
7312 if not opts.get(b'local'):
7317 if not opts.get(b'local'):
7313 p1, p2 = repo.dirstate.parents()
7318 p1, p2 = repo.dirstate.parents()
7314 if p2 != nullid:
7319 if p2 != nullid:
7315 raise error.Abort(_(b'uncommitted merge'))
7320 raise error.Abort(_(b'uncommitted merge'))
7316 bheads = repo.branchheads()
7321 bheads = repo.branchheads()
7317 if not opts.get(b'force') and bheads and p1 not in bheads:
7322 if not opts.get(b'force') and bheads and p1 not in bheads:
7318 raise error.Abort(
7323 raise error.Abort(
7319 _(
7324 _(
7320 b'working directory is not at a branch head '
7325 b'working directory is not at a branch head '
7321 b'(use -f to force)'
7326 b'(use -f to force)'
7322 )
7327 )
7323 )
7328 )
7324 node = scmutil.revsingle(repo, rev_).node()
7329 node = scmutil.revsingle(repo, rev_).node()
7325
7330
7326 if not message:
7331 if not message:
7327 # we don't translate commit messages
7332 # we don't translate commit messages
7328 message = b'Added tag %s for changeset %s' % (
7333 message = b'Added tag %s for changeset %s' % (
7329 b', '.join(names),
7334 b', '.join(names),
7330 short(node),
7335 short(node),
7331 )
7336 )
7332
7337
7333 date = opts.get(b'date')
7338 date = opts.get(b'date')
7334 if date:
7339 if date:
7335 date = dateutil.parsedate(date)
7340 date = dateutil.parsedate(date)
7336
7341
7337 if opts.get(b'remove'):
7342 if opts.get(b'remove'):
7338 editform = b'tag.remove'
7343 editform = b'tag.remove'
7339 else:
7344 else:
7340 editform = b'tag.add'
7345 editform = b'tag.add'
7341 editor = cmdutil.getcommiteditor(
7346 editor = cmdutil.getcommiteditor(
7342 editform=editform, **pycompat.strkwargs(opts)
7347 editform=editform, **pycompat.strkwargs(opts)
7343 )
7348 )
7344
7349
7345 # don't allow tagging the null rev
7350 # don't allow tagging the null rev
7346 if (
7351 if (
7347 not opts.get(b'remove')
7352 not opts.get(b'remove')
7348 and scmutil.revsingle(repo, rev_).rev() == nullrev
7353 and scmutil.revsingle(repo, rev_).rev() == nullrev
7349 ):
7354 ):
7350 raise error.Abort(_(b"cannot tag null revision"))
7355 raise error.Abort(_(b"cannot tag null revision"))
7351
7356
7352 tagsmod.tag(
7357 tagsmod.tag(
7353 repo,
7358 repo,
7354 names,
7359 names,
7355 node,
7360 node,
7356 message,
7361 message,
7357 opts.get(b'local'),
7362 opts.get(b'local'),
7358 opts.get(b'user'),
7363 opts.get(b'user'),
7359 date,
7364 date,
7360 editor=editor,
7365 editor=editor,
7361 )
7366 )
7362
7367
7363
7368
7364 @command(
7369 @command(
7365 b'tags',
7370 b'tags',
7366 formatteropts,
7371 formatteropts,
7367 b'',
7372 b'',
7368 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7373 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7369 intents={INTENT_READONLY},
7374 intents={INTENT_READONLY},
7370 )
7375 )
7371 def tags(ui, repo, **opts):
7376 def tags(ui, repo, **opts):
7372 """list repository tags
7377 """list repository tags
7373
7378
7374 This lists both regular and local tags. When the -v/--verbose
7379 This lists both regular and local tags. When the -v/--verbose
7375 switch is used, a third column "local" is printed for local tags.
7380 switch is used, a third column "local" is printed for local tags.
7376 When the -q/--quiet switch is used, only the tag name is printed.
7381 When the -q/--quiet switch is used, only the tag name is printed.
7377
7382
7378 .. container:: verbose
7383 .. container:: verbose
7379
7384
7380 Template:
7385 Template:
7381
7386
7382 The following keywords are supported in addition to the common template
7387 The following keywords are supported in addition to the common template
7383 keywords and functions such as ``{tag}``. See also
7388 keywords and functions such as ``{tag}``. See also
7384 :hg:`help templates`.
7389 :hg:`help templates`.
7385
7390
7386 :type: String. ``local`` for local tags.
7391 :type: String. ``local`` for local tags.
7387
7392
7388 Returns 0 on success.
7393 Returns 0 on success.
7389 """
7394 """
7390
7395
7391 opts = pycompat.byteskwargs(opts)
7396 opts = pycompat.byteskwargs(opts)
7392 ui.pager(b'tags')
7397 ui.pager(b'tags')
7393 fm = ui.formatter(b'tags', opts)
7398 fm = ui.formatter(b'tags', opts)
7394 hexfunc = fm.hexfunc
7399 hexfunc = fm.hexfunc
7395
7400
7396 for t, n in reversed(repo.tagslist()):
7401 for t, n in reversed(repo.tagslist()):
7397 hn = hexfunc(n)
7402 hn = hexfunc(n)
7398 label = b'tags.normal'
7403 label = b'tags.normal'
7399 tagtype = b''
7404 tagtype = b''
7400 if repo.tagtype(t) == b'local':
7405 if repo.tagtype(t) == b'local':
7401 label = b'tags.local'
7406 label = b'tags.local'
7402 tagtype = b'local'
7407 tagtype = b'local'
7403
7408
7404 fm.startitem()
7409 fm.startitem()
7405 fm.context(repo=repo)
7410 fm.context(repo=repo)
7406 fm.write(b'tag', b'%s', t, label=label)
7411 fm.write(b'tag', b'%s', t, label=label)
7407 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7412 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7408 fm.condwrite(
7413 fm.condwrite(
7409 not ui.quiet,
7414 not ui.quiet,
7410 b'rev node',
7415 b'rev node',
7411 fmt,
7416 fmt,
7412 repo.changelog.rev(n),
7417 repo.changelog.rev(n),
7413 hn,
7418 hn,
7414 label=label,
7419 label=label,
7415 )
7420 )
7416 fm.condwrite(
7421 fm.condwrite(
7417 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7422 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7418 )
7423 )
7419 fm.plain(b'\n')
7424 fm.plain(b'\n')
7420 fm.end()
7425 fm.end()
7421
7426
7422
7427
7423 @command(
7428 @command(
7424 b'tip',
7429 b'tip',
7425 [
7430 [
7426 (b'p', b'patch', None, _(b'show patch')),
7431 (b'p', b'patch', None, _(b'show patch')),
7427 (b'g', b'git', None, _(b'use git extended diff format')),
7432 (b'g', b'git', None, _(b'use git extended diff format')),
7428 ]
7433 ]
7429 + templateopts,
7434 + templateopts,
7430 _(b'[-p] [-g]'),
7435 _(b'[-p] [-g]'),
7431 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7436 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7432 )
7437 )
7433 def tip(ui, repo, **opts):
7438 def tip(ui, repo, **opts):
7434 """show the tip revision (DEPRECATED)
7439 """show the tip revision (DEPRECATED)
7435
7440
7436 The tip revision (usually just called the tip) is the changeset
7441 The tip revision (usually just called the tip) is the changeset
7437 most recently added to the repository (and therefore the most
7442 most recently added to the repository (and therefore the most
7438 recently changed head).
7443 recently changed head).
7439
7444
7440 If you have just made a commit, that commit will be the tip. If
7445 If you have just made a commit, that commit will be the tip. If
7441 you have just pulled changes from another repository, the tip of
7446 you have just pulled changes from another repository, the tip of
7442 that repository becomes the current tip. The "tip" tag is special
7447 that repository becomes the current tip. The "tip" tag is special
7443 and cannot be renamed or assigned to a different changeset.
7448 and cannot be renamed or assigned to a different changeset.
7444
7449
7445 This command is deprecated, please use :hg:`heads` instead.
7450 This command is deprecated, please use :hg:`heads` instead.
7446
7451
7447 Returns 0 on success.
7452 Returns 0 on success.
7448 """
7453 """
7449 opts = pycompat.byteskwargs(opts)
7454 opts = pycompat.byteskwargs(opts)
7450 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7455 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7451 displayer.show(repo[b'tip'])
7456 displayer.show(repo[b'tip'])
7452 displayer.close()
7457 displayer.close()
7453
7458
7454
7459
7455 @command(
7460 @command(
7456 b'unbundle',
7461 b'unbundle',
7457 [
7462 [
7458 (
7463 (
7459 b'u',
7464 b'u',
7460 b'update',
7465 b'update',
7461 None,
7466 None,
7462 _(b'update to new branch head if changesets were unbundled'),
7467 _(b'update to new branch head if changesets were unbundled'),
7463 )
7468 )
7464 ],
7469 ],
7465 _(b'[-u] FILE...'),
7470 _(b'[-u] FILE...'),
7466 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7471 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7467 )
7472 )
7468 def unbundle(ui, repo, fname1, *fnames, **opts):
7473 def unbundle(ui, repo, fname1, *fnames, **opts):
7469 """apply one or more bundle files
7474 """apply one or more bundle files
7470
7475
7471 Apply one or more bundle files generated by :hg:`bundle`.
7476 Apply one or more bundle files generated by :hg:`bundle`.
7472
7477
7473 Returns 0 on success, 1 if an update has unresolved files.
7478 Returns 0 on success, 1 if an update has unresolved files.
7474 """
7479 """
7475 fnames = (fname1,) + fnames
7480 fnames = (fname1,) + fnames
7476
7481
7477 with repo.lock():
7482 with repo.lock():
7478 for fname in fnames:
7483 for fname in fnames:
7479 f = hg.openpath(ui, fname)
7484 f = hg.openpath(ui, fname)
7480 gen = exchange.readbundle(ui, f, fname)
7485 gen = exchange.readbundle(ui, f, fname)
7481 if isinstance(gen, streamclone.streamcloneapplier):
7486 if isinstance(gen, streamclone.streamcloneapplier):
7482 raise error.Abort(
7487 raise error.Abort(
7483 _(
7488 _(
7484 b'packed bundles cannot be applied with '
7489 b'packed bundles cannot be applied with '
7485 b'"hg unbundle"'
7490 b'"hg unbundle"'
7486 ),
7491 ),
7487 hint=_(b'use "hg debugapplystreamclonebundle"'),
7492 hint=_(b'use "hg debugapplystreamclonebundle"'),
7488 )
7493 )
7489 url = b'bundle:' + fname
7494 url = b'bundle:' + fname
7490 try:
7495 try:
7491 txnname = b'unbundle'
7496 txnname = b'unbundle'
7492 if not isinstance(gen, bundle2.unbundle20):
7497 if not isinstance(gen, bundle2.unbundle20):
7493 txnname = b'unbundle\n%s' % util.hidepassword(url)
7498 txnname = b'unbundle\n%s' % util.hidepassword(url)
7494 with repo.transaction(txnname) as tr:
7499 with repo.transaction(txnname) as tr:
7495 op = bundle2.applybundle(
7500 op = bundle2.applybundle(
7496 repo, gen, tr, source=b'unbundle', url=url
7501 repo, gen, tr, source=b'unbundle', url=url
7497 )
7502 )
7498 except error.BundleUnknownFeatureError as exc:
7503 except error.BundleUnknownFeatureError as exc:
7499 raise error.Abort(
7504 raise error.Abort(
7500 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7505 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7501 hint=_(
7506 hint=_(
7502 b"see https://mercurial-scm.org/"
7507 b"see https://mercurial-scm.org/"
7503 b"wiki/BundleFeature for more "
7508 b"wiki/BundleFeature for more "
7504 b"information"
7509 b"information"
7505 ),
7510 ),
7506 )
7511 )
7507 modheads = bundle2.combinechangegroupresults(op)
7512 modheads = bundle2.combinechangegroupresults(op)
7508
7513
7509 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7514 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7510
7515
7511
7516
7512 @command(
7517 @command(
7513 b'unshelve',
7518 b'unshelve',
7514 [
7519 [
7515 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7520 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7516 (
7521 (
7517 b'c',
7522 b'c',
7518 b'continue',
7523 b'continue',
7519 None,
7524 None,
7520 _(b'continue an incomplete unshelve operation'),
7525 _(b'continue an incomplete unshelve operation'),
7521 ),
7526 ),
7522 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7527 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7523 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7528 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7524 (
7529 (
7525 b'n',
7530 b'n',
7526 b'name',
7531 b'name',
7527 b'',
7532 b'',
7528 _(b'restore shelved change with given name'),
7533 _(b'restore shelved change with given name'),
7529 _(b'NAME'),
7534 _(b'NAME'),
7530 ),
7535 ),
7531 (b't', b'tool', b'', _(b'specify merge tool')),
7536 (b't', b'tool', b'', _(b'specify merge tool')),
7532 (
7537 (
7533 b'',
7538 b'',
7534 b'date',
7539 b'date',
7535 b'',
7540 b'',
7536 _(b'set date for temporary commits (DEPRECATED)'),
7541 _(b'set date for temporary commits (DEPRECATED)'),
7537 _(b'DATE'),
7542 _(b'DATE'),
7538 ),
7543 ),
7539 ],
7544 ],
7540 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7545 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7541 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7546 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7542 )
7547 )
7543 def unshelve(ui, repo, *shelved, **opts):
7548 def unshelve(ui, repo, *shelved, **opts):
7544 """restore a shelved change to the working directory
7549 """restore a shelved change to the working directory
7545
7550
7546 This command accepts an optional name of a shelved change to
7551 This command accepts an optional name of a shelved change to
7547 restore. If none is given, the most recent shelved change is used.
7552 restore. If none is given, the most recent shelved change is used.
7548
7553
7549 If a shelved change is applied successfully, the bundle that
7554 If a shelved change is applied successfully, the bundle that
7550 contains the shelved changes is moved to a backup location
7555 contains the shelved changes is moved to a backup location
7551 (.hg/shelve-backup).
7556 (.hg/shelve-backup).
7552
7557
7553 Since you can restore a shelved change on top of an arbitrary
7558 Since you can restore a shelved change on top of an arbitrary
7554 commit, it is possible that unshelving will result in a conflict
7559 commit, it is possible that unshelving will result in a conflict
7555 between your changes and the commits you are unshelving onto. If
7560 between your changes and the commits you are unshelving onto. If
7556 this occurs, you must resolve the conflict, then use
7561 this occurs, you must resolve the conflict, then use
7557 ``--continue`` to complete the unshelve operation. (The bundle
7562 ``--continue`` to complete the unshelve operation. (The bundle
7558 will not be moved until you successfully complete the unshelve.)
7563 will not be moved until you successfully complete the unshelve.)
7559
7564
7560 (Alternatively, you can use ``--abort`` to abandon an unshelve
7565 (Alternatively, you can use ``--abort`` to abandon an unshelve
7561 that causes a conflict. This reverts the unshelved changes, and
7566 that causes a conflict. This reverts the unshelved changes, and
7562 leaves the bundle in place.)
7567 leaves the bundle in place.)
7563
7568
7564 If bare shelved change (without interactive, include and exclude
7569 If bare shelved change (without interactive, include and exclude
7565 option) was done on newly created branch it would restore branch
7570 option) was done on newly created branch it would restore branch
7566 information to the working directory.
7571 information to the working directory.
7567
7572
7568 After a successful unshelve, the shelved changes are stored in a
7573 After a successful unshelve, the shelved changes are stored in a
7569 backup directory. Only the N most recent backups are kept. N
7574 backup directory. Only the N most recent backups are kept. N
7570 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7575 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7571 configuration option.
7576 configuration option.
7572
7577
7573 .. container:: verbose
7578 .. container:: verbose
7574
7579
7575 Timestamp in seconds is used to decide order of backups. More
7580 Timestamp in seconds is used to decide order of backups. More
7576 than ``maxbackups`` backups are kept, if same timestamp
7581 than ``maxbackups`` backups are kept, if same timestamp
7577 prevents from deciding exact order of them, for safety.
7582 prevents from deciding exact order of them, for safety.
7578
7583
7579 Selected changes can be unshelved with ``--interactive`` flag.
7584 Selected changes can be unshelved with ``--interactive`` flag.
7580 The working directory is updated with the selected changes, and
7585 The working directory is updated with the selected changes, and
7581 only the unselected changes remain shelved.
7586 only the unselected changes remain shelved.
7582 Note: The whole shelve is applied to working directory first before
7587 Note: The whole shelve is applied to working directory first before
7583 running interactively. So, this will bring up all the conflicts between
7588 running interactively. So, this will bring up all the conflicts between
7584 working directory and the shelve, irrespective of which changes will be
7589 working directory and the shelve, irrespective of which changes will be
7585 unshelved.
7590 unshelved.
7586 """
7591 """
7587 with repo.wlock():
7592 with repo.wlock():
7588 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7593 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7589
7594
7590
7595
7591 statemod.addunfinished(
7596 statemod.addunfinished(
7592 b'unshelve',
7597 b'unshelve',
7593 fname=b'shelvedstate',
7598 fname=b'shelvedstate',
7594 continueflag=True,
7599 continueflag=True,
7595 abortfunc=shelvemod.hgabortunshelve,
7600 abortfunc=shelvemod.hgabortunshelve,
7596 continuefunc=shelvemod.hgcontinueunshelve,
7601 continuefunc=shelvemod.hgcontinueunshelve,
7597 cmdmsg=_(b'unshelve already in progress'),
7602 cmdmsg=_(b'unshelve already in progress'),
7598 )
7603 )
7599
7604
7600
7605
7601 @command(
7606 @command(
7602 b'update|up|checkout|co',
7607 b'update|up|checkout|co',
7603 [
7608 [
7604 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7609 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7605 (b'c', b'check', None, _(b'require clean working directory')),
7610 (b'c', b'check', None, _(b'require clean working directory')),
7606 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7611 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7607 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7612 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7608 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7613 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7609 ]
7614 ]
7610 + mergetoolopts,
7615 + mergetoolopts,
7611 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7616 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7612 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7617 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7613 helpbasic=True,
7618 helpbasic=True,
7614 )
7619 )
7615 def update(ui, repo, node=None, **opts):
7620 def update(ui, repo, node=None, **opts):
7616 """update working directory (or switch revisions)
7621 """update working directory (or switch revisions)
7617
7622
7618 Update the repository's working directory to the specified
7623 Update the repository's working directory to the specified
7619 changeset. If no changeset is specified, update to the tip of the
7624 changeset. If no changeset is specified, update to the tip of the
7620 current named branch and move the active bookmark (see :hg:`help
7625 current named branch and move the active bookmark (see :hg:`help
7621 bookmarks`).
7626 bookmarks`).
7622
7627
7623 Update sets the working directory's parent revision to the specified
7628 Update sets the working directory's parent revision to the specified
7624 changeset (see :hg:`help parents`).
7629 changeset (see :hg:`help parents`).
7625
7630
7626 If the changeset is not a descendant or ancestor of the working
7631 If the changeset is not a descendant or ancestor of the working
7627 directory's parent and there are uncommitted changes, the update is
7632 directory's parent and there are uncommitted changes, the update is
7628 aborted. With the -c/--check option, the working directory is checked
7633 aborted. With the -c/--check option, the working directory is checked
7629 for uncommitted changes; if none are found, the working directory is
7634 for uncommitted changes; if none are found, the working directory is
7630 updated to the specified changeset.
7635 updated to the specified changeset.
7631
7636
7632 .. container:: verbose
7637 .. container:: verbose
7633
7638
7634 The -C/--clean, -c/--check, and -m/--merge options control what
7639 The -C/--clean, -c/--check, and -m/--merge options control what
7635 happens if the working directory contains uncommitted changes.
7640 happens if the working directory contains uncommitted changes.
7636 At most of one of them can be specified.
7641 At most of one of them can be specified.
7637
7642
7638 1. If no option is specified, and if
7643 1. If no option is specified, and if
7639 the requested changeset is an ancestor or descendant of
7644 the requested changeset is an ancestor or descendant of
7640 the working directory's parent, the uncommitted changes
7645 the working directory's parent, the uncommitted changes
7641 are merged into the requested changeset and the merged
7646 are merged into the requested changeset and the merged
7642 result is left uncommitted. If the requested changeset is
7647 result is left uncommitted. If the requested changeset is
7643 not an ancestor or descendant (that is, it is on another
7648 not an ancestor or descendant (that is, it is on another
7644 branch), the update is aborted and the uncommitted changes
7649 branch), the update is aborted and the uncommitted changes
7645 are preserved.
7650 are preserved.
7646
7651
7647 2. With the -m/--merge option, the update is allowed even if the
7652 2. With the -m/--merge option, the update is allowed even if the
7648 requested changeset is not an ancestor or descendant of
7653 requested changeset is not an ancestor or descendant of
7649 the working directory's parent.
7654 the working directory's parent.
7650
7655
7651 3. With the -c/--check option, the update is aborted and the
7656 3. With the -c/--check option, the update is aborted and the
7652 uncommitted changes are preserved.
7657 uncommitted changes are preserved.
7653
7658
7654 4. With the -C/--clean option, uncommitted changes are discarded and
7659 4. With the -C/--clean option, uncommitted changes are discarded and
7655 the working directory is updated to the requested changeset.
7660 the working directory is updated to the requested changeset.
7656
7661
7657 To cancel an uncommitted merge (and lose your changes), use
7662 To cancel an uncommitted merge (and lose your changes), use
7658 :hg:`merge --abort`.
7663 :hg:`merge --abort`.
7659
7664
7660 Use null as the changeset to remove the working directory (like
7665 Use null as the changeset to remove the working directory (like
7661 :hg:`clone -U`).
7666 :hg:`clone -U`).
7662
7667
7663 If you want to revert just one file to an older revision, use
7668 If you want to revert just one file to an older revision, use
7664 :hg:`revert [-r REV] NAME`.
7669 :hg:`revert [-r REV] NAME`.
7665
7670
7666 See :hg:`help dates` for a list of formats valid for -d/--date.
7671 See :hg:`help dates` for a list of formats valid for -d/--date.
7667
7672
7668 Returns 0 on success, 1 if there are unresolved files.
7673 Returns 0 on success, 1 if there are unresolved files.
7669 """
7674 """
7670 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7675 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7671 rev = opts.get('rev')
7676 rev = opts.get('rev')
7672 date = opts.get('date')
7677 date = opts.get('date')
7673 clean = opts.get('clean')
7678 clean = opts.get('clean')
7674 check = opts.get('check')
7679 check = opts.get('check')
7675 merge = opts.get('merge')
7680 merge = opts.get('merge')
7676 if rev and node:
7681 if rev and node:
7677 raise error.Abort(_(b"please specify just one revision"))
7682 raise error.Abort(_(b"please specify just one revision"))
7678
7683
7679 if ui.configbool(b'commands', b'update.requiredest'):
7684 if ui.configbool(b'commands', b'update.requiredest'):
7680 if not node and not rev and not date:
7685 if not node and not rev and not date:
7681 raise error.Abort(
7686 raise error.Abort(
7682 _(b'you must specify a destination'),
7687 _(b'you must specify a destination'),
7683 hint=_(b'for example: hg update ".::"'),
7688 hint=_(b'for example: hg update ".::"'),
7684 )
7689 )
7685
7690
7686 if rev is None or rev == b'':
7691 if rev is None or rev == b'':
7687 rev = node
7692 rev = node
7688
7693
7689 if date and rev is not None:
7694 if date and rev is not None:
7690 raise error.Abort(_(b"you can't specify a revision and a date"))
7695 raise error.Abort(_(b"you can't specify a revision and a date"))
7691
7696
7692 updatecheck = None
7697 updatecheck = None
7693 if check:
7698 if check:
7694 updatecheck = b'abort'
7699 updatecheck = b'abort'
7695 elif merge:
7700 elif merge:
7696 updatecheck = b'none'
7701 updatecheck = b'none'
7697
7702
7698 with repo.wlock():
7703 with repo.wlock():
7699 cmdutil.clearunfinished(repo)
7704 cmdutil.clearunfinished(repo)
7700 if date:
7705 if date:
7701 rev = cmdutil.finddate(ui, repo, date)
7706 rev = cmdutil.finddate(ui, repo, date)
7702
7707
7703 # if we defined a bookmark, we have to remember the original name
7708 # if we defined a bookmark, we have to remember the original name
7704 brev = rev
7709 brev = rev
7705 if rev:
7710 if rev:
7706 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7707 ctx = scmutil.revsingle(repo, rev, default=None)
7712 ctx = scmutil.revsingle(repo, rev, default=None)
7708 rev = ctx.rev()
7713 rev = ctx.rev()
7709 hidden = ctx.hidden()
7714 hidden = ctx.hidden()
7710 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7715 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7711 with ui.configoverride(overrides, b'update'):
7716 with ui.configoverride(overrides, b'update'):
7712 ret = hg.updatetotally(
7717 ret = hg.updatetotally(
7713 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7718 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7714 )
7719 )
7715 if hidden:
7720 if hidden:
7716 ctxstr = ctx.hex()[:12]
7721 ctxstr = ctx.hex()[:12]
7717 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7722 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7718
7723
7719 if ctx.obsolete():
7724 if ctx.obsolete():
7720 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7725 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7721 ui.warn(b"(%s)\n" % obsfatemsg)
7726 ui.warn(b"(%s)\n" % obsfatemsg)
7722 return ret
7727 return ret
7723
7728
7724
7729
7725 @command(
7730 @command(
7726 b'verify',
7731 b'verify',
7727 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7732 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7728 helpcategory=command.CATEGORY_MAINTENANCE,
7733 helpcategory=command.CATEGORY_MAINTENANCE,
7729 )
7734 )
7730 def verify(ui, repo, **opts):
7735 def verify(ui, repo, **opts):
7731 """verify the integrity of the repository
7736 """verify the integrity of the repository
7732
7737
7733 Verify the integrity of the current repository.
7738 Verify the integrity of the current repository.
7734
7739
7735 This will perform an extensive check of the repository's
7740 This will perform an extensive check of the repository's
7736 integrity, validating the hashes and checksums of each entry in
7741 integrity, validating the hashes and checksums of each entry in
7737 the changelog, manifest, and tracked files, as well as the
7742 the changelog, manifest, and tracked files, as well as the
7738 integrity of their crosslinks and indices.
7743 integrity of their crosslinks and indices.
7739
7744
7740 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7745 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7741 for more information about recovery from corruption of the
7746 for more information about recovery from corruption of the
7742 repository.
7747 repository.
7743
7748
7744 Returns 0 on success, 1 if errors are encountered.
7749 Returns 0 on success, 1 if errors are encountered.
7745 """
7750 """
7746 opts = pycompat.byteskwargs(opts)
7751 opts = pycompat.byteskwargs(opts)
7747
7752
7748 level = None
7753 level = None
7749 if opts[b'full']:
7754 if opts[b'full']:
7750 level = verifymod.VERIFY_FULL
7755 level = verifymod.VERIFY_FULL
7751 return hg.verify(repo, level)
7756 return hg.verify(repo, level)
7752
7757
7753
7758
7754 @command(
7759 @command(
7755 b'version',
7760 b'version',
7756 [] + formatteropts,
7761 [] + formatteropts,
7757 helpcategory=command.CATEGORY_HELP,
7762 helpcategory=command.CATEGORY_HELP,
7758 norepo=True,
7763 norepo=True,
7759 intents={INTENT_READONLY},
7764 intents={INTENT_READONLY},
7760 )
7765 )
7761 def version_(ui, **opts):
7766 def version_(ui, **opts):
7762 """output version and copyright information
7767 """output version and copyright information
7763
7768
7764 .. container:: verbose
7769 .. container:: verbose
7765
7770
7766 Template:
7771 Template:
7767
7772
7768 The following keywords are supported. See also :hg:`help templates`.
7773 The following keywords are supported. See also :hg:`help templates`.
7769
7774
7770 :extensions: List of extensions.
7775 :extensions: List of extensions.
7771 :ver: String. Version number.
7776 :ver: String. Version number.
7772
7777
7773 And each entry of ``{extensions}`` provides the following sub-keywords
7778 And each entry of ``{extensions}`` provides the following sub-keywords
7774 in addition to ``{ver}``.
7779 in addition to ``{ver}``.
7775
7780
7776 :bundled: Boolean. True if included in the release.
7781 :bundled: Boolean. True if included in the release.
7777 :name: String. Extension name.
7782 :name: String. Extension name.
7778 """
7783 """
7779 opts = pycompat.byteskwargs(opts)
7784 opts = pycompat.byteskwargs(opts)
7780 if ui.verbose:
7785 if ui.verbose:
7781 ui.pager(b'version')
7786 ui.pager(b'version')
7782 fm = ui.formatter(b"version", opts)
7787 fm = ui.formatter(b"version", opts)
7783 fm.startitem()
7788 fm.startitem()
7784 fm.write(
7789 fm.write(
7785 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7790 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7786 )
7791 )
7787 license = _(
7792 license = _(
7788 b"(see https://mercurial-scm.org for more information)\n"
7793 b"(see https://mercurial-scm.org for more information)\n"
7789 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7794 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7790 b"This is free software; see the source for copying conditions. "
7795 b"This is free software; see the source for copying conditions. "
7791 b"There is NO\nwarranty; "
7796 b"There is NO\nwarranty; "
7792 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7797 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7793 )
7798 )
7794 if not ui.quiet:
7799 if not ui.quiet:
7795 fm.plain(license)
7800 fm.plain(license)
7796
7801
7797 if ui.verbose:
7802 if ui.verbose:
7798 fm.plain(_(b"\nEnabled extensions:\n\n"))
7803 fm.plain(_(b"\nEnabled extensions:\n\n"))
7799 # format names and versions into columns
7804 # format names and versions into columns
7800 names = []
7805 names = []
7801 vers = []
7806 vers = []
7802 isinternals = []
7807 isinternals = []
7803 for name, module in extensions.extensions():
7808 for name, module in extensions.extensions():
7804 names.append(name)
7809 names.append(name)
7805 vers.append(extensions.moduleversion(module) or None)
7810 vers.append(extensions.moduleversion(module) or None)
7806 isinternals.append(extensions.ismoduleinternal(module))
7811 isinternals.append(extensions.ismoduleinternal(module))
7807 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7812 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7808 if names:
7813 if names:
7809 namefmt = b" %%-%ds " % max(len(n) for n in names)
7814 namefmt = b" %%-%ds " % max(len(n) for n in names)
7810 places = [_(b"external"), _(b"internal")]
7815 places = [_(b"external"), _(b"internal")]
7811 for n, v, p in zip(names, vers, isinternals):
7816 for n, v, p in zip(names, vers, isinternals):
7812 fn.startitem()
7817 fn.startitem()
7813 fn.condwrite(ui.verbose, b"name", namefmt, n)
7818 fn.condwrite(ui.verbose, b"name", namefmt, n)
7814 if ui.verbose:
7819 if ui.verbose:
7815 fn.plain(b"%s " % places[p])
7820 fn.plain(b"%s " % places[p])
7816 fn.data(bundled=p)
7821 fn.data(bundled=p)
7817 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7822 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7818 if ui.verbose:
7823 if ui.verbose:
7819 fn.plain(b"\n")
7824 fn.plain(b"\n")
7820 fn.end()
7825 fn.end()
7821 fm.end()
7826 fm.end()
7822
7827
7823
7828
7824 def loadcmdtable(ui, name, cmdtable):
7829 def loadcmdtable(ui, name, cmdtable):
7825 """Load command functions from specified cmdtable
7830 """Load command functions from specified cmdtable
7826 """
7831 """
7827 overrides = [cmd for cmd in cmdtable if cmd in table]
7832 overrides = [cmd for cmd in cmdtable if cmd in table]
7828 if overrides:
7833 if overrides:
7829 ui.warn(
7834 ui.warn(
7830 _(b"extension '%s' overrides commands: %s\n")
7835 _(b"extension '%s' overrides commands: %s\n")
7831 % (name, b" ".join(overrides))
7836 % (name, b" ".join(overrides))
7832 )
7837 )
7833 table.update(cmdtable)
7838 table.update(cmdtable)
@@ -1,3804 +1,3812 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 os
11 import os
12 import random
12 import random
13 import sys
13 import sys
14 import time
14 import time
15 import weakref
15 import weakref
16
16
17 from .i18n import _
17 from .i18n import _
18 from .node import (
18 from .node import (
19 bin,
19 bin,
20 hex,
20 hex,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 short,
23 short,
24 )
24 )
25 from .pycompat import (
25 from .pycompat import (
26 delattr,
26 delattr,
27 getattr,
27 getattr,
28 )
28 )
29 from . import (
29 from . import (
30 bookmarks,
30 bookmarks,
31 branchmap,
31 branchmap,
32 bundle2,
32 bundle2,
33 changegroup,
33 changegroup,
34 color,
34 color,
35 context,
35 context,
36 dirstate,
36 dirstate,
37 dirstateguard,
37 dirstateguard,
38 discovery,
38 discovery,
39 encoding,
39 encoding,
40 error,
40 error,
41 exchange,
41 exchange,
42 extensions,
42 extensions,
43 filelog,
43 filelog,
44 hook,
44 hook,
45 lock as lockmod,
45 lock as lockmod,
46 match as matchmod,
46 match as matchmod,
47 merge as mergemod,
47 merge as mergemod,
48 mergeutil,
48 mergeutil,
49 namespaces,
49 namespaces,
50 narrowspec,
50 narrowspec,
51 obsolete,
51 obsolete,
52 pathutil,
52 pathutil,
53 phases,
53 phases,
54 pushkey,
54 pushkey,
55 pycompat,
55 pycompat,
56 rcutil,
56 rcutil,
57 repoview,
57 repoview,
58 revset,
58 revset,
59 revsetlang,
59 revsetlang,
60 scmutil,
60 scmutil,
61 sparse,
61 sparse,
62 store as storemod,
62 store as storemod,
63 subrepoutil,
63 subrepoutil,
64 tags as tagsmod,
64 tags as tagsmod,
65 transaction,
65 transaction,
66 txnutil,
66 txnutil,
67 util,
67 util,
68 vfs as vfsmod,
68 vfs as vfsmod,
69 )
69 )
70
70
71 from .interfaces import (
71 from .interfaces import (
72 repository,
72 repository,
73 util as interfaceutil,
73 util as interfaceutil,
74 )
74 )
75
75
76 from .utils import (
76 from .utils import (
77 hashutil,
77 hashutil,
78 procutil,
78 procutil,
79 stringutil,
79 stringutil,
80 )
80 )
81
81
82 from .revlogutils import constants as revlogconst
82 from .revlogutils import constants as revlogconst
83
83
84 release = lockmod.release
84 release = lockmod.release
85 urlerr = util.urlerr
85 urlerr = util.urlerr
86 urlreq = util.urlreq
86 urlreq = util.urlreq
87
87
88 # set of (path, vfs-location) tuples. vfs-location is:
88 # set of (path, vfs-location) tuples. vfs-location is:
89 # - 'plain for vfs relative paths
89 # - 'plain for vfs relative paths
90 # - '' for svfs relative paths
90 # - '' for svfs relative paths
91 _cachedfiles = set()
91 _cachedfiles = set()
92
92
93
93
94 class _basefilecache(scmutil.filecache):
94 class _basefilecache(scmutil.filecache):
95 """All filecache usage on repo are done for logic that should be unfiltered
95 """All filecache usage on repo are done for logic that should be unfiltered
96 """
96 """
97
97
98 def __get__(self, repo, type=None):
98 def __get__(self, repo, type=None):
99 if repo is None:
99 if repo is None:
100 return self
100 return self
101 # proxy to unfiltered __dict__ since filtered repo has no entry
101 # proxy to unfiltered __dict__ since filtered repo has no entry
102 unfi = repo.unfiltered()
102 unfi = repo.unfiltered()
103 try:
103 try:
104 return unfi.__dict__[self.sname]
104 return unfi.__dict__[self.sname]
105 except KeyError:
105 except KeyError:
106 pass
106 pass
107 return super(_basefilecache, self).__get__(unfi, type)
107 return super(_basefilecache, self).__get__(unfi, type)
108
108
109 def set(self, repo, value):
109 def set(self, repo, value):
110 return super(_basefilecache, self).set(repo.unfiltered(), value)
110 return super(_basefilecache, self).set(repo.unfiltered(), value)
111
111
112
112
113 class repofilecache(_basefilecache):
113 class repofilecache(_basefilecache):
114 """filecache for files in .hg but outside of .hg/store"""
114 """filecache for files in .hg but outside of .hg/store"""
115
115
116 def __init__(self, *paths):
116 def __init__(self, *paths):
117 super(repofilecache, self).__init__(*paths)
117 super(repofilecache, self).__init__(*paths)
118 for path in paths:
118 for path in paths:
119 _cachedfiles.add((path, b'plain'))
119 _cachedfiles.add((path, b'plain'))
120
120
121 def join(self, obj, fname):
121 def join(self, obj, fname):
122 return obj.vfs.join(fname)
122 return obj.vfs.join(fname)
123
123
124
124
125 class storecache(_basefilecache):
125 class storecache(_basefilecache):
126 """filecache for files in the store"""
126 """filecache for files in the store"""
127
127
128 def __init__(self, *paths):
128 def __init__(self, *paths):
129 super(storecache, self).__init__(*paths)
129 super(storecache, self).__init__(*paths)
130 for path in paths:
130 for path in paths:
131 _cachedfiles.add((path, b''))
131 _cachedfiles.add((path, b''))
132
132
133 def join(self, obj, fname):
133 def join(self, obj, fname):
134 return obj.sjoin(fname)
134 return obj.sjoin(fname)
135
135
136
136
137 class mixedrepostorecache(_basefilecache):
137 class mixedrepostorecache(_basefilecache):
138 """filecache for a mix files in .hg/store and outside"""
138 """filecache for a mix files in .hg/store and outside"""
139
139
140 def __init__(self, *pathsandlocations):
140 def __init__(self, *pathsandlocations):
141 # scmutil.filecache only uses the path for passing back into our
141 # scmutil.filecache only uses the path for passing back into our
142 # join(), so we can safely pass a list of paths and locations
142 # join(), so we can safely pass a list of paths and locations
143 super(mixedrepostorecache, self).__init__(*pathsandlocations)
143 super(mixedrepostorecache, self).__init__(*pathsandlocations)
144 _cachedfiles.update(pathsandlocations)
144 _cachedfiles.update(pathsandlocations)
145
145
146 def join(self, obj, fnameandlocation):
146 def join(self, obj, fnameandlocation):
147 fname, location = fnameandlocation
147 fname, location = fnameandlocation
148 if location == b'plain':
148 if location == b'plain':
149 return obj.vfs.join(fname)
149 return obj.vfs.join(fname)
150 else:
150 else:
151 if location != b'':
151 if location != b'':
152 raise error.ProgrammingError(
152 raise error.ProgrammingError(
153 b'unexpected location: %s' % location
153 b'unexpected location: %s' % location
154 )
154 )
155 return obj.sjoin(fname)
155 return obj.sjoin(fname)
156
156
157
157
158 def isfilecached(repo, name):
158 def isfilecached(repo, name):
159 """check if a repo has already cached "name" filecache-ed property
159 """check if a repo has already cached "name" filecache-ed property
160
160
161 This returns (cachedobj-or-None, iscached) tuple.
161 This returns (cachedobj-or-None, iscached) tuple.
162 """
162 """
163 cacheentry = repo.unfiltered()._filecache.get(name, None)
163 cacheentry = repo.unfiltered()._filecache.get(name, None)
164 if not cacheentry:
164 if not cacheentry:
165 return None, False
165 return None, False
166 return cacheentry.obj, True
166 return cacheentry.obj, True
167
167
168
168
169 class unfilteredpropertycache(util.propertycache):
169 class unfilteredpropertycache(util.propertycache):
170 """propertycache that apply to unfiltered repo only"""
170 """propertycache that apply to unfiltered repo only"""
171
171
172 def __get__(self, repo, type=None):
172 def __get__(self, repo, type=None):
173 unfi = repo.unfiltered()
173 unfi = repo.unfiltered()
174 if unfi is repo:
174 if unfi is repo:
175 return super(unfilteredpropertycache, self).__get__(unfi)
175 return super(unfilteredpropertycache, self).__get__(unfi)
176 return getattr(unfi, self.name)
176 return getattr(unfi, self.name)
177
177
178
178
179 class filteredpropertycache(util.propertycache):
179 class filteredpropertycache(util.propertycache):
180 """propertycache that must take filtering in account"""
180 """propertycache that must take filtering in account"""
181
181
182 def cachevalue(self, obj, value):
182 def cachevalue(self, obj, value):
183 object.__setattr__(obj, self.name, value)
183 object.__setattr__(obj, self.name, value)
184
184
185
185
186 def hasunfilteredcache(repo, name):
186 def hasunfilteredcache(repo, name):
187 """check if a repo has an unfilteredpropertycache value for <name>"""
187 """check if a repo has an unfilteredpropertycache value for <name>"""
188 return name in vars(repo.unfiltered())
188 return name in vars(repo.unfiltered())
189
189
190
190
191 def unfilteredmethod(orig):
191 def unfilteredmethod(orig):
192 """decorate method that always need to be run on unfiltered version"""
192 """decorate method that always need to be run on unfiltered version"""
193
193
194 def wrapper(repo, *args, **kwargs):
194 def wrapper(repo, *args, **kwargs):
195 return orig(repo.unfiltered(), *args, **kwargs)
195 return orig(repo.unfiltered(), *args, **kwargs)
196
196
197 return wrapper
197 return wrapper
198
198
199
199
200 moderncaps = {
200 moderncaps = {
201 b'lookup',
201 b'lookup',
202 b'branchmap',
202 b'branchmap',
203 b'pushkey',
203 b'pushkey',
204 b'known',
204 b'known',
205 b'getbundle',
205 b'getbundle',
206 b'unbundle',
206 b'unbundle',
207 }
207 }
208 legacycaps = moderncaps.union({b'changegroupsubset'})
208 legacycaps = moderncaps.union({b'changegroupsubset'})
209
209
210
210
211 @interfaceutil.implementer(repository.ipeercommandexecutor)
211 @interfaceutil.implementer(repository.ipeercommandexecutor)
212 class localcommandexecutor(object):
212 class localcommandexecutor(object):
213 def __init__(self, peer):
213 def __init__(self, peer):
214 self._peer = peer
214 self._peer = peer
215 self._sent = False
215 self._sent = False
216 self._closed = False
216 self._closed = False
217
217
218 def __enter__(self):
218 def __enter__(self):
219 return self
219 return self
220
220
221 def __exit__(self, exctype, excvalue, exctb):
221 def __exit__(self, exctype, excvalue, exctb):
222 self.close()
222 self.close()
223
223
224 def callcommand(self, command, args):
224 def callcommand(self, command, args):
225 if self._sent:
225 if self._sent:
226 raise error.ProgrammingError(
226 raise error.ProgrammingError(
227 b'callcommand() cannot be used after sendcommands()'
227 b'callcommand() cannot be used after sendcommands()'
228 )
228 )
229
229
230 if self._closed:
230 if self._closed:
231 raise error.ProgrammingError(
231 raise error.ProgrammingError(
232 b'callcommand() cannot be used after close()'
232 b'callcommand() cannot be used after close()'
233 )
233 )
234
234
235 # We don't need to support anything fancy. Just call the named
235 # We don't need to support anything fancy. Just call the named
236 # method on the peer and return a resolved future.
236 # method on the peer and return a resolved future.
237 fn = getattr(self._peer, pycompat.sysstr(command))
237 fn = getattr(self._peer, pycompat.sysstr(command))
238
238
239 f = pycompat.futures.Future()
239 f = pycompat.futures.Future()
240
240
241 try:
241 try:
242 result = fn(**pycompat.strkwargs(args))
242 result = fn(**pycompat.strkwargs(args))
243 except Exception:
243 except Exception:
244 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
244 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
245 else:
245 else:
246 f.set_result(result)
246 f.set_result(result)
247
247
248 return f
248 return f
249
249
250 def sendcommands(self):
250 def sendcommands(self):
251 self._sent = True
251 self._sent = True
252
252
253 def close(self):
253 def close(self):
254 self._closed = True
254 self._closed = True
255
255
256
256
257 @interfaceutil.implementer(repository.ipeercommands)
257 @interfaceutil.implementer(repository.ipeercommands)
258 class localpeer(repository.peer):
258 class localpeer(repository.peer):
259 '''peer for a local repo; reflects only the most recent API'''
259 '''peer for a local repo; reflects only the most recent API'''
260
260
261 def __init__(self, repo, caps=None):
261 def __init__(self, repo, caps=None):
262 super(localpeer, self).__init__()
262 super(localpeer, self).__init__()
263
263
264 if caps is None:
264 if caps is None:
265 caps = moderncaps.copy()
265 caps = moderncaps.copy()
266 self._repo = repo.filtered(b'served')
266 self._repo = repo.filtered(b'served')
267 self.ui = repo.ui
267 self.ui = repo.ui
268 self._caps = repo._restrictcapabilities(caps)
268 self._caps = repo._restrictcapabilities(caps)
269
269
270 # Begin of _basepeer interface.
270 # Begin of _basepeer interface.
271
271
272 def url(self):
272 def url(self):
273 return self._repo.url()
273 return self._repo.url()
274
274
275 def local(self):
275 def local(self):
276 return self._repo
276 return self._repo
277
277
278 def peer(self):
278 def peer(self):
279 return self
279 return self
280
280
281 def canpush(self):
281 def canpush(self):
282 return True
282 return True
283
283
284 def close(self):
284 def close(self):
285 self._repo.close()
285 self._repo.close()
286
286
287 # End of _basepeer interface.
287 # End of _basepeer interface.
288
288
289 # Begin of _basewirecommands interface.
289 # Begin of _basewirecommands interface.
290
290
291 def branchmap(self):
291 def branchmap(self):
292 return self._repo.branchmap()
292 return self._repo.branchmap()
293
293
294 def capabilities(self):
294 def capabilities(self):
295 return self._caps
295 return self._caps
296
296
297 def clonebundles(self):
297 def clonebundles(self):
298 return self._repo.tryread(b'clonebundles.manifest')
298 return self._repo.tryread(b'clonebundles.manifest')
299
299
300 def debugwireargs(self, one, two, three=None, four=None, five=None):
300 def debugwireargs(self, one, two, three=None, four=None, five=None):
301 """Used to test argument passing over the wire"""
301 """Used to test argument passing over the wire"""
302 return b"%s %s %s %s %s" % (
302 return b"%s %s %s %s %s" % (
303 one,
303 one,
304 two,
304 two,
305 pycompat.bytestr(three),
305 pycompat.bytestr(three),
306 pycompat.bytestr(four),
306 pycompat.bytestr(four),
307 pycompat.bytestr(five),
307 pycompat.bytestr(five),
308 )
308 )
309
309
310 def getbundle(
310 def getbundle(
311 self, source, heads=None, common=None, bundlecaps=None, **kwargs
311 self, source, heads=None, common=None, bundlecaps=None, **kwargs
312 ):
312 ):
313 chunks = exchange.getbundlechunks(
313 chunks = exchange.getbundlechunks(
314 self._repo,
314 self._repo,
315 source,
315 source,
316 heads=heads,
316 heads=heads,
317 common=common,
317 common=common,
318 bundlecaps=bundlecaps,
318 bundlecaps=bundlecaps,
319 **kwargs
319 **kwargs
320 )[1]
320 )[1]
321 cb = util.chunkbuffer(chunks)
321 cb = util.chunkbuffer(chunks)
322
322
323 if exchange.bundle2requested(bundlecaps):
323 if exchange.bundle2requested(bundlecaps):
324 # When requesting a bundle2, getbundle returns a stream to make the
324 # When requesting a bundle2, getbundle returns a stream to make the
325 # wire level function happier. We need to build a proper object
325 # wire level function happier. We need to build a proper object
326 # from it in local peer.
326 # from it in local peer.
327 return bundle2.getunbundler(self.ui, cb)
327 return bundle2.getunbundler(self.ui, cb)
328 else:
328 else:
329 return changegroup.getunbundler(b'01', cb, None)
329 return changegroup.getunbundler(b'01', cb, None)
330
330
331 def heads(self):
331 def heads(self):
332 return self._repo.heads()
332 return self._repo.heads()
333
333
334 def known(self, nodes):
334 def known(self, nodes):
335 return self._repo.known(nodes)
335 return self._repo.known(nodes)
336
336
337 def listkeys(self, namespace):
337 def listkeys(self, namespace):
338 return self._repo.listkeys(namespace)
338 return self._repo.listkeys(namespace)
339
339
340 def lookup(self, key):
340 def lookup(self, key):
341 return self._repo.lookup(key)
341 return self._repo.lookup(key)
342
342
343 def pushkey(self, namespace, key, old, new):
343 def pushkey(self, namespace, key, old, new):
344 return self._repo.pushkey(namespace, key, old, new)
344 return self._repo.pushkey(namespace, key, old, new)
345
345
346 def stream_out(self):
346 def stream_out(self):
347 raise error.Abort(_(b'cannot perform stream clone against local peer'))
347 raise error.Abort(_(b'cannot perform stream clone against local peer'))
348
348
349 def unbundle(self, bundle, heads, url):
349 def unbundle(self, bundle, heads, url):
350 """apply a bundle on a repo
350 """apply a bundle on a repo
351
351
352 This function handles the repo locking itself."""
352 This function handles the repo locking itself."""
353 try:
353 try:
354 try:
354 try:
355 bundle = exchange.readbundle(self.ui, bundle, None)
355 bundle = exchange.readbundle(self.ui, bundle, None)
356 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
356 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
357 if util.safehasattr(ret, b'getchunks'):
357 if util.safehasattr(ret, b'getchunks'):
358 # This is a bundle20 object, turn it into an unbundler.
358 # This is a bundle20 object, turn it into an unbundler.
359 # This little dance should be dropped eventually when the
359 # This little dance should be dropped eventually when the
360 # API is finally improved.
360 # API is finally improved.
361 stream = util.chunkbuffer(ret.getchunks())
361 stream = util.chunkbuffer(ret.getchunks())
362 ret = bundle2.getunbundler(self.ui, stream)
362 ret = bundle2.getunbundler(self.ui, stream)
363 return ret
363 return ret
364 except Exception as exc:
364 except Exception as exc:
365 # If the exception contains output salvaged from a bundle2
365 # If the exception contains output salvaged from a bundle2
366 # reply, we need to make sure it is printed before continuing
366 # reply, we need to make sure it is printed before continuing
367 # to fail. So we build a bundle2 with such output and consume
367 # to fail. So we build a bundle2 with such output and consume
368 # it directly.
368 # it directly.
369 #
369 #
370 # This is not very elegant but allows a "simple" solution for
370 # This is not very elegant but allows a "simple" solution for
371 # issue4594
371 # issue4594
372 output = getattr(exc, '_bundle2salvagedoutput', ())
372 output = getattr(exc, '_bundle2salvagedoutput', ())
373 if output:
373 if output:
374 bundler = bundle2.bundle20(self._repo.ui)
374 bundler = bundle2.bundle20(self._repo.ui)
375 for out in output:
375 for out in output:
376 bundler.addpart(out)
376 bundler.addpart(out)
377 stream = util.chunkbuffer(bundler.getchunks())
377 stream = util.chunkbuffer(bundler.getchunks())
378 b = bundle2.getunbundler(self.ui, stream)
378 b = bundle2.getunbundler(self.ui, stream)
379 bundle2.processbundle(self._repo, b)
379 bundle2.processbundle(self._repo, b)
380 raise
380 raise
381 except error.PushRaced as exc:
381 except error.PushRaced as exc:
382 raise error.ResponseError(
382 raise error.ResponseError(
383 _(b'push failed:'), stringutil.forcebytestr(exc)
383 _(b'push failed:'), stringutil.forcebytestr(exc)
384 )
384 )
385
385
386 # End of _basewirecommands interface.
386 # End of _basewirecommands interface.
387
387
388 # Begin of peer interface.
388 # Begin of peer interface.
389
389
390 def commandexecutor(self):
390 def commandexecutor(self):
391 return localcommandexecutor(self)
391 return localcommandexecutor(self)
392
392
393 # End of peer interface.
393 # End of peer interface.
394
394
395
395
396 @interfaceutil.implementer(repository.ipeerlegacycommands)
396 @interfaceutil.implementer(repository.ipeerlegacycommands)
397 class locallegacypeer(localpeer):
397 class locallegacypeer(localpeer):
398 '''peer extension which implements legacy methods too; used for tests with
398 '''peer extension which implements legacy methods too; used for tests with
399 restricted capabilities'''
399 restricted capabilities'''
400
400
401 def __init__(self, repo):
401 def __init__(self, repo):
402 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
402 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
403
403
404 # Begin of baselegacywirecommands interface.
404 # Begin of baselegacywirecommands interface.
405
405
406 def between(self, pairs):
406 def between(self, pairs):
407 return self._repo.between(pairs)
407 return self._repo.between(pairs)
408
408
409 def branches(self, nodes):
409 def branches(self, nodes):
410 return self._repo.branches(nodes)
410 return self._repo.branches(nodes)
411
411
412 def changegroup(self, nodes, source):
412 def changegroup(self, nodes, source):
413 outgoing = discovery.outgoing(
413 outgoing = discovery.outgoing(
414 self._repo, missingroots=nodes, missingheads=self._repo.heads()
414 self._repo, missingroots=nodes, missingheads=self._repo.heads()
415 )
415 )
416 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
416 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
417
417
418 def changegroupsubset(self, bases, heads, source):
418 def changegroupsubset(self, bases, heads, source):
419 outgoing = discovery.outgoing(
419 outgoing = discovery.outgoing(
420 self._repo, missingroots=bases, missingheads=heads
420 self._repo, missingroots=bases, missingheads=heads
421 )
421 )
422 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
422 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
423
423
424 # End of baselegacywirecommands interface.
424 # End of baselegacywirecommands interface.
425
425
426
426
427 # Increment the sub-version when the revlog v2 format changes to lock out old
427 # Increment the sub-version when the revlog v2 format changes to lock out old
428 # clients.
428 # clients.
429 REVLOGV2_REQUIREMENT = b'exp-revlogv2.1'
429 REVLOGV2_REQUIREMENT = b'exp-revlogv2.1'
430
430
431 # A repository with the sparserevlog feature will have delta chains that
431 # A repository with the sparserevlog feature will have delta chains that
432 # can spread over a larger span. Sparse reading cuts these large spans into
432 # can spread over a larger span. Sparse reading cuts these large spans into
433 # pieces, so that each piece isn't too big.
433 # pieces, so that each piece isn't too big.
434 # Without the sparserevlog capability, reading from the repository could use
434 # Without the sparserevlog capability, reading from the repository could use
435 # huge amounts of memory, because the whole span would be read at once,
435 # huge amounts of memory, because the whole span would be read at once,
436 # including all the intermediate revisions that aren't pertinent for the chain.
436 # including all the intermediate revisions that aren't pertinent for the chain.
437 # This is why once a repository has enabled sparse-read, it becomes required.
437 # This is why once a repository has enabled sparse-read, it becomes required.
438 SPARSEREVLOG_REQUIREMENT = b'sparserevlog'
438 SPARSEREVLOG_REQUIREMENT = b'sparserevlog'
439
439
440 # A repository with the sidedataflag requirement will allow to store extra
440 # A repository with the sidedataflag requirement will allow to store extra
441 # information for revision without altering their original hashes.
441 # information for revision without altering their original hashes.
442 SIDEDATA_REQUIREMENT = b'exp-sidedata-flag'
442 SIDEDATA_REQUIREMENT = b'exp-sidedata-flag'
443
443
444 # A repository with the the copies-sidedata-changeset requirement will store
444 # A repository with the the copies-sidedata-changeset requirement will store
445 # copies related information in changeset's sidedata.
445 # copies related information in changeset's sidedata.
446 COPIESSDC_REQUIREMENT = b'exp-copies-sidedata-changeset'
446 COPIESSDC_REQUIREMENT = b'exp-copies-sidedata-changeset'
447
447
448 # Functions receiving (ui, features) that extensions can register to impact
448 # Functions receiving (ui, features) that extensions can register to impact
449 # the ability to load repositories with custom requirements. Only
449 # the ability to load repositories with custom requirements. Only
450 # functions defined in loaded extensions are called.
450 # functions defined in loaded extensions are called.
451 #
451 #
452 # The function receives a set of requirement strings that the repository
452 # The function receives a set of requirement strings that the repository
453 # is capable of opening. Functions will typically add elements to the
453 # is capable of opening. Functions will typically add elements to the
454 # set to reflect that the extension knows how to handle that requirements.
454 # set to reflect that the extension knows how to handle that requirements.
455 featuresetupfuncs = set()
455 featuresetupfuncs = set()
456
456
457
457
458 def makelocalrepository(baseui, path, intents=None):
458 def makelocalrepository(baseui, path, intents=None):
459 """Create a local repository object.
459 """Create a local repository object.
460
460
461 Given arguments needed to construct a local repository, this function
461 Given arguments needed to construct a local repository, this function
462 performs various early repository loading functionality (such as
462 performs various early repository loading functionality (such as
463 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
463 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
464 the repository can be opened, derives a type suitable for representing
464 the repository can be opened, derives a type suitable for representing
465 that repository, and returns an instance of it.
465 that repository, and returns an instance of it.
466
466
467 The returned object conforms to the ``repository.completelocalrepository``
467 The returned object conforms to the ``repository.completelocalrepository``
468 interface.
468 interface.
469
469
470 The repository type is derived by calling a series of factory functions
470 The repository type is derived by calling a series of factory functions
471 for each aspect/interface of the final repository. These are defined by
471 for each aspect/interface of the final repository. These are defined by
472 ``REPO_INTERFACES``.
472 ``REPO_INTERFACES``.
473
473
474 Each factory function is called to produce a type implementing a specific
474 Each factory function is called to produce a type implementing a specific
475 interface. The cumulative list of returned types will be combined into a
475 interface. The cumulative list of returned types will be combined into a
476 new type and that type will be instantiated to represent the local
476 new type and that type will be instantiated to represent the local
477 repository.
477 repository.
478
478
479 The factory functions each receive various state that may be consulted
479 The factory functions each receive various state that may be consulted
480 as part of deriving a type.
480 as part of deriving a type.
481
481
482 Extensions should wrap these factory functions to customize repository type
482 Extensions should wrap these factory functions to customize repository type
483 creation. Note that an extension's wrapped function may be called even if
483 creation. Note that an extension's wrapped function may be called even if
484 that extension is not loaded for the repo being constructed. Extensions
484 that extension is not loaded for the repo being constructed. Extensions
485 should check if their ``__name__`` appears in the
485 should check if their ``__name__`` appears in the
486 ``extensionmodulenames`` set passed to the factory function and no-op if
486 ``extensionmodulenames`` set passed to the factory function and no-op if
487 not.
487 not.
488 """
488 """
489 ui = baseui.copy()
489 ui = baseui.copy()
490 # Prevent copying repo configuration.
490 # Prevent copying repo configuration.
491 ui.copy = baseui.copy
491 ui.copy = baseui.copy
492
492
493 # Working directory VFS rooted at repository root.
493 # Working directory VFS rooted at repository root.
494 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
494 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
495
495
496 # Main VFS for .hg/ directory.
496 # Main VFS for .hg/ directory.
497 hgpath = wdirvfs.join(b'.hg')
497 hgpath = wdirvfs.join(b'.hg')
498 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
498 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
499
499
500 # The .hg/ path should exist and should be a directory. All other
500 # The .hg/ path should exist and should be a directory. All other
501 # cases are errors.
501 # cases are errors.
502 if not hgvfs.isdir():
502 if not hgvfs.isdir():
503 try:
503 try:
504 hgvfs.stat()
504 hgvfs.stat()
505 except OSError as e:
505 except OSError as e:
506 if e.errno != errno.ENOENT:
506 if e.errno != errno.ENOENT:
507 raise
507 raise
508
508
509 raise error.RepoError(_(b'repository %s not found') % path)
509 raise error.RepoError(_(b'repository %s not found') % path)
510
510
511 # .hg/requires file contains a newline-delimited list of
511 # .hg/requires file contains a newline-delimited list of
512 # features/capabilities the opener (us) must have in order to use
512 # features/capabilities the opener (us) must have in order to use
513 # the repository. This file was introduced in Mercurial 0.9.2,
513 # the repository. This file was introduced in Mercurial 0.9.2,
514 # which means very old repositories may not have one. We assume
514 # which means very old repositories may not have one. We assume
515 # a missing file translates to no requirements.
515 # a missing file translates to no requirements.
516 try:
516 try:
517 requirements = set(hgvfs.read(b'requires').splitlines())
517 requirements = set(hgvfs.read(b'requires').splitlines())
518 except IOError as e:
518 except IOError as e:
519 if e.errno != errno.ENOENT:
519 if e.errno != errno.ENOENT:
520 raise
520 raise
521 requirements = set()
521 requirements = set()
522
522
523 # The .hg/hgrc file may load extensions or contain config options
523 # The .hg/hgrc file may load extensions or contain config options
524 # that influence repository construction. Attempt to load it and
524 # that influence repository construction. Attempt to load it and
525 # process any new extensions that it may have pulled in.
525 # process any new extensions that it may have pulled in.
526 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
526 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
527 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
527 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
528 extensions.loadall(ui)
528 extensions.loadall(ui)
529 extensions.populateui(ui)
529 extensions.populateui(ui)
530
530
531 # Set of module names of extensions loaded for this repository.
531 # Set of module names of extensions loaded for this repository.
532 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
532 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
533
533
534 supportedrequirements = gathersupportedrequirements(ui)
534 supportedrequirements = gathersupportedrequirements(ui)
535
535
536 # We first validate the requirements are known.
536 # We first validate the requirements are known.
537 ensurerequirementsrecognized(requirements, supportedrequirements)
537 ensurerequirementsrecognized(requirements, supportedrequirements)
538
538
539 # Then we validate that the known set is reasonable to use together.
539 # Then we validate that the known set is reasonable to use together.
540 ensurerequirementscompatible(ui, requirements)
540 ensurerequirementscompatible(ui, requirements)
541
541
542 # TODO there are unhandled edge cases related to opening repositories with
542 # TODO there are unhandled edge cases related to opening repositories with
543 # shared storage. If storage is shared, we should also test for requirements
543 # shared storage. If storage is shared, we should also test for requirements
544 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
544 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
545 # that repo, as that repo may load extensions needed to open it. This is a
545 # that repo, as that repo may load extensions needed to open it. This is a
546 # bit complicated because we don't want the other hgrc to overwrite settings
546 # bit complicated because we don't want the other hgrc to overwrite settings
547 # in this hgrc.
547 # in this hgrc.
548 #
548 #
549 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
549 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
550 # file when sharing repos. But if a requirement is added after the share is
550 # file when sharing repos. But if a requirement is added after the share is
551 # performed, thereby introducing a new requirement for the opener, we may
551 # performed, thereby introducing a new requirement for the opener, we may
552 # will not see that and could encounter a run-time error interacting with
552 # will not see that and could encounter a run-time error interacting with
553 # that shared store since it has an unknown-to-us requirement.
553 # that shared store since it has an unknown-to-us requirement.
554
554
555 # At this point, we know we should be capable of opening the repository.
555 # At this point, we know we should be capable of opening the repository.
556 # Now get on with doing that.
556 # Now get on with doing that.
557
557
558 features = set()
558 features = set()
559
559
560 # The "store" part of the repository holds versioned data. How it is
560 # The "store" part of the repository holds versioned data. How it is
561 # accessed is determined by various requirements. The ``shared`` or
561 # accessed is determined by various requirements. The ``shared`` or
562 # ``relshared`` requirements indicate the store lives in the path contained
562 # ``relshared`` requirements indicate the store lives in the path contained
563 # in the ``.hg/sharedpath`` file. This is an absolute path for
563 # in the ``.hg/sharedpath`` file. This is an absolute path for
564 # ``shared`` and relative to ``.hg/`` for ``relshared``.
564 # ``shared`` and relative to ``.hg/`` for ``relshared``.
565 if b'shared' in requirements or b'relshared' in requirements:
565 if b'shared' in requirements or b'relshared' in requirements:
566 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
566 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
567 if b'relshared' in requirements:
567 if b'relshared' in requirements:
568 sharedpath = hgvfs.join(sharedpath)
568 sharedpath = hgvfs.join(sharedpath)
569
569
570 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
570 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
571
571
572 if not sharedvfs.exists():
572 if not sharedvfs.exists():
573 raise error.RepoError(
573 raise error.RepoError(
574 _(b'.hg/sharedpath points to nonexistent directory %s')
574 _(b'.hg/sharedpath points to nonexistent directory %s')
575 % sharedvfs.base
575 % sharedvfs.base
576 )
576 )
577
577
578 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
578 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
579
579
580 storebasepath = sharedvfs.base
580 storebasepath = sharedvfs.base
581 cachepath = sharedvfs.join(b'cache')
581 cachepath = sharedvfs.join(b'cache')
582 else:
582 else:
583 storebasepath = hgvfs.base
583 storebasepath = hgvfs.base
584 cachepath = hgvfs.join(b'cache')
584 cachepath = hgvfs.join(b'cache')
585 wcachepath = hgvfs.join(b'wcache')
585 wcachepath = hgvfs.join(b'wcache')
586
586
587 # The store has changed over time and the exact layout is dictated by
587 # The store has changed over time and the exact layout is dictated by
588 # requirements. The store interface abstracts differences across all
588 # requirements. The store interface abstracts differences across all
589 # of them.
589 # of them.
590 store = makestore(
590 store = makestore(
591 requirements,
591 requirements,
592 storebasepath,
592 storebasepath,
593 lambda base: vfsmod.vfs(base, cacheaudited=True),
593 lambda base: vfsmod.vfs(base, cacheaudited=True),
594 )
594 )
595 hgvfs.createmode = store.createmode
595 hgvfs.createmode = store.createmode
596
596
597 storevfs = store.vfs
597 storevfs = store.vfs
598 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
598 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
599
599
600 # The cache vfs is used to manage cache files.
600 # The cache vfs is used to manage cache files.
601 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
601 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
602 cachevfs.createmode = store.createmode
602 cachevfs.createmode = store.createmode
603 # The cache vfs is used to manage cache files related to the working copy
603 # The cache vfs is used to manage cache files related to the working copy
604 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
604 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
605 wcachevfs.createmode = store.createmode
605 wcachevfs.createmode = store.createmode
606
606
607 # Now resolve the type for the repository object. We do this by repeatedly
607 # Now resolve the type for the repository object. We do this by repeatedly
608 # calling a factory function to produces types for specific aspects of the
608 # calling a factory function to produces types for specific aspects of the
609 # repo's operation. The aggregate returned types are used as base classes
609 # repo's operation. The aggregate returned types are used as base classes
610 # for a dynamically-derived type, which will represent our new repository.
610 # for a dynamically-derived type, which will represent our new repository.
611
611
612 bases = []
612 bases = []
613 extrastate = {}
613 extrastate = {}
614
614
615 for iface, fn in REPO_INTERFACES:
615 for iface, fn in REPO_INTERFACES:
616 # We pass all potentially useful state to give extensions tons of
616 # We pass all potentially useful state to give extensions tons of
617 # flexibility.
617 # flexibility.
618 typ = fn()(
618 typ = fn()(
619 ui=ui,
619 ui=ui,
620 intents=intents,
620 intents=intents,
621 requirements=requirements,
621 requirements=requirements,
622 features=features,
622 features=features,
623 wdirvfs=wdirvfs,
623 wdirvfs=wdirvfs,
624 hgvfs=hgvfs,
624 hgvfs=hgvfs,
625 store=store,
625 store=store,
626 storevfs=storevfs,
626 storevfs=storevfs,
627 storeoptions=storevfs.options,
627 storeoptions=storevfs.options,
628 cachevfs=cachevfs,
628 cachevfs=cachevfs,
629 wcachevfs=wcachevfs,
629 wcachevfs=wcachevfs,
630 extensionmodulenames=extensionmodulenames,
630 extensionmodulenames=extensionmodulenames,
631 extrastate=extrastate,
631 extrastate=extrastate,
632 baseclasses=bases,
632 baseclasses=bases,
633 )
633 )
634
634
635 if not isinstance(typ, type):
635 if not isinstance(typ, type):
636 raise error.ProgrammingError(
636 raise error.ProgrammingError(
637 b'unable to construct type for %s' % iface
637 b'unable to construct type for %s' % iface
638 )
638 )
639
639
640 bases.append(typ)
640 bases.append(typ)
641
641
642 # type() allows you to use characters in type names that wouldn't be
642 # type() allows you to use characters in type names that wouldn't be
643 # recognized as Python symbols in source code. We abuse that to add
643 # recognized as Python symbols in source code. We abuse that to add
644 # rich information about our constructed repo.
644 # rich information about our constructed repo.
645 name = pycompat.sysstr(
645 name = pycompat.sysstr(
646 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
646 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
647 )
647 )
648
648
649 cls = type(name, tuple(bases), {})
649 cls = type(name, tuple(bases), {})
650
650
651 return cls(
651 return cls(
652 baseui=baseui,
652 baseui=baseui,
653 ui=ui,
653 ui=ui,
654 origroot=path,
654 origroot=path,
655 wdirvfs=wdirvfs,
655 wdirvfs=wdirvfs,
656 hgvfs=hgvfs,
656 hgvfs=hgvfs,
657 requirements=requirements,
657 requirements=requirements,
658 supportedrequirements=supportedrequirements,
658 supportedrequirements=supportedrequirements,
659 sharedpath=storebasepath,
659 sharedpath=storebasepath,
660 store=store,
660 store=store,
661 cachevfs=cachevfs,
661 cachevfs=cachevfs,
662 wcachevfs=wcachevfs,
662 wcachevfs=wcachevfs,
663 features=features,
663 features=features,
664 intents=intents,
664 intents=intents,
665 )
665 )
666
666
667
667
668 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
668 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
669 """Load hgrc files/content into a ui instance.
669 """Load hgrc files/content into a ui instance.
670
670
671 This is called during repository opening to load any additional
671 This is called during repository opening to load any additional
672 config files or settings relevant to the current repository.
672 config files or settings relevant to the current repository.
673
673
674 Returns a bool indicating whether any additional configs were loaded.
674 Returns a bool indicating whether any additional configs were loaded.
675
675
676 Extensions should monkeypatch this function to modify how per-repo
676 Extensions should monkeypatch this function to modify how per-repo
677 configs are loaded. For example, an extension may wish to pull in
677 configs are loaded. For example, an extension may wish to pull in
678 configs from alternate files or sources.
678 configs from alternate files or sources.
679 """
679 """
680 if not rcutil.use_repo_hgrc():
680 if not rcutil.use_repo_hgrc():
681 return False
681 return False
682 try:
682 try:
683 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
683 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
684 return True
684 return True
685 except IOError:
685 except IOError:
686 return False
686 return False
687
687
688
688
689 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
689 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
690 """Perform additional actions after .hg/hgrc is loaded.
690 """Perform additional actions after .hg/hgrc is loaded.
691
691
692 This function is called during repository loading immediately after
692 This function is called during repository loading immediately after
693 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
693 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
694
694
695 The function can be used to validate configs, automatically add
695 The function can be used to validate configs, automatically add
696 options (including extensions) based on requirements, etc.
696 options (including extensions) based on requirements, etc.
697 """
697 """
698
698
699 # Map of requirements to list of extensions to load automatically when
699 # Map of requirements to list of extensions to load automatically when
700 # requirement is present.
700 # requirement is present.
701 autoextensions = {
701 autoextensions = {
702 b'git': [b'git'],
702 b'git': [b'git'],
703 b'largefiles': [b'largefiles'],
703 b'largefiles': [b'largefiles'],
704 b'lfs': [b'lfs'],
704 b'lfs': [b'lfs'],
705 }
705 }
706
706
707 for requirement, names in sorted(autoextensions.items()):
707 for requirement, names in sorted(autoextensions.items()):
708 if requirement not in requirements:
708 if requirement not in requirements:
709 continue
709 continue
710
710
711 for name in names:
711 for name in names:
712 if not ui.hasconfig(b'extensions', name):
712 if not ui.hasconfig(b'extensions', name):
713 ui.setconfig(b'extensions', name, b'', source=b'autoload')
713 ui.setconfig(b'extensions', name, b'', source=b'autoload')
714
714
715
715
716 def gathersupportedrequirements(ui):
716 def gathersupportedrequirements(ui):
717 """Determine the complete set of recognized requirements."""
717 """Determine the complete set of recognized requirements."""
718 # Start with all requirements supported by this file.
718 # Start with all requirements supported by this file.
719 supported = set(localrepository._basesupported)
719 supported = set(localrepository._basesupported)
720
720
721 # Execute ``featuresetupfuncs`` entries if they belong to an extension
721 # Execute ``featuresetupfuncs`` entries if they belong to an extension
722 # relevant to this ui instance.
722 # relevant to this ui instance.
723 modules = {m.__name__ for n, m in extensions.extensions(ui)}
723 modules = {m.__name__ for n, m in extensions.extensions(ui)}
724
724
725 for fn in featuresetupfuncs:
725 for fn in featuresetupfuncs:
726 if fn.__module__ in modules:
726 if fn.__module__ in modules:
727 fn(ui, supported)
727 fn(ui, supported)
728
728
729 # Add derived requirements from registered compression engines.
729 # Add derived requirements from registered compression engines.
730 for name in util.compengines:
730 for name in util.compengines:
731 engine = util.compengines[name]
731 engine = util.compengines[name]
732 if engine.available() and engine.revlogheader():
732 if engine.available() and engine.revlogheader():
733 supported.add(b'exp-compression-%s' % name)
733 supported.add(b'exp-compression-%s' % name)
734 if engine.name() == b'zstd':
734 if engine.name() == b'zstd':
735 supported.add(b'revlog-compression-zstd')
735 supported.add(b'revlog-compression-zstd')
736
736
737 return supported
737 return supported
738
738
739
739
740 def ensurerequirementsrecognized(requirements, supported):
740 def ensurerequirementsrecognized(requirements, supported):
741 """Validate that a set of local requirements is recognized.
741 """Validate that a set of local requirements is recognized.
742
742
743 Receives a set of requirements. Raises an ``error.RepoError`` if there
743 Receives a set of requirements. Raises an ``error.RepoError`` if there
744 exists any requirement in that set that currently loaded code doesn't
744 exists any requirement in that set that currently loaded code doesn't
745 recognize.
745 recognize.
746
746
747 Returns a set of supported requirements.
747 Returns a set of supported requirements.
748 """
748 """
749 missing = set()
749 missing = set()
750
750
751 for requirement in requirements:
751 for requirement in requirements:
752 if requirement in supported:
752 if requirement in supported:
753 continue
753 continue
754
754
755 if not requirement or not requirement[0:1].isalnum():
755 if not requirement or not requirement[0:1].isalnum():
756 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
756 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
757
757
758 missing.add(requirement)
758 missing.add(requirement)
759
759
760 if missing:
760 if missing:
761 raise error.RequirementError(
761 raise error.RequirementError(
762 _(b'repository requires features unknown to this Mercurial: %s')
762 _(b'repository requires features unknown to this Mercurial: %s')
763 % b' '.join(sorted(missing)),
763 % b' '.join(sorted(missing)),
764 hint=_(
764 hint=_(
765 b'see https://mercurial-scm.org/wiki/MissingRequirement '
765 b'see https://mercurial-scm.org/wiki/MissingRequirement '
766 b'for more information'
766 b'for more information'
767 ),
767 ),
768 )
768 )
769
769
770
770
771 def ensurerequirementscompatible(ui, requirements):
771 def ensurerequirementscompatible(ui, requirements):
772 """Validates that a set of recognized requirements is mutually compatible.
772 """Validates that a set of recognized requirements is mutually compatible.
773
773
774 Some requirements may not be compatible with others or require
774 Some requirements may not be compatible with others or require
775 config options that aren't enabled. This function is called during
775 config options that aren't enabled. This function is called during
776 repository opening to ensure that the set of requirements needed
776 repository opening to ensure that the set of requirements needed
777 to open a repository is sane and compatible with config options.
777 to open a repository is sane and compatible with config options.
778
778
779 Extensions can monkeypatch this function to perform additional
779 Extensions can monkeypatch this function to perform additional
780 checking.
780 checking.
781
781
782 ``error.RepoError`` should be raised on failure.
782 ``error.RepoError`` should be raised on failure.
783 """
783 """
784 if b'exp-sparse' in requirements and not sparse.enabled:
784 if b'exp-sparse' in requirements and not sparse.enabled:
785 raise error.RepoError(
785 raise error.RepoError(
786 _(
786 _(
787 b'repository is using sparse feature but '
787 b'repository is using sparse feature but '
788 b'sparse is not enabled; enable the '
788 b'sparse is not enabled; enable the '
789 b'"sparse" extensions to access'
789 b'"sparse" extensions to access'
790 )
790 )
791 )
791 )
792
792
793
793
794 def makestore(requirements, path, vfstype):
794 def makestore(requirements, path, vfstype):
795 """Construct a storage object for a repository."""
795 """Construct a storage object for a repository."""
796 if b'store' in requirements:
796 if b'store' in requirements:
797 if b'fncache' in requirements:
797 if b'fncache' in requirements:
798 return storemod.fncachestore(
798 return storemod.fncachestore(
799 path, vfstype, b'dotencode' in requirements
799 path, vfstype, b'dotencode' in requirements
800 )
800 )
801
801
802 return storemod.encodedstore(path, vfstype)
802 return storemod.encodedstore(path, vfstype)
803
803
804 return storemod.basicstore(path, vfstype)
804 return storemod.basicstore(path, vfstype)
805
805
806
806
807 def resolvestorevfsoptions(ui, requirements, features):
807 def resolvestorevfsoptions(ui, requirements, features):
808 """Resolve the options to pass to the store vfs opener.
808 """Resolve the options to pass to the store vfs opener.
809
809
810 The returned dict is used to influence behavior of the storage layer.
810 The returned dict is used to influence behavior of the storage layer.
811 """
811 """
812 options = {}
812 options = {}
813
813
814 if b'treemanifest' in requirements:
814 if b'treemanifest' in requirements:
815 options[b'treemanifest'] = True
815 options[b'treemanifest'] = True
816
816
817 # experimental config: format.manifestcachesize
817 # experimental config: format.manifestcachesize
818 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
818 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
819 if manifestcachesize is not None:
819 if manifestcachesize is not None:
820 options[b'manifestcachesize'] = manifestcachesize
820 options[b'manifestcachesize'] = manifestcachesize
821
821
822 # In the absence of another requirement superseding a revlog-related
822 # In the absence of another requirement superseding a revlog-related
823 # requirement, we have to assume the repo is using revlog version 0.
823 # requirement, we have to assume the repo is using revlog version 0.
824 # This revlog format is super old and we don't bother trying to parse
824 # This revlog format is super old and we don't bother trying to parse
825 # opener options for it because those options wouldn't do anything
825 # opener options for it because those options wouldn't do anything
826 # meaningful on such old repos.
826 # meaningful on such old repos.
827 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
827 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
828 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
828 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
829 else: # explicitly mark repo as using revlogv0
829 else: # explicitly mark repo as using revlogv0
830 options[b'revlogv0'] = True
830 options[b'revlogv0'] = True
831
831
832 if COPIESSDC_REQUIREMENT in requirements:
832 if COPIESSDC_REQUIREMENT in requirements:
833 options[b'copies-storage'] = b'changeset-sidedata'
833 options[b'copies-storage'] = b'changeset-sidedata'
834 else:
834 else:
835 writecopiesto = ui.config(b'experimental', b'copies.write-to')
835 writecopiesto = ui.config(b'experimental', b'copies.write-to')
836 copiesextramode = (b'changeset-only', b'compatibility')
836 copiesextramode = (b'changeset-only', b'compatibility')
837 if writecopiesto in copiesextramode:
837 if writecopiesto in copiesextramode:
838 options[b'copies-storage'] = b'extra'
838 options[b'copies-storage'] = b'extra'
839
839
840 return options
840 return options
841
841
842
842
843 def resolverevlogstorevfsoptions(ui, requirements, features):
843 def resolverevlogstorevfsoptions(ui, requirements, features):
844 """Resolve opener options specific to revlogs."""
844 """Resolve opener options specific to revlogs."""
845
845
846 options = {}
846 options = {}
847 options[b'flagprocessors'] = {}
847 options[b'flagprocessors'] = {}
848
848
849 if b'revlogv1' in requirements:
849 if b'revlogv1' in requirements:
850 options[b'revlogv1'] = True
850 options[b'revlogv1'] = True
851 if REVLOGV2_REQUIREMENT in requirements:
851 if REVLOGV2_REQUIREMENT in requirements:
852 options[b'revlogv2'] = True
852 options[b'revlogv2'] = True
853
853
854 if b'generaldelta' in requirements:
854 if b'generaldelta' in requirements:
855 options[b'generaldelta'] = True
855 options[b'generaldelta'] = True
856
856
857 # experimental config: format.chunkcachesize
857 # experimental config: format.chunkcachesize
858 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
858 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
859 if chunkcachesize is not None:
859 if chunkcachesize is not None:
860 options[b'chunkcachesize'] = chunkcachesize
860 options[b'chunkcachesize'] = chunkcachesize
861
861
862 deltabothparents = ui.configbool(
862 deltabothparents = ui.configbool(
863 b'storage', b'revlog.optimize-delta-parent-choice'
863 b'storage', b'revlog.optimize-delta-parent-choice'
864 )
864 )
865 options[b'deltabothparents'] = deltabothparents
865 options[b'deltabothparents'] = deltabothparents
866
866
867 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
867 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
868 lazydeltabase = False
868 lazydeltabase = False
869 if lazydelta:
869 if lazydelta:
870 lazydeltabase = ui.configbool(
870 lazydeltabase = ui.configbool(
871 b'storage', b'revlog.reuse-external-delta-parent'
871 b'storage', b'revlog.reuse-external-delta-parent'
872 )
872 )
873 if lazydeltabase is None:
873 if lazydeltabase is None:
874 lazydeltabase = not scmutil.gddeltaconfig(ui)
874 lazydeltabase = not scmutil.gddeltaconfig(ui)
875 options[b'lazydelta'] = lazydelta
875 options[b'lazydelta'] = lazydelta
876 options[b'lazydeltabase'] = lazydeltabase
876 options[b'lazydeltabase'] = lazydeltabase
877
877
878 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
878 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
879 if 0 <= chainspan:
879 if 0 <= chainspan:
880 options[b'maxdeltachainspan'] = chainspan
880 options[b'maxdeltachainspan'] = chainspan
881
881
882 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
882 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
883 if mmapindexthreshold is not None:
883 if mmapindexthreshold is not None:
884 options[b'mmapindexthreshold'] = mmapindexthreshold
884 options[b'mmapindexthreshold'] = mmapindexthreshold
885
885
886 withsparseread = ui.configbool(b'experimental', b'sparse-read')
886 withsparseread = ui.configbool(b'experimental', b'sparse-read')
887 srdensitythres = float(
887 srdensitythres = float(
888 ui.config(b'experimental', b'sparse-read.density-threshold')
888 ui.config(b'experimental', b'sparse-read.density-threshold')
889 )
889 )
890 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
890 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
891 options[b'with-sparse-read'] = withsparseread
891 options[b'with-sparse-read'] = withsparseread
892 options[b'sparse-read-density-threshold'] = srdensitythres
892 options[b'sparse-read-density-threshold'] = srdensitythres
893 options[b'sparse-read-min-gap-size'] = srmingapsize
893 options[b'sparse-read-min-gap-size'] = srmingapsize
894
894
895 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
895 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
896 options[b'sparse-revlog'] = sparserevlog
896 options[b'sparse-revlog'] = sparserevlog
897 if sparserevlog:
897 if sparserevlog:
898 options[b'generaldelta'] = True
898 options[b'generaldelta'] = True
899
899
900 sidedata = SIDEDATA_REQUIREMENT in requirements
900 sidedata = SIDEDATA_REQUIREMENT in requirements
901 options[b'side-data'] = sidedata
901 options[b'side-data'] = sidedata
902
902
903 maxchainlen = None
903 maxchainlen = None
904 if sparserevlog:
904 if sparserevlog:
905 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
905 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
906 # experimental config: format.maxchainlen
906 # experimental config: format.maxchainlen
907 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
907 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
908 if maxchainlen is not None:
908 if maxchainlen is not None:
909 options[b'maxchainlen'] = maxchainlen
909 options[b'maxchainlen'] = maxchainlen
910
910
911 for r in requirements:
911 for r in requirements:
912 # we allow multiple compression engine requirement to co-exist because
912 # we allow multiple compression engine requirement to co-exist because
913 # strickly speaking, revlog seems to support mixed compression style.
913 # strickly speaking, revlog seems to support mixed compression style.
914 #
914 #
915 # The compression used for new entries will be "the last one"
915 # The compression used for new entries will be "the last one"
916 prefix = r.startswith
916 prefix = r.startswith
917 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
917 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
918 options[b'compengine'] = r.split(b'-', 2)[2]
918 options[b'compengine'] = r.split(b'-', 2)[2]
919
919
920 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
920 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
921 if options[b'zlib.level'] is not None:
921 if options[b'zlib.level'] is not None:
922 if not (0 <= options[b'zlib.level'] <= 9):
922 if not (0 <= options[b'zlib.level'] <= 9):
923 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
923 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
924 raise error.Abort(msg % options[b'zlib.level'])
924 raise error.Abort(msg % options[b'zlib.level'])
925 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
925 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
926 if options[b'zstd.level'] is not None:
926 if options[b'zstd.level'] is not None:
927 if not (0 <= options[b'zstd.level'] <= 22):
927 if not (0 <= options[b'zstd.level'] <= 22):
928 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
928 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
929 raise error.Abort(msg % options[b'zstd.level'])
929 raise error.Abort(msg % options[b'zstd.level'])
930
930
931 if repository.NARROW_REQUIREMENT in requirements:
931 if repository.NARROW_REQUIREMENT in requirements:
932 options[b'enableellipsis'] = True
932 options[b'enableellipsis'] = True
933
933
934 if ui.configbool(b'experimental', b'rust.index'):
934 if ui.configbool(b'experimental', b'rust.index'):
935 options[b'rust.index'] = True
935 options[b'rust.index'] = True
936 if ui.configbool(b'experimental', b'exp-persistent-nodemap'):
936 if ui.configbool(b'experimental', b'exp-persistent-nodemap'):
937 options[b'exp-persistent-nodemap'] = True
937 options[b'exp-persistent-nodemap'] = True
938 if ui.configbool(b'experimental', b'exp-persistent-nodemap.mmap'):
938 if ui.configbool(b'experimental', b'exp-persistent-nodemap.mmap'):
939 options[b'exp-persistent-nodemap.mmap'] = True
939 options[b'exp-persistent-nodemap.mmap'] = True
940 if ui.configbool(b'devel', b'persistent-nodemap'):
940 if ui.configbool(b'devel', b'persistent-nodemap'):
941 options[b'devel-force-nodemap'] = True
941 options[b'devel-force-nodemap'] = True
942
942
943 return options
943 return options
944
944
945
945
946 def makemain(**kwargs):
946 def makemain(**kwargs):
947 """Produce a type conforming to ``ilocalrepositorymain``."""
947 """Produce a type conforming to ``ilocalrepositorymain``."""
948 return localrepository
948 return localrepository
949
949
950
950
951 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
951 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
952 class revlogfilestorage(object):
952 class revlogfilestorage(object):
953 """File storage when using revlogs."""
953 """File storage when using revlogs."""
954
954
955 def file(self, path):
955 def file(self, path):
956 if path[0] == b'/':
956 if path[0] == b'/':
957 path = path[1:]
957 path = path[1:]
958
958
959 return filelog.filelog(self.svfs, path)
959 return filelog.filelog(self.svfs, path)
960
960
961
961
962 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
962 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
963 class revlognarrowfilestorage(object):
963 class revlognarrowfilestorage(object):
964 """File storage when using revlogs and narrow files."""
964 """File storage when using revlogs and narrow files."""
965
965
966 def file(self, path):
966 def file(self, path):
967 if path[0] == b'/':
967 if path[0] == b'/':
968 path = path[1:]
968 path = path[1:]
969
969
970 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
970 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
971
971
972
972
973 def makefilestorage(requirements, features, **kwargs):
973 def makefilestorage(requirements, features, **kwargs):
974 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
974 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
975 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
975 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
976 features.add(repository.REPO_FEATURE_STREAM_CLONE)
976 features.add(repository.REPO_FEATURE_STREAM_CLONE)
977
977
978 if repository.NARROW_REQUIREMENT in requirements:
978 if repository.NARROW_REQUIREMENT in requirements:
979 return revlognarrowfilestorage
979 return revlognarrowfilestorage
980 else:
980 else:
981 return revlogfilestorage
981 return revlogfilestorage
982
982
983
983
984 # List of repository interfaces and factory functions for them. Each
984 # List of repository interfaces and factory functions for them. Each
985 # will be called in order during ``makelocalrepository()`` to iteratively
985 # will be called in order during ``makelocalrepository()`` to iteratively
986 # derive the final type for a local repository instance. We capture the
986 # derive the final type for a local repository instance. We capture the
987 # function as a lambda so we don't hold a reference and the module-level
987 # function as a lambda so we don't hold a reference and the module-level
988 # functions can be wrapped.
988 # functions can be wrapped.
989 REPO_INTERFACES = [
989 REPO_INTERFACES = [
990 (repository.ilocalrepositorymain, lambda: makemain),
990 (repository.ilocalrepositorymain, lambda: makemain),
991 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
991 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
992 ]
992 ]
993
993
994
994
995 @interfaceutil.implementer(repository.ilocalrepositorymain)
995 @interfaceutil.implementer(repository.ilocalrepositorymain)
996 class localrepository(object):
996 class localrepository(object):
997 """Main class for representing local repositories.
997 """Main class for representing local repositories.
998
998
999 All local repositories are instances of this class.
999 All local repositories are instances of this class.
1000
1000
1001 Constructed on its own, instances of this class are not usable as
1001 Constructed on its own, instances of this class are not usable as
1002 repository objects. To obtain a usable repository object, call
1002 repository objects. To obtain a usable repository object, call
1003 ``hg.repository()``, ``localrepo.instance()``, or
1003 ``hg.repository()``, ``localrepo.instance()``, or
1004 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1004 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1005 ``instance()`` adds support for creating new repositories.
1005 ``instance()`` adds support for creating new repositories.
1006 ``hg.repository()`` adds more extension integration, including calling
1006 ``hg.repository()`` adds more extension integration, including calling
1007 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1007 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1008 used.
1008 used.
1009 """
1009 """
1010
1010
1011 # obsolete experimental requirements:
1011 # obsolete experimental requirements:
1012 # - manifestv2: An experimental new manifest format that allowed
1012 # - manifestv2: An experimental new manifest format that allowed
1013 # for stem compression of long paths. Experiment ended up not
1013 # for stem compression of long paths. Experiment ended up not
1014 # being successful (repository sizes went up due to worse delta
1014 # being successful (repository sizes went up due to worse delta
1015 # chains), and the code was deleted in 4.6.
1015 # chains), and the code was deleted in 4.6.
1016 supportedformats = {
1016 supportedformats = {
1017 b'revlogv1',
1017 b'revlogv1',
1018 b'generaldelta',
1018 b'generaldelta',
1019 b'treemanifest',
1019 b'treemanifest',
1020 COPIESSDC_REQUIREMENT,
1020 COPIESSDC_REQUIREMENT,
1021 REVLOGV2_REQUIREMENT,
1021 REVLOGV2_REQUIREMENT,
1022 SIDEDATA_REQUIREMENT,
1022 SIDEDATA_REQUIREMENT,
1023 SPARSEREVLOG_REQUIREMENT,
1023 SPARSEREVLOG_REQUIREMENT,
1024 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1024 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1025 }
1025 }
1026 _basesupported = supportedformats | {
1026 _basesupported = supportedformats | {
1027 b'store',
1027 b'store',
1028 b'fncache',
1028 b'fncache',
1029 b'shared',
1029 b'shared',
1030 b'relshared',
1030 b'relshared',
1031 b'dotencode',
1031 b'dotencode',
1032 b'exp-sparse',
1032 b'exp-sparse',
1033 b'internal-phase',
1033 b'internal-phase',
1034 }
1034 }
1035
1035
1036 # list of prefix for file which can be written without 'wlock'
1036 # list of prefix for file which can be written without 'wlock'
1037 # Extensions should extend this list when needed
1037 # Extensions should extend this list when needed
1038 _wlockfreeprefix = {
1038 _wlockfreeprefix = {
1039 # We migh consider requiring 'wlock' for the next
1039 # We migh consider requiring 'wlock' for the next
1040 # two, but pretty much all the existing code assume
1040 # two, but pretty much all the existing code assume
1041 # wlock is not needed so we keep them excluded for
1041 # wlock is not needed so we keep them excluded for
1042 # now.
1042 # now.
1043 b'hgrc',
1043 b'hgrc',
1044 b'requires',
1044 b'requires',
1045 # XXX cache is a complicatged business someone
1045 # XXX cache is a complicatged business someone
1046 # should investigate this in depth at some point
1046 # should investigate this in depth at some point
1047 b'cache/',
1047 b'cache/',
1048 # XXX shouldn't be dirstate covered by the wlock?
1048 # XXX shouldn't be dirstate covered by the wlock?
1049 b'dirstate',
1049 b'dirstate',
1050 # XXX bisect was still a bit too messy at the time
1050 # XXX bisect was still a bit too messy at the time
1051 # this changeset was introduced. Someone should fix
1051 # this changeset was introduced. Someone should fix
1052 # the remainig bit and drop this line
1052 # the remainig bit and drop this line
1053 b'bisect.state',
1053 b'bisect.state',
1054 }
1054 }
1055
1055
1056 def __init__(
1056 def __init__(
1057 self,
1057 self,
1058 baseui,
1058 baseui,
1059 ui,
1059 ui,
1060 origroot,
1060 origroot,
1061 wdirvfs,
1061 wdirvfs,
1062 hgvfs,
1062 hgvfs,
1063 requirements,
1063 requirements,
1064 supportedrequirements,
1064 supportedrequirements,
1065 sharedpath,
1065 sharedpath,
1066 store,
1066 store,
1067 cachevfs,
1067 cachevfs,
1068 wcachevfs,
1068 wcachevfs,
1069 features,
1069 features,
1070 intents=None,
1070 intents=None,
1071 ):
1071 ):
1072 """Create a new local repository instance.
1072 """Create a new local repository instance.
1073
1073
1074 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1074 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1075 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1075 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1076 object.
1076 object.
1077
1077
1078 Arguments:
1078 Arguments:
1079
1079
1080 baseui
1080 baseui
1081 ``ui.ui`` instance that ``ui`` argument was based off of.
1081 ``ui.ui`` instance that ``ui`` argument was based off of.
1082
1082
1083 ui
1083 ui
1084 ``ui.ui`` instance for use by the repository.
1084 ``ui.ui`` instance for use by the repository.
1085
1085
1086 origroot
1086 origroot
1087 ``bytes`` path to working directory root of this repository.
1087 ``bytes`` path to working directory root of this repository.
1088
1088
1089 wdirvfs
1089 wdirvfs
1090 ``vfs.vfs`` rooted at the working directory.
1090 ``vfs.vfs`` rooted at the working directory.
1091
1091
1092 hgvfs
1092 hgvfs
1093 ``vfs.vfs`` rooted at .hg/
1093 ``vfs.vfs`` rooted at .hg/
1094
1094
1095 requirements
1095 requirements
1096 ``set`` of bytestrings representing repository opening requirements.
1096 ``set`` of bytestrings representing repository opening requirements.
1097
1097
1098 supportedrequirements
1098 supportedrequirements
1099 ``set`` of bytestrings representing repository requirements that we
1099 ``set`` of bytestrings representing repository requirements that we
1100 know how to open. May be a supetset of ``requirements``.
1100 know how to open. May be a supetset of ``requirements``.
1101
1101
1102 sharedpath
1102 sharedpath
1103 ``bytes`` Defining path to storage base directory. Points to a
1103 ``bytes`` Defining path to storage base directory. Points to a
1104 ``.hg/`` directory somewhere.
1104 ``.hg/`` directory somewhere.
1105
1105
1106 store
1106 store
1107 ``store.basicstore`` (or derived) instance providing access to
1107 ``store.basicstore`` (or derived) instance providing access to
1108 versioned storage.
1108 versioned storage.
1109
1109
1110 cachevfs
1110 cachevfs
1111 ``vfs.vfs`` used for cache files.
1111 ``vfs.vfs`` used for cache files.
1112
1112
1113 wcachevfs
1113 wcachevfs
1114 ``vfs.vfs`` used for cache files related to the working copy.
1114 ``vfs.vfs`` used for cache files related to the working copy.
1115
1115
1116 features
1116 features
1117 ``set`` of bytestrings defining features/capabilities of this
1117 ``set`` of bytestrings defining features/capabilities of this
1118 instance.
1118 instance.
1119
1119
1120 intents
1120 intents
1121 ``set`` of system strings indicating what this repo will be used
1121 ``set`` of system strings indicating what this repo will be used
1122 for.
1122 for.
1123 """
1123 """
1124 self.baseui = baseui
1124 self.baseui = baseui
1125 self.ui = ui
1125 self.ui = ui
1126 self.origroot = origroot
1126 self.origroot = origroot
1127 # vfs rooted at working directory.
1127 # vfs rooted at working directory.
1128 self.wvfs = wdirvfs
1128 self.wvfs = wdirvfs
1129 self.root = wdirvfs.base
1129 self.root = wdirvfs.base
1130 # vfs rooted at .hg/. Used to access most non-store paths.
1130 # vfs rooted at .hg/. Used to access most non-store paths.
1131 self.vfs = hgvfs
1131 self.vfs = hgvfs
1132 self.path = hgvfs.base
1132 self.path = hgvfs.base
1133 self.requirements = requirements
1133 self.requirements = requirements
1134 self.supported = supportedrequirements
1134 self.supported = supportedrequirements
1135 self.sharedpath = sharedpath
1135 self.sharedpath = sharedpath
1136 self.store = store
1136 self.store = store
1137 self.cachevfs = cachevfs
1137 self.cachevfs = cachevfs
1138 self.wcachevfs = wcachevfs
1138 self.wcachevfs = wcachevfs
1139 self.features = features
1139 self.features = features
1140
1140
1141 self.filtername = None
1141 self.filtername = None
1142
1142
1143 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1143 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1144 b'devel', b'check-locks'
1144 b'devel', b'check-locks'
1145 ):
1145 ):
1146 self.vfs.audit = self._getvfsward(self.vfs.audit)
1146 self.vfs.audit = self._getvfsward(self.vfs.audit)
1147 # A list of callback to shape the phase if no data were found.
1147 # A list of callback to shape the phase if no data were found.
1148 # Callback are in the form: func(repo, roots) --> processed root.
1148 # Callback are in the form: func(repo, roots) --> processed root.
1149 # This list it to be filled by extension during repo setup
1149 # This list it to be filled by extension during repo setup
1150 self._phasedefaults = []
1150 self._phasedefaults = []
1151
1151
1152 color.setup(self.ui)
1152 color.setup(self.ui)
1153
1153
1154 self.spath = self.store.path
1154 self.spath = self.store.path
1155 self.svfs = self.store.vfs
1155 self.svfs = self.store.vfs
1156 self.sjoin = self.store.join
1156 self.sjoin = self.store.join
1157 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1157 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1158 b'devel', b'check-locks'
1158 b'devel', b'check-locks'
1159 ):
1159 ):
1160 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1160 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1161 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1161 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1162 else: # standard vfs
1162 else: # standard vfs
1163 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1163 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1164
1164
1165 self._dirstatevalidatewarned = False
1165 self._dirstatevalidatewarned = False
1166
1166
1167 self._branchcaches = branchmap.BranchMapCache()
1167 self._branchcaches = branchmap.BranchMapCache()
1168 self._revbranchcache = None
1168 self._revbranchcache = None
1169 self._filterpats = {}
1169 self._filterpats = {}
1170 self._datafilters = {}
1170 self._datafilters = {}
1171 self._transref = self._lockref = self._wlockref = None
1171 self._transref = self._lockref = self._wlockref = None
1172
1172
1173 # A cache for various files under .hg/ that tracks file changes,
1173 # A cache for various files under .hg/ that tracks file changes,
1174 # (used by the filecache decorator)
1174 # (used by the filecache decorator)
1175 #
1175 #
1176 # Maps a property name to its util.filecacheentry
1176 # Maps a property name to its util.filecacheentry
1177 self._filecache = {}
1177 self._filecache = {}
1178
1178
1179 # hold sets of revision to be filtered
1179 # hold sets of revision to be filtered
1180 # should be cleared when something might have changed the filter value:
1180 # should be cleared when something might have changed the filter value:
1181 # - new changesets,
1181 # - new changesets,
1182 # - phase change,
1182 # - phase change,
1183 # - new obsolescence marker,
1183 # - new obsolescence marker,
1184 # - working directory parent change,
1184 # - working directory parent change,
1185 # - bookmark changes
1185 # - bookmark changes
1186 self.filteredrevcache = {}
1186 self.filteredrevcache = {}
1187
1187
1188 # post-dirstate-status hooks
1188 # post-dirstate-status hooks
1189 self._postdsstatus = []
1189 self._postdsstatus = []
1190
1190
1191 # generic mapping between names and nodes
1191 # generic mapping between names and nodes
1192 self.names = namespaces.namespaces()
1192 self.names = namespaces.namespaces()
1193
1193
1194 # Key to signature value.
1194 # Key to signature value.
1195 self._sparsesignaturecache = {}
1195 self._sparsesignaturecache = {}
1196 # Signature to cached matcher instance.
1196 # Signature to cached matcher instance.
1197 self._sparsematchercache = {}
1197 self._sparsematchercache = {}
1198
1198
1199 self._extrafilterid = repoview.extrafilter(ui)
1199 self._extrafilterid = repoview.extrafilter(ui)
1200
1200
1201 self.filecopiesmode = None
1201 self.filecopiesmode = None
1202 if COPIESSDC_REQUIREMENT in self.requirements:
1202 if COPIESSDC_REQUIREMENT in self.requirements:
1203 self.filecopiesmode = b'changeset-sidedata'
1203 self.filecopiesmode = b'changeset-sidedata'
1204
1204
1205 def _getvfsward(self, origfunc):
1205 def _getvfsward(self, origfunc):
1206 """build a ward for self.vfs"""
1206 """build a ward for self.vfs"""
1207 rref = weakref.ref(self)
1207 rref = weakref.ref(self)
1208
1208
1209 def checkvfs(path, mode=None):
1209 def checkvfs(path, mode=None):
1210 ret = origfunc(path, mode=mode)
1210 ret = origfunc(path, mode=mode)
1211 repo = rref()
1211 repo = rref()
1212 if (
1212 if (
1213 repo is None
1213 repo is None
1214 or not util.safehasattr(repo, b'_wlockref')
1214 or not util.safehasattr(repo, b'_wlockref')
1215 or not util.safehasattr(repo, b'_lockref')
1215 or not util.safehasattr(repo, b'_lockref')
1216 ):
1216 ):
1217 return
1217 return
1218 if mode in (None, b'r', b'rb'):
1218 if mode in (None, b'r', b'rb'):
1219 return
1219 return
1220 if path.startswith(repo.path):
1220 if path.startswith(repo.path):
1221 # truncate name relative to the repository (.hg)
1221 # truncate name relative to the repository (.hg)
1222 path = path[len(repo.path) + 1 :]
1222 path = path[len(repo.path) + 1 :]
1223 if path.startswith(b'cache/'):
1223 if path.startswith(b'cache/'):
1224 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1224 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1225 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1225 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1226 if path.startswith(b'journal.') or path.startswith(b'undo.'):
1226 if path.startswith(b'journal.') or path.startswith(b'undo.'):
1227 # journal is covered by 'lock'
1227 # journal is covered by 'lock'
1228 if repo._currentlock(repo._lockref) is None:
1228 if repo._currentlock(repo._lockref) is None:
1229 repo.ui.develwarn(
1229 repo.ui.develwarn(
1230 b'write with no lock: "%s"' % path,
1230 b'write with no lock: "%s"' % path,
1231 stacklevel=3,
1231 stacklevel=3,
1232 config=b'check-locks',
1232 config=b'check-locks',
1233 )
1233 )
1234 elif repo._currentlock(repo._wlockref) is None:
1234 elif repo._currentlock(repo._wlockref) is None:
1235 # rest of vfs files are covered by 'wlock'
1235 # rest of vfs files are covered by 'wlock'
1236 #
1236 #
1237 # exclude special files
1237 # exclude special files
1238 for prefix in self._wlockfreeprefix:
1238 for prefix in self._wlockfreeprefix:
1239 if path.startswith(prefix):
1239 if path.startswith(prefix):
1240 return
1240 return
1241 repo.ui.develwarn(
1241 repo.ui.develwarn(
1242 b'write with no wlock: "%s"' % path,
1242 b'write with no wlock: "%s"' % path,
1243 stacklevel=3,
1243 stacklevel=3,
1244 config=b'check-locks',
1244 config=b'check-locks',
1245 )
1245 )
1246 return ret
1246 return ret
1247
1247
1248 return checkvfs
1248 return checkvfs
1249
1249
1250 def _getsvfsward(self, origfunc):
1250 def _getsvfsward(self, origfunc):
1251 """build a ward for self.svfs"""
1251 """build a ward for self.svfs"""
1252 rref = weakref.ref(self)
1252 rref = weakref.ref(self)
1253
1253
1254 def checksvfs(path, mode=None):
1254 def checksvfs(path, mode=None):
1255 ret = origfunc(path, mode=mode)
1255 ret = origfunc(path, mode=mode)
1256 repo = rref()
1256 repo = rref()
1257 if repo is None or not util.safehasattr(repo, b'_lockref'):
1257 if repo is None or not util.safehasattr(repo, b'_lockref'):
1258 return
1258 return
1259 if mode in (None, b'r', b'rb'):
1259 if mode in (None, b'r', b'rb'):
1260 return
1260 return
1261 if path.startswith(repo.sharedpath):
1261 if path.startswith(repo.sharedpath):
1262 # truncate name relative to the repository (.hg)
1262 # truncate name relative to the repository (.hg)
1263 path = path[len(repo.sharedpath) + 1 :]
1263 path = path[len(repo.sharedpath) + 1 :]
1264 if repo._currentlock(repo._lockref) is None:
1264 if repo._currentlock(repo._lockref) is None:
1265 repo.ui.develwarn(
1265 repo.ui.develwarn(
1266 b'write with no lock: "%s"' % path, stacklevel=4
1266 b'write with no lock: "%s"' % path, stacklevel=4
1267 )
1267 )
1268 return ret
1268 return ret
1269
1269
1270 return checksvfs
1270 return checksvfs
1271
1271
1272 def close(self):
1272 def close(self):
1273 self._writecaches()
1273 self._writecaches()
1274
1274
1275 def _writecaches(self):
1275 def _writecaches(self):
1276 if self._revbranchcache:
1276 if self._revbranchcache:
1277 self._revbranchcache.write()
1277 self._revbranchcache.write()
1278
1278
1279 def _restrictcapabilities(self, caps):
1279 def _restrictcapabilities(self, caps):
1280 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1280 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1281 caps = set(caps)
1281 caps = set(caps)
1282 capsblob = bundle2.encodecaps(
1282 capsblob = bundle2.encodecaps(
1283 bundle2.getrepocaps(self, role=b'client')
1283 bundle2.getrepocaps(self, role=b'client')
1284 )
1284 )
1285 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1285 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1286 return caps
1286 return caps
1287
1287
1288 def _writerequirements(self):
1288 def _writerequirements(self):
1289 scmutil.writerequires(self.vfs, self.requirements)
1289 scmutil.writerequires(self.vfs, self.requirements)
1290
1290
1291 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1291 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1292 # self -> auditor -> self._checknested -> self
1292 # self -> auditor -> self._checknested -> self
1293
1293
1294 @property
1294 @property
1295 def auditor(self):
1295 def auditor(self):
1296 # This is only used by context.workingctx.match in order to
1296 # This is only used by context.workingctx.match in order to
1297 # detect files in subrepos.
1297 # detect files in subrepos.
1298 return pathutil.pathauditor(self.root, callback=self._checknested)
1298 return pathutil.pathauditor(self.root, callback=self._checknested)
1299
1299
1300 @property
1300 @property
1301 def nofsauditor(self):
1301 def nofsauditor(self):
1302 # This is only used by context.basectx.match in order to detect
1302 # This is only used by context.basectx.match in order to detect
1303 # files in subrepos.
1303 # files in subrepos.
1304 return pathutil.pathauditor(
1304 return pathutil.pathauditor(
1305 self.root, callback=self._checknested, realfs=False, cached=True
1305 self.root, callback=self._checknested, realfs=False, cached=True
1306 )
1306 )
1307
1307
1308 def _checknested(self, path):
1308 def _checknested(self, path):
1309 """Determine if path is a legal nested repository."""
1309 """Determine if path is a legal nested repository."""
1310 if not path.startswith(self.root):
1310 if not path.startswith(self.root):
1311 return False
1311 return False
1312 subpath = path[len(self.root) + 1 :]
1312 subpath = path[len(self.root) + 1 :]
1313 normsubpath = util.pconvert(subpath)
1313 normsubpath = util.pconvert(subpath)
1314
1314
1315 # XXX: Checking against the current working copy is wrong in
1315 # XXX: Checking against the current working copy is wrong in
1316 # the sense that it can reject things like
1316 # the sense that it can reject things like
1317 #
1317 #
1318 # $ hg cat -r 10 sub/x.txt
1318 # $ hg cat -r 10 sub/x.txt
1319 #
1319 #
1320 # if sub/ is no longer a subrepository in the working copy
1320 # if sub/ is no longer a subrepository in the working copy
1321 # parent revision.
1321 # parent revision.
1322 #
1322 #
1323 # However, it can of course also allow things that would have
1323 # However, it can of course also allow things that would have
1324 # been rejected before, such as the above cat command if sub/
1324 # been rejected before, such as the above cat command if sub/
1325 # is a subrepository now, but was a normal directory before.
1325 # is a subrepository now, but was a normal directory before.
1326 # The old path auditor would have rejected by mistake since it
1326 # The old path auditor would have rejected by mistake since it
1327 # panics when it sees sub/.hg/.
1327 # panics when it sees sub/.hg/.
1328 #
1328 #
1329 # All in all, checking against the working copy seems sensible
1329 # All in all, checking against the working copy seems sensible
1330 # since we want to prevent access to nested repositories on
1330 # since we want to prevent access to nested repositories on
1331 # the filesystem *now*.
1331 # the filesystem *now*.
1332 ctx = self[None]
1332 ctx = self[None]
1333 parts = util.splitpath(subpath)
1333 parts = util.splitpath(subpath)
1334 while parts:
1334 while parts:
1335 prefix = b'/'.join(parts)
1335 prefix = b'/'.join(parts)
1336 if prefix in ctx.substate:
1336 if prefix in ctx.substate:
1337 if prefix == normsubpath:
1337 if prefix == normsubpath:
1338 return True
1338 return True
1339 else:
1339 else:
1340 sub = ctx.sub(prefix)
1340 sub = ctx.sub(prefix)
1341 return sub.checknested(subpath[len(prefix) + 1 :])
1341 return sub.checknested(subpath[len(prefix) + 1 :])
1342 else:
1342 else:
1343 parts.pop()
1343 parts.pop()
1344 return False
1344 return False
1345
1345
1346 def peer(self):
1346 def peer(self):
1347 return localpeer(self) # not cached to avoid reference cycle
1347 return localpeer(self) # not cached to avoid reference cycle
1348
1348
1349 def unfiltered(self):
1349 def unfiltered(self):
1350 """Return unfiltered version of the repository
1350 """Return unfiltered version of the repository
1351
1351
1352 Intended to be overwritten by filtered repo."""
1352 Intended to be overwritten by filtered repo."""
1353 return self
1353 return self
1354
1354
1355 def filtered(self, name, visibilityexceptions=None):
1355 def filtered(self, name, visibilityexceptions=None):
1356 """Return a filtered version of a repository
1356 """Return a filtered version of a repository
1357
1357
1358 The `name` parameter is the identifier of the requested view. This
1358 The `name` parameter is the identifier of the requested view. This
1359 will return a repoview object set "exactly" to the specified view.
1359 will return a repoview object set "exactly" to the specified view.
1360
1360
1361 This function does not apply recursive filtering to a repository. For
1361 This function does not apply recursive filtering to a repository. For
1362 example calling `repo.filtered("served")` will return a repoview using
1362 example calling `repo.filtered("served")` will return a repoview using
1363 the "served" view, regardless of the initial view used by `repo`.
1363 the "served" view, regardless of the initial view used by `repo`.
1364
1364
1365 In other word, there is always only one level of `repoview` "filtering".
1365 In other word, there is always only one level of `repoview` "filtering".
1366 """
1366 """
1367 if self._extrafilterid is not None and b'%' not in name:
1367 if self._extrafilterid is not None and b'%' not in name:
1368 name = name + b'%' + self._extrafilterid
1368 name = name + b'%' + self._extrafilterid
1369
1369
1370 cls = repoview.newtype(self.unfiltered().__class__)
1370 cls = repoview.newtype(self.unfiltered().__class__)
1371 return cls(self, name, visibilityexceptions)
1371 return cls(self, name, visibilityexceptions)
1372
1372
1373 @mixedrepostorecache(
1373 @mixedrepostorecache(
1374 (b'bookmarks', b'plain'),
1374 (b'bookmarks', b'plain'),
1375 (b'bookmarks.current', b'plain'),
1375 (b'bookmarks.current', b'plain'),
1376 (b'bookmarks', b''),
1376 (b'bookmarks', b''),
1377 (b'00changelog.i', b''),
1377 (b'00changelog.i', b''),
1378 )
1378 )
1379 def _bookmarks(self):
1379 def _bookmarks(self):
1380 # Since the multiple files involved in the transaction cannot be
1380 # Since the multiple files involved in the transaction cannot be
1381 # written atomically (with current repository format), there is a race
1381 # written atomically (with current repository format), there is a race
1382 # condition here.
1382 # condition here.
1383 #
1383 #
1384 # 1) changelog content A is read
1384 # 1) changelog content A is read
1385 # 2) outside transaction update changelog to content B
1385 # 2) outside transaction update changelog to content B
1386 # 3) outside transaction update bookmark file referring to content B
1386 # 3) outside transaction update bookmark file referring to content B
1387 # 4) bookmarks file content is read and filtered against changelog-A
1387 # 4) bookmarks file content is read and filtered against changelog-A
1388 #
1388 #
1389 # When this happens, bookmarks against nodes missing from A are dropped.
1389 # When this happens, bookmarks against nodes missing from A are dropped.
1390 #
1390 #
1391 # Having this happening during read is not great, but it become worse
1391 # Having this happening during read is not great, but it become worse
1392 # when this happen during write because the bookmarks to the "unknown"
1392 # when this happen during write because the bookmarks to the "unknown"
1393 # nodes will be dropped for good. However, writes happen within locks.
1393 # nodes will be dropped for good. However, writes happen within locks.
1394 # This locking makes it possible to have a race free consistent read.
1394 # This locking makes it possible to have a race free consistent read.
1395 # For this purpose data read from disc before locking are
1395 # For this purpose data read from disc before locking are
1396 # "invalidated" right after the locks are taken. This invalidations are
1396 # "invalidated" right after the locks are taken. This invalidations are
1397 # "light", the `filecache` mechanism keep the data in memory and will
1397 # "light", the `filecache` mechanism keep the data in memory and will
1398 # reuse them if the underlying files did not changed. Not parsing the
1398 # reuse them if the underlying files did not changed. Not parsing the
1399 # same data multiple times helps performances.
1399 # same data multiple times helps performances.
1400 #
1400 #
1401 # Unfortunately in the case describe above, the files tracked by the
1401 # Unfortunately in the case describe above, the files tracked by the
1402 # bookmarks file cache might not have changed, but the in-memory
1402 # bookmarks file cache might not have changed, but the in-memory
1403 # content is still "wrong" because we used an older changelog content
1403 # content is still "wrong" because we used an older changelog content
1404 # to process the on-disk data. So after locking, the changelog would be
1404 # to process the on-disk data. So after locking, the changelog would be
1405 # refreshed but `_bookmarks` would be preserved.
1405 # refreshed but `_bookmarks` would be preserved.
1406 # Adding `00changelog.i` to the list of tracked file is not
1406 # Adding `00changelog.i` to the list of tracked file is not
1407 # enough, because at the time we build the content for `_bookmarks` in
1407 # enough, because at the time we build the content for `_bookmarks` in
1408 # (4), the changelog file has already diverged from the content used
1408 # (4), the changelog file has already diverged from the content used
1409 # for loading `changelog` in (1)
1409 # for loading `changelog` in (1)
1410 #
1410 #
1411 # To prevent the issue, we force the changelog to be explicitly
1411 # To prevent the issue, we force the changelog to be explicitly
1412 # reloaded while computing `_bookmarks`. The data race can still happen
1412 # reloaded while computing `_bookmarks`. The data race can still happen
1413 # without the lock (with a narrower window), but it would no longer go
1413 # without the lock (with a narrower window), but it would no longer go
1414 # undetected during the lock time refresh.
1414 # undetected during the lock time refresh.
1415 #
1415 #
1416 # The new schedule is as follow
1416 # The new schedule is as follow
1417 #
1417 #
1418 # 1) filecache logic detect that `_bookmarks` needs to be computed
1418 # 1) filecache logic detect that `_bookmarks` needs to be computed
1419 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1419 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1420 # 3) We force `changelog` filecache to be tested
1420 # 3) We force `changelog` filecache to be tested
1421 # 4) cachestat for `changelog` are captured (for changelog)
1421 # 4) cachestat for `changelog` are captured (for changelog)
1422 # 5) `_bookmarks` is computed and cached
1422 # 5) `_bookmarks` is computed and cached
1423 #
1423 #
1424 # The step in (3) ensure we have a changelog at least as recent as the
1424 # The step in (3) ensure we have a changelog at least as recent as the
1425 # cache stat computed in (1). As a result at locking time:
1425 # cache stat computed in (1). As a result at locking time:
1426 # * if the changelog did not changed since (1) -> we can reuse the data
1426 # * if the changelog did not changed since (1) -> we can reuse the data
1427 # * otherwise -> the bookmarks get refreshed.
1427 # * otherwise -> the bookmarks get refreshed.
1428 self._refreshchangelog()
1428 self._refreshchangelog()
1429 return bookmarks.bmstore(self)
1429 return bookmarks.bmstore(self)
1430
1430
1431 def _refreshchangelog(self):
1431 def _refreshchangelog(self):
1432 """make sure the in memory changelog match the on-disk one"""
1432 """make sure the in memory changelog match the on-disk one"""
1433 if 'changelog' in vars(self) and self.currenttransaction() is None:
1433 if 'changelog' in vars(self) and self.currenttransaction() is None:
1434 del self.changelog
1434 del self.changelog
1435
1435
1436 @property
1436 @property
1437 def _activebookmark(self):
1437 def _activebookmark(self):
1438 return self._bookmarks.active
1438 return self._bookmarks.active
1439
1439
1440 # _phasesets depend on changelog. what we need is to call
1440 # _phasesets depend on changelog. what we need is to call
1441 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1441 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1442 # can't be easily expressed in filecache mechanism.
1442 # can't be easily expressed in filecache mechanism.
1443 @storecache(b'phaseroots', b'00changelog.i')
1443 @storecache(b'phaseroots', b'00changelog.i')
1444 def _phasecache(self):
1444 def _phasecache(self):
1445 return phases.phasecache(self, self._phasedefaults)
1445 return phases.phasecache(self, self._phasedefaults)
1446
1446
1447 @storecache(b'obsstore')
1447 @storecache(b'obsstore')
1448 def obsstore(self):
1448 def obsstore(self):
1449 return obsolete.makestore(self.ui, self)
1449 return obsolete.makestore(self.ui, self)
1450
1450
1451 @storecache(b'00changelog.i')
1451 @storecache(b'00changelog.i')
1452 def changelog(self):
1452 def changelog(self):
1453 return self.store.changelog(txnutil.mayhavepending(self.root))
1453 return self.store.changelog(txnutil.mayhavepending(self.root))
1454
1454
1455 @storecache(b'00manifest.i')
1455 @storecache(b'00manifest.i')
1456 def manifestlog(self):
1456 def manifestlog(self):
1457 return self.store.manifestlog(self, self._storenarrowmatch)
1457 return self.store.manifestlog(self, self._storenarrowmatch)
1458
1458
1459 @repofilecache(b'dirstate')
1459 @repofilecache(b'dirstate')
1460 def dirstate(self):
1460 def dirstate(self):
1461 return self._makedirstate()
1461 return self._makedirstate()
1462
1462
1463 def _makedirstate(self):
1463 def _makedirstate(self):
1464 """Extension point for wrapping the dirstate per-repo."""
1464 """Extension point for wrapping the dirstate per-repo."""
1465 sparsematchfn = lambda: sparse.matcher(self)
1465 sparsematchfn = lambda: sparse.matcher(self)
1466
1466
1467 return dirstate.dirstate(
1467 return dirstate.dirstate(
1468 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1468 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1469 )
1469 )
1470
1470
1471 def _dirstatevalidate(self, node):
1471 def _dirstatevalidate(self, node):
1472 try:
1472 try:
1473 self.changelog.rev(node)
1473 self.changelog.rev(node)
1474 return node
1474 return node
1475 except error.LookupError:
1475 except error.LookupError:
1476 if not self._dirstatevalidatewarned:
1476 if not self._dirstatevalidatewarned:
1477 self._dirstatevalidatewarned = True
1477 self._dirstatevalidatewarned = True
1478 self.ui.warn(
1478 self.ui.warn(
1479 _(b"warning: ignoring unknown working parent %s!\n")
1479 _(b"warning: ignoring unknown working parent %s!\n")
1480 % short(node)
1480 % short(node)
1481 )
1481 )
1482 return nullid
1482 return nullid
1483
1483
1484 @storecache(narrowspec.FILENAME)
1484 @storecache(narrowspec.FILENAME)
1485 def narrowpats(self):
1485 def narrowpats(self):
1486 """matcher patterns for this repository's narrowspec
1486 """matcher patterns for this repository's narrowspec
1487
1487
1488 A tuple of (includes, excludes).
1488 A tuple of (includes, excludes).
1489 """
1489 """
1490 return narrowspec.load(self)
1490 return narrowspec.load(self)
1491
1491
1492 @storecache(narrowspec.FILENAME)
1492 @storecache(narrowspec.FILENAME)
1493 def _storenarrowmatch(self):
1493 def _storenarrowmatch(self):
1494 if repository.NARROW_REQUIREMENT not in self.requirements:
1494 if repository.NARROW_REQUIREMENT not in self.requirements:
1495 return matchmod.always()
1495 return matchmod.always()
1496 include, exclude = self.narrowpats
1496 include, exclude = self.narrowpats
1497 return narrowspec.match(self.root, include=include, exclude=exclude)
1497 return narrowspec.match(self.root, include=include, exclude=exclude)
1498
1498
1499 @storecache(narrowspec.FILENAME)
1499 @storecache(narrowspec.FILENAME)
1500 def _narrowmatch(self):
1500 def _narrowmatch(self):
1501 if repository.NARROW_REQUIREMENT not in self.requirements:
1501 if repository.NARROW_REQUIREMENT not in self.requirements:
1502 return matchmod.always()
1502 return matchmod.always()
1503 narrowspec.checkworkingcopynarrowspec(self)
1503 narrowspec.checkworkingcopynarrowspec(self)
1504 include, exclude = self.narrowpats
1504 include, exclude = self.narrowpats
1505 return narrowspec.match(self.root, include=include, exclude=exclude)
1505 return narrowspec.match(self.root, include=include, exclude=exclude)
1506
1506
1507 def narrowmatch(self, match=None, includeexact=False):
1507 def narrowmatch(self, match=None, includeexact=False):
1508 """matcher corresponding the the repo's narrowspec
1508 """matcher corresponding the the repo's narrowspec
1509
1509
1510 If `match` is given, then that will be intersected with the narrow
1510 If `match` is given, then that will be intersected with the narrow
1511 matcher.
1511 matcher.
1512
1512
1513 If `includeexact` is True, then any exact matches from `match` will
1513 If `includeexact` is True, then any exact matches from `match` will
1514 be included even if they're outside the narrowspec.
1514 be included even if they're outside the narrowspec.
1515 """
1515 """
1516 if match:
1516 if match:
1517 if includeexact and not self._narrowmatch.always():
1517 if includeexact and not self._narrowmatch.always():
1518 # do not exclude explicitly-specified paths so that they can
1518 # do not exclude explicitly-specified paths so that they can
1519 # be warned later on
1519 # be warned later on
1520 em = matchmod.exact(match.files())
1520 em = matchmod.exact(match.files())
1521 nm = matchmod.unionmatcher([self._narrowmatch, em])
1521 nm = matchmod.unionmatcher([self._narrowmatch, em])
1522 return matchmod.intersectmatchers(match, nm)
1522 return matchmod.intersectmatchers(match, nm)
1523 return matchmod.intersectmatchers(match, self._narrowmatch)
1523 return matchmod.intersectmatchers(match, self._narrowmatch)
1524 return self._narrowmatch
1524 return self._narrowmatch
1525
1525
1526 def setnarrowpats(self, newincludes, newexcludes):
1526 def setnarrowpats(self, newincludes, newexcludes):
1527 narrowspec.save(self, newincludes, newexcludes)
1527 narrowspec.save(self, newincludes, newexcludes)
1528 self.invalidate(clearfilecache=True)
1528 self.invalidate(clearfilecache=True)
1529
1529
1530 @unfilteredpropertycache
1530 @unfilteredpropertycache
1531 def _quick_access_changeid_null(self):
1531 def _quick_access_changeid_null(self):
1532 return {
1532 return {
1533 b'null': (nullrev, nullid),
1533 b'null': (nullrev, nullid),
1534 nullrev: (nullrev, nullid),
1534 nullrev: (nullrev, nullid),
1535 nullid: (nullrev, nullid),
1535 nullid: (nullrev, nullid),
1536 }
1536 }
1537
1537
1538 @unfilteredpropertycache
1538 @unfilteredpropertycache
1539 def _quick_access_changeid_wc(self):
1539 def _quick_access_changeid_wc(self):
1540 # also fast path access to the working copy parents
1540 # also fast path access to the working copy parents
1541 # however, only do it for filter that ensure wc is visible.
1541 # however, only do it for filter that ensure wc is visible.
1542 quick = {}
1542 quick = {}
1543 cl = self.unfiltered().changelog
1543 cl = self.unfiltered().changelog
1544 for node in self.dirstate.parents():
1544 for node in self.dirstate.parents():
1545 if node == nullid:
1545 if node == nullid:
1546 continue
1546 continue
1547 rev = cl.index.get_rev(node)
1547 rev = cl.index.get_rev(node)
1548 if rev is None:
1548 if rev is None:
1549 # unknown working copy parent case:
1549 # unknown working copy parent case:
1550 #
1550 #
1551 # skip the fast path and let higher code deal with it
1551 # skip the fast path and let higher code deal with it
1552 continue
1552 continue
1553 pair = (rev, node)
1553 pair = (rev, node)
1554 quick[rev] = pair
1554 quick[rev] = pair
1555 quick[node] = pair
1555 quick[node] = pair
1556 # also add the parents of the parents
1556 # also add the parents of the parents
1557 for r in cl.parentrevs(rev):
1557 for r in cl.parentrevs(rev):
1558 if r == nullrev:
1558 if r == nullrev:
1559 continue
1559 continue
1560 n = cl.node(r)
1560 n = cl.node(r)
1561 pair = (r, n)
1561 pair = (r, n)
1562 quick[r] = pair
1562 quick[r] = pair
1563 quick[n] = pair
1563 quick[n] = pair
1564 p1node = self.dirstate.p1()
1564 p1node = self.dirstate.p1()
1565 if p1node != nullid:
1565 if p1node != nullid:
1566 quick[b'.'] = quick[p1node]
1566 quick[b'.'] = quick[p1node]
1567 return quick
1567 return quick
1568
1568
1569 @unfilteredmethod
1569 @unfilteredmethod
1570 def _quick_access_changeid_invalidate(self):
1570 def _quick_access_changeid_invalidate(self):
1571 if '_quick_access_changeid_wc' in vars(self):
1571 if '_quick_access_changeid_wc' in vars(self):
1572 del self.__dict__['_quick_access_changeid_wc']
1572 del self.__dict__['_quick_access_changeid_wc']
1573
1573
1574 @property
1574 @property
1575 def _quick_access_changeid(self):
1575 def _quick_access_changeid(self):
1576 """an helper dictionnary for __getitem__ calls
1576 """an helper dictionnary for __getitem__ calls
1577
1577
1578 This contains a list of symbol we can recognise right away without
1578 This contains a list of symbol we can recognise right away without
1579 further processing.
1579 further processing.
1580 """
1580 """
1581 mapping = self._quick_access_changeid_null
1581 mapping = self._quick_access_changeid_null
1582 if self.filtername in repoview.filter_has_wc:
1582 if self.filtername in repoview.filter_has_wc:
1583 mapping = mapping.copy()
1583 mapping = mapping.copy()
1584 mapping.update(self._quick_access_changeid_wc)
1584 mapping.update(self._quick_access_changeid_wc)
1585 return mapping
1585 return mapping
1586
1586
1587 def __getitem__(self, changeid):
1587 def __getitem__(self, changeid):
1588 # dealing with special cases
1588 # dealing with special cases
1589 if changeid is None:
1589 if changeid is None:
1590 return context.workingctx(self)
1590 return context.workingctx(self)
1591 if isinstance(changeid, context.basectx):
1591 if isinstance(changeid, context.basectx):
1592 return changeid
1592 return changeid
1593
1593
1594 # dealing with multiple revisions
1594 # dealing with multiple revisions
1595 if isinstance(changeid, slice):
1595 if isinstance(changeid, slice):
1596 # wdirrev isn't contiguous so the slice shouldn't include it
1596 # wdirrev isn't contiguous so the slice shouldn't include it
1597 return [
1597 return [
1598 self[i]
1598 self[i]
1599 for i in pycompat.xrange(*changeid.indices(len(self)))
1599 for i in pycompat.xrange(*changeid.indices(len(self)))
1600 if i not in self.changelog.filteredrevs
1600 if i not in self.changelog.filteredrevs
1601 ]
1601 ]
1602
1602
1603 # dealing with some special values
1603 # dealing with some special values
1604 quick_access = self._quick_access_changeid.get(changeid)
1604 quick_access = self._quick_access_changeid.get(changeid)
1605 if quick_access is not None:
1605 if quick_access is not None:
1606 rev, node = quick_access
1606 rev, node = quick_access
1607 return context.changectx(self, rev, node, maybe_filtered=False)
1607 return context.changectx(self, rev, node, maybe_filtered=False)
1608 if changeid == b'tip':
1608 if changeid == b'tip':
1609 node = self.changelog.tip()
1609 node = self.changelog.tip()
1610 rev = self.changelog.rev(node)
1610 rev = self.changelog.rev(node)
1611 return context.changectx(self, rev, node)
1611 return context.changectx(self, rev, node)
1612
1612
1613 # dealing with arbitrary values
1613 # dealing with arbitrary values
1614 try:
1614 try:
1615 if isinstance(changeid, int):
1615 if isinstance(changeid, int):
1616 node = self.changelog.node(changeid)
1616 node = self.changelog.node(changeid)
1617 rev = changeid
1617 rev = changeid
1618 elif changeid == b'.':
1618 elif changeid == b'.':
1619 # this is a hack to delay/avoid loading obsmarkers
1619 # this is a hack to delay/avoid loading obsmarkers
1620 # when we know that '.' won't be hidden
1620 # when we know that '.' won't be hidden
1621 node = self.dirstate.p1()
1621 node = self.dirstate.p1()
1622 rev = self.unfiltered().changelog.rev(node)
1622 rev = self.unfiltered().changelog.rev(node)
1623 elif len(changeid) == 20:
1623 elif len(changeid) == 20:
1624 try:
1624 try:
1625 node = changeid
1625 node = changeid
1626 rev = self.changelog.rev(changeid)
1626 rev = self.changelog.rev(changeid)
1627 except error.FilteredLookupError:
1627 except error.FilteredLookupError:
1628 changeid = hex(changeid) # for the error message
1628 changeid = hex(changeid) # for the error message
1629 raise
1629 raise
1630 except LookupError:
1630 except LookupError:
1631 # check if it might have come from damaged dirstate
1631 # check if it might have come from damaged dirstate
1632 #
1632 #
1633 # XXX we could avoid the unfiltered if we had a recognizable
1633 # XXX we could avoid the unfiltered if we had a recognizable
1634 # exception for filtered changeset access
1634 # exception for filtered changeset access
1635 if (
1635 if (
1636 self.local()
1636 self.local()
1637 and changeid in self.unfiltered().dirstate.parents()
1637 and changeid in self.unfiltered().dirstate.parents()
1638 ):
1638 ):
1639 msg = _(b"working directory has unknown parent '%s'!")
1639 msg = _(b"working directory has unknown parent '%s'!")
1640 raise error.Abort(msg % short(changeid))
1640 raise error.Abort(msg % short(changeid))
1641 changeid = hex(changeid) # for the error message
1641 changeid = hex(changeid) # for the error message
1642 raise
1642 raise
1643
1643
1644 elif len(changeid) == 40:
1644 elif len(changeid) == 40:
1645 node = bin(changeid)
1645 node = bin(changeid)
1646 rev = self.changelog.rev(node)
1646 rev = self.changelog.rev(node)
1647 else:
1647 else:
1648 raise error.ProgrammingError(
1648 raise error.ProgrammingError(
1649 b"unsupported changeid '%s' of type %s"
1649 b"unsupported changeid '%s' of type %s"
1650 % (changeid, pycompat.bytestr(type(changeid)))
1650 % (changeid, pycompat.bytestr(type(changeid)))
1651 )
1651 )
1652
1652
1653 return context.changectx(self, rev, node)
1653 return context.changectx(self, rev, node)
1654
1654
1655 except (error.FilteredIndexError, error.FilteredLookupError):
1655 except (error.FilteredIndexError, error.FilteredLookupError):
1656 raise error.FilteredRepoLookupError(
1656 raise error.FilteredRepoLookupError(
1657 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1657 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1658 )
1658 )
1659 except (IndexError, LookupError):
1659 except (IndexError, LookupError):
1660 raise error.RepoLookupError(
1660 raise error.RepoLookupError(
1661 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1661 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1662 )
1662 )
1663 except error.WdirUnsupported:
1663 except error.WdirUnsupported:
1664 return context.workingctx(self)
1664 return context.workingctx(self)
1665
1665
1666 def __contains__(self, changeid):
1666 def __contains__(self, changeid):
1667 """True if the given changeid exists
1667 """True if the given changeid exists
1668
1668
1669 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1669 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1670 specified.
1670 specified.
1671 """
1671 """
1672 try:
1672 try:
1673 self[changeid]
1673 self[changeid]
1674 return True
1674 return True
1675 except error.RepoLookupError:
1675 except error.RepoLookupError:
1676 return False
1676 return False
1677
1677
1678 def __nonzero__(self):
1678 def __nonzero__(self):
1679 return True
1679 return True
1680
1680
1681 __bool__ = __nonzero__
1681 __bool__ = __nonzero__
1682
1682
1683 def __len__(self):
1683 def __len__(self):
1684 # no need to pay the cost of repoview.changelog
1684 # no need to pay the cost of repoview.changelog
1685 unfi = self.unfiltered()
1685 unfi = self.unfiltered()
1686 return len(unfi.changelog)
1686 return len(unfi.changelog)
1687
1687
1688 def __iter__(self):
1688 def __iter__(self):
1689 return iter(self.changelog)
1689 return iter(self.changelog)
1690
1690
1691 def revs(self, expr, *args):
1691 def revs(self, expr, *args):
1692 '''Find revisions matching a revset.
1692 '''Find revisions matching a revset.
1693
1693
1694 The revset is specified as a string ``expr`` that may contain
1694 The revset is specified as a string ``expr`` that may contain
1695 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1695 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1696
1696
1697 Revset aliases from the configuration are not expanded. To expand
1697 Revset aliases from the configuration are not expanded. To expand
1698 user aliases, consider calling ``scmutil.revrange()`` or
1698 user aliases, consider calling ``scmutil.revrange()`` or
1699 ``repo.anyrevs([expr], user=True)``.
1699 ``repo.anyrevs([expr], user=True)``.
1700
1700
1701 Returns a smartset.abstractsmartset, which is a list-like interface
1701 Returns a smartset.abstractsmartset, which is a list-like interface
1702 that contains integer revisions.
1702 that contains integer revisions.
1703 '''
1703 '''
1704 tree = revsetlang.spectree(expr, *args)
1704 tree = revsetlang.spectree(expr, *args)
1705 return revset.makematcher(tree)(self)
1705 return revset.makematcher(tree)(self)
1706
1706
1707 def set(self, expr, *args):
1707 def set(self, expr, *args):
1708 '''Find revisions matching a revset and emit changectx instances.
1708 '''Find revisions matching a revset and emit changectx instances.
1709
1709
1710 This is a convenience wrapper around ``revs()`` that iterates the
1710 This is a convenience wrapper around ``revs()`` that iterates the
1711 result and is a generator of changectx instances.
1711 result and is a generator of changectx instances.
1712
1712
1713 Revset aliases from the configuration are not expanded. To expand
1713 Revset aliases from the configuration are not expanded. To expand
1714 user aliases, consider calling ``scmutil.revrange()``.
1714 user aliases, consider calling ``scmutil.revrange()``.
1715 '''
1715 '''
1716 for r in self.revs(expr, *args):
1716 for r in self.revs(expr, *args):
1717 yield self[r]
1717 yield self[r]
1718
1718
1719 def anyrevs(self, specs, user=False, localalias=None):
1719 def anyrevs(self, specs, user=False, localalias=None):
1720 '''Find revisions matching one of the given revsets.
1720 '''Find revisions matching one of the given revsets.
1721
1721
1722 Revset aliases from the configuration are not expanded by default. To
1722 Revset aliases from the configuration are not expanded by default. To
1723 expand user aliases, specify ``user=True``. To provide some local
1723 expand user aliases, specify ``user=True``. To provide some local
1724 definitions overriding user aliases, set ``localalias`` to
1724 definitions overriding user aliases, set ``localalias`` to
1725 ``{name: definitionstring}``.
1725 ``{name: definitionstring}``.
1726 '''
1726 '''
1727 if specs == [b'null']:
1727 if specs == [b'null']:
1728 return revset.baseset([nullrev])
1728 return revset.baseset([nullrev])
1729 if specs == [b'.']:
1729 if specs == [b'.']:
1730 quick_data = self._quick_access_changeid.get(b'.')
1730 quick_data = self._quick_access_changeid.get(b'.')
1731 if quick_data is not None:
1731 if quick_data is not None:
1732 return revset.baseset([quick_data[0]])
1732 return revset.baseset([quick_data[0]])
1733 if user:
1733 if user:
1734 m = revset.matchany(
1734 m = revset.matchany(
1735 self.ui,
1735 self.ui,
1736 specs,
1736 specs,
1737 lookup=revset.lookupfn(self),
1737 lookup=revset.lookupfn(self),
1738 localalias=localalias,
1738 localalias=localalias,
1739 )
1739 )
1740 else:
1740 else:
1741 m = revset.matchany(None, specs, localalias=localalias)
1741 m = revset.matchany(None, specs, localalias=localalias)
1742 return m(self)
1742 return m(self)
1743
1743
1744 def url(self):
1744 def url(self):
1745 return b'file:' + self.root
1745 return b'file:' + self.root
1746
1746
1747 def hook(self, name, throw=False, **args):
1747 def hook(self, name, throw=False, **args):
1748 """Call a hook, passing this repo instance.
1748 """Call a hook, passing this repo instance.
1749
1749
1750 This a convenience method to aid invoking hooks. Extensions likely
1750 This a convenience method to aid invoking hooks. Extensions likely
1751 won't call this unless they have registered a custom hook or are
1751 won't call this unless they have registered a custom hook or are
1752 replacing code that is expected to call a hook.
1752 replacing code that is expected to call a hook.
1753 """
1753 """
1754 return hook.hook(self.ui, self, name, throw, **args)
1754 return hook.hook(self.ui, self, name, throw, **args)
1755
1755
1756 @filteredpropertycache
1756 @filteredpropertycache
1757 def _tagscache(self):
1757 def _tagscache(self):
1758 '''Returns a tagscache object that contains various tags related
1758 '''Returns a tagscache object that contains various tags related
1759 caches.'''
1759 caches.'''
1760
1760
1761 # This simplifies its cache management by having one decorated
1761 # This simplifies its cache management by having one decorated
1762 # function (this one) and the rest simply fetch things from it.
1762 # function (this one) and the rest simply fetch things from it.
1763 class tagscache(object):
1763 class tagscache(object):
1764 def __init__(self):
1764 def __init__(self):
1765 # These two define the set of tags for this repository. tags
1765 # These two define the set of tags for this repository. tags
1766 # maps tag name to node; tagtypes maps tag name to 'global' or
1766 # maps tag name to node; tagtypes maps tag name to 'global' or
1767 # 'local'. (Global tags are defined by .hgtags across all
1767 # 'local'. (Global tags are defined by .hgtags across all
1768 # heads, and local tags are defined in .hg/localtags.)
1768 # heads, and local tags are defined in .hg/localtags.)
1769 # They constitute the in-memory cache of tags.
1769 # They constitute the in-memory cache of tags.
1770 self.tags = self.tagtypes = None
1770 self.tags = self.tagtypes = None
1771
1771
1772 self.nodetagscache = self.tagslist = None
1772 self.nodetagscache = self.tagslist = None
1773
1773
1774 cache = tagscache()
1774 cache = tagscache()
1775 cache.tags, cache.tagtypes = self._findtags()
1775 cache.tags, cache.tagtypes = self._findtags()
1776
1776
1777 return cache
1777 return cache
1778
1778
1779 def tags(self):
1779 def tags(self):
1780 '''return a mapping of tag to node'''
1780 '''return a mapping of tag to node'''
1781 t = {}
1781 t = {}
1782 if self.changelog.filteredrevs:
1782 if self.changelog.filteredrevs:
1783 tags, tt = self._findtags()
1783 tags, tt = self._findtags()
1784 else:
1784 else:
1785 tags = self._tagscache.tags
1785 tags = self._tagscache.tags
1786 rev = self.changelog.rev
1786 rev = self.changelog.rev
1787 for k, v in pycompat.iteritems(tags):
1787 for k, v in pycompat.iteritems(tags):
1788 try:
1788 try:
1789 # ignore tags to unknown nodes
1789 # ignore tags to unknown nodes
1790 rev(v)
1790 rev(v)
1791 t[k] = v
1791 t[k] = v
1792 except (error.LookupError, ValueError):
1792 except (error.LookupError, ValueError):
1793 pass
1793 pass
1794 return t
1794 return t
1795
1795
1796 def _findtags(self):
1796 def _findtags(self):
1797 '''Do the hard work of finding tags. Return a pair of dicts
1797 '''Do the hard work of finding tags. Return a pair of dicts
1798 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1798 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1799 maps tag name to a string like \'global\' or \'local\'.
1799 maps tag name to a string like \'global\' or \'local\'.
1800 Subclasses or extensions are free to add their own tags, but
1800 Subclasses or extensions are free to add their own tags, but
1801 should be aware that the returned dicts will be retained for the
1801 should be aware that the returned dicts will be retained for the
1802 duration of the localrepo object.'''
1802 duration of the localrepo object.'''
1803
1803
1804 # XXX what tagtype should subclasses/extensions use? Currently
1804 # XXX what tagtype should subclasses/extensions use? Currently
1805 # mq and bookmarks add tags, but do not set the tagtype at all.
1805 # mq and bookmarks add tags, but do not set the tagtype at all.
1806 # Should each extension invent its own tag type? Should there
1806 # Should each extension invent its own tag type? Should there
1807 # be one tagtype for all such "virtual" tags? Or is the status
1807 # be one tagtype for all such "virtual" tags? Or is the status
1808 # quo fine?
1808 # quo fine?
1809
1809
1810 # map tag name to (node, hist)
1810 # map tag name to (node, hist)
1811 alltags = tagsmod.findglobaltags(self.ui, self)
1811 alltags = tagsmod.findglobaltags(self.ui, self)
1812 # map tag name to tag type
1812 # map tag name to tag type
1813 tagtypes = {tag: b'global' for tag in alltags}
1813 tagtypes = {tag: b'global' for tag in alltags}
1814
1814
1815 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1815 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1816
1816
1817 # Build the return dicts. Have to re-encode tag names because
1817 # Build the return dicts. Have to re-encode tag names because
1818 # the tags module always uses UTF-8 (in order not to lose info
1818 # the tags module always uses UTF-8 (in order not to lose info
1819 # writing to the cache), but the rest of Mercurial wants them in
1819 # writing to the cache), but the rest of Mercurial wants them in
1820 # local encoding.
1820 # local encoding.
1821 tags = {}
1821 tags = {}
1822 for (name, (node, hist)) in pycompat.iteritems(alltags):
1822 for (name, (node, hist)) in pycompat.iteritems(alltags):
1823 if node != nullid:
1823 if node != nullid:
1824 tags[encoding.tolocal(name)] = node
1824 tags[encoding.tolocal(name)] = node
1825 tags[b'tip'] = self.changelog.tip()
1825 tags[b'tip'] = self.changelog.tip()
1826 tagtypes = {
1826 tagtypes = {
1827 encoding.tolocal(name): value
1827 encoding.tolocal(name): value
1828 for (name, value) in pycompat.iteritems(tagtypes)
1828 for (name, value) in pycompat.iteritems(tagtypes)
1829 }
1829 }
1830 return (tags, tagtypes)
1830 return (tags, tagtypes)
1831
1831
1832 def tagtype(self, tagname):
1832 def tagtype(self, tagname):
1833 '''
1833 '''
1834 return the type of the given tag. result can be:
1834 return the type of the given tag. result can be:
1835
1835
1836 'local' : a local tag
1836 'local' : a local tag
1837 'global' : a global tag
1837 'global' : a global tag
1838 None : tag does not exist
1838 None : tag does not exist
1839 '''
1839 '''
1840
1840
1841 return self._tagscache.tagtypes.get(tagname)
1841 return self._tagscache.tagtypes.get(tagname)
1842
1842
1843 def tagslist(self):
1843 def tagslist(self):
1844 '''return a list of tags ordered by revision'''
1844 '''return a list of tags ordered by revision'''
1845 if not self._tagscache.tagslist:
1845 if not self._tagscache.tagslist:
1846 l = []
1846 l = []
1847 for t, n in pycompat.iteritems(self.tags()):
1847 for t, n in pycompat.iteritems(self.tags()):
1848 l.append((self.changelog.rev(n), t, n))
1848 l.append((self.changelog.rev(n), t, n))
1849 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1849 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1850
1850
1851 return self._tagscache.tagslist
1851 return self._tagscache.tagslist
1852
1852
1853 def nodetags(self, node):
1853 def nodetags(self, node):
1854 '''return the tags associated with a node'''
1854 '''return the tags associated with a node'''
1855 if not self._tagscache.nodetagscache:
1855 if not self._tagscache.nodetagscache:
1856 nodetagscache = {}
1856 nodetagscache = {}
1857 for t, n in pycompat.iteritems(self._tagscache.tags):
1857 for t, n in pycompat.iteritems(self._tagscache.tags):
1858 nodetagscache.setdefault(n, []).append(t)
1858 nodetagscache.setdefault(n, []).append(t)
1859 for tags in pycompat.itervalues(nodetagscache):
1859 for tags in pycompat.itervalues(nodetagscache):
1860 tags.sort()
1860 tags.sort()
1861 self._tagscache.nodetagscache = nodetagscache
1861 self._tagscache.nodetagscache = nodetagscache
1862 return self._tagscache.nodetagscache.get(node, [])
1862 return self._tagscache.nodetagscache.get(node, [])
1863
1863
1864 def nodebookmarks(self, node):
1864 def nodebookmarks(self, node):
1865 """return the list of bookmarks pointing to the specified node"""
1865 """return the list of bookmarks pointing to the specified node"""
1866 return self._bookmarks.names(node)
1866 return self._bookmarks.names(node)
1867
1867
1868 def branchmap(self):
1868 def branchmap(self):
1869 '''returns a dictionary {branch: [branchheads]} with branchheads
1869 '''returns a dictionary {branch: [branchheads]} with branchheads
1870 ordered by increasing revision number'''
1870 ordered by increasing revision number'''
1871 return self._branchcaches[self]
1871 return self._branchcaches[self]
1872
1872
1873 @unfilteredmethod
1873 @unfilteredmethod
1874 def revbranchcache(self):
1874 def revbranchcache(self):
1875 if not self._revbranchcache:
1875 if not self._revbranchcache:
1876 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1876 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1877 return self._revbranchcache
1877 return self._revbranchcache
1878
1878
1879 def branchtip(self, branch, ignoremissing=False):
1879 def branchtip(self, branch, ignoremissing=False):
1880 '''return the tip node for a given branch
1880 '''return the tip node for a given branch
1881
1881
1882 If ignoremissing is True, then this method will not raise an error.
1882 If ignoremissing is True, then this method will not raise an error.
1883 This is helpful for callers that only expect None for a missing branch
1883 This is helpful for callers that only expect None for a missing branch
1884 (e.g. namespace).
1884 (e.g. namespace).
1885
1885
1886 '''
1886 '''
1887 try:
1887 try:
1888 return self.branchmap().branchtip(branch)
1888 return self.branchmap().branchtip(branch)
1889 except KeyError:
1889 except KeyError:
1890 if not ignoremissing:
1890 if not ignoremissing:
1891 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1891 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1892 else:
1892 else:
1893 pass
1893 pass
1894
1894
1895 def lookup(self, key):
1895 def lookup(self, key):
1896 node = scmutil.revsymbol(self, key).node()
1896 node = scmutil.revsymbol(self, key).node()
1897 if node is None:
1897 if node is None:
1898 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1898 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1899 return node
1899 return node
1900
1900
1901 def lookupbranch(self, key):
1901 def lookupbranch(self, key):
1902 if self.branchmap().hasbranch(key):
1902 if self.branchmap().hasbranch(key):
1903 return key
1903 return key
1904
1904
1905 return scmutil.revsymbol(self, key).branch()
1905 return scmutil.revsymbol(self, key).branch()
1906
1906
1907 def known(self, nodes):
1907 def known(self, nodes):
1908 cl = self.changelog
1908 cl = self.changelog
1909 get_rev = cl.index.get_rev
1909 get_rev = cl.index.get_rev
1910 filtered = cl.filteredrevs
1910 filtered = cl.filteredrevs
1911 result = []
1911 result = []
1912 for n in nodes:
1912 for n in nodes:
1913 r = get_rev(n)
1913 r = get_rev(n)
1914 resp = not (r is None or r in filtered)
1914 resp = not (r is None or r in filtered)
1915 result.append(resp)
1915 result.append(resp)
1916 return result
1916 return result
1917
1917
1918 def local(self):
1918 def local(self):
1919 return self
1919 return self
1920
1920
1921 def publishing(self):
1921 def publishing(self):
1922 # it's safe (and desirable) to trust the publish flag unconditionally
1922 # it's safe (and desirable) to trust the publish flag unconditionally
1923 # so that we don't finalize changes shared between users via ssh or nfs
1923 # so that we don't finalize changes shared between users via ssh or nfs
1924 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1924 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1925
1925
1926 def cancopy(self):
1926 def cancopy(self):
1927 # so statichttprepo's override of local() works
1927 # so statichttprepo's override of local() works
1928 if not self.local():
1928 if not self.local():
1929 return False
1929 return False
1930 if not self.publishing():
1930 if not self.publishing():
1931 return True
1931 return True
1932 # if publishing we can't copy if there is filtered content
1932 # if publishing we can't copy if there is filtered content
1933 return not self.filtered(b'visible').changelog.filteredrevs
1933 return not self.filtered(b'visible').changelog.filteredrevs
1934
1934
1935 def shared(self):
1935 def shared(self):
1936 '''the type of shared repository (None if not shared)'''
1936 '''the type of shared repository (None if not shared)'''
1937 if self.sharedpath != self.path:
1937 if self.sharedpath != self.path:
1938 return b'store'
1938 return b'store'
1939 return None
1939 return None
1940
1940
1941 def wjoin(self, f, *insidef):
1941 def wjoin(self, f, *insidef):
1942 return self.vfs.reljoin(self.root, f, *insidef)
1942 return self.vfs.reljoin(self.root, f, *insidef)
1943
1943
1944 def setparents(self, p1, p2=nullid):
1944 def setparents(self, p1, p2=nullid):
1945 self[None].setparents(p1, p2)
1945 self[None].setparents(p1, p2)
1946 self._quick_access_changeid_invalidate()
1946 self._quick_access_changeid_invalidate()
1947
1947
1948 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1948 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1949 """changeid must be a changeset revision, if specified.
1949 """changeid must be a changeset revision, if specified.
1950 fileid can be a file revision or node."""
1950 fileid can be a file revision or node."""
1951 return context.filectx(
1951 return context.filectx(
1952 self, path, changeid, fileid, changectx=changectx
1952 self, path, changeid, fileid, changectx=changectx
1953 )
1953 )
1954
1954
1955 def getcwd(self):
1955 def getcwd(self):
1956 return self.dirstate.getcwd()
1956 return self.dirstate.getcwd()
1957
1957
1958 def pathto(self, f, cwd=None):
1958 def pathto(self, f, cwd=None):
1959 return self.dirstate.pathto(f, cwd)
1959 return self.dirstate.pathto(f, cwd)
1960
1960
1961 def _loadfilter(self, filter):
1961 def _loadfilter(self, filter):
1962 if filter not in self._filterpats:
1962 if filter not in self._filterpats:
1963 l = []
1963 l = []
1964 for pat, cmd in self.ui.configitems(filter):
1964 for pat, cmd in self.ui.configitems(filter):
1965 if cmd == b'!':
1965 if cmd == b'!':
1966 continue
1966 continue
1967 mf = matchmod.match(self.root, b'', [pat])
1967 mf = matchmod.match(self.root, b'', [pat])
1968 fn = None
1968 fn = None
1969 params = cmd
1969 params = cmd
1970 for name, filterfn in pycompat.iteritems(self._datafilters):
1970 for name, filterfn in pycompat.iteritems(self._datafilters):
1971 if cmd.startswith(name):
1971 if cmd.startswith(name):
1972 fn = filterfn
1972 fn = filterfn
1973 params = cmd[len(name) :].lstrip()
1973 params = cmd[len(name) :].lstrip()
1974 break
1974 break
1975 if not fn:
1975 if not fn:
1976 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1976 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1977 fn.__name__ = 'commandfilter'
1977 fn.__name__ = 'commandfilter'
1978 # Wrap old filters not supporting keyword arguments
1978 # Wrap old filters not supporting keyword arguments
1979 if not pycompat.getargspec(fn)[2]:
1979 if not pycompat.getargspec(fn)[2]:
1980 oldfn = fn
1980 oldfn = fn
1981 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
1981 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
1982 fn.__name__ = 'compat-' + oldfn.__name__
1982 fn.__name__ = 'compat-' + oldfn.__name__
1983 l.append((mf, fn, params))
1983 l.append((mf, fn, params))
1984 self._filterpats[filter] = l
1984 self._filterpats[filter] = l
1985 return self._filterpats[filter]
1985 return self._filterpats[filter]
1986
1986
1987 def _filter(self, filterpats, filename, data):
1987 def _filter(self, filterpats, filename, data):
1988 for mf, fn, cmd in filterpats:
1988 for mf, fn, cmd in filterpats:
1989 if mf(filename):
1989 if mf(filename):
1990 self.ui.debug(
1990 self.ui.debug(
1991 b"filtering %s through %s\n"
1991 b"filtering %s through %s\n"
1992 % (filename, cmd or pycompat.sysbytes(fn.__name__))
1992 % (filename, cmd or pycompat.sysbytes(fn.__name__))
1993 )
1993 )
1994 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1994 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1995 break
1995 break
1996
1996
1997 return data
1997 return data
1998
1998
1999 @unfilteredpropertycache
1999 @unfilteredpropertycache
2000 def _encodefilterpats(self):
2000 def _encodefilterpats(self):
2001 return self._loadfilter(b'encode')
2001 return self._loadfilter(b'encode')
2002
2002
2003 @unfilteredpropertycache
2003 @unfilteredpropertycache
2004 def _decodefilterpats(self):
2004 def _decodefilterpats(self):
2005 return self._loadfilter(b'decode')
2005 return self._loadfilter(b'decode')
2006
2006
2007 def adddatafilter(self, name, filter):
2007 def adddatafilter(self, name, filter):
2008 self._datafilters[name] = filter
2008 self._datafilters[name] = filter
2009
2009
2010 def wread(self, filename):
2010 def wread(self, filename):
2011 if self.wvfs.islink(filename):
2011 if self.wvfs.islink(filename):
2012 data = self.wvfs.readlink(filename)
2012 data = self.wvfs.readlink(filename)
2013 else:
2013 else:
2014 data = self.wvfs.read(filename)
2014 data = self.wvfs.read(filename)
2015 return self._filter(self._encodefilterpats, filename, data)
2015 return self._filter(self._encodefilterpats, filename, data)
2016
2016
2017 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2017 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2018 """write ``data`` into ``filename`` in the working directory
2018 """write ``data`` into ``filename`` in the working directory
2019
2019
2020 This returns length of written (maybe decoded) data.
2020 This returns length of written (maybe decoded) data.
2021 """
2021 """
2022 data = self._filter(self._decodefilterpats, filename, data)
2022 data = self._filter(self._decodefilterpats, filename, data)
2023 if b'l' in flags:
2023 if b'l' in flags:
2024 self.wvfs.symlink(data, filename)
2024 self.wvfs.symlink(data, filename)
2025 else:
2025 else:
2026 self.wvfs.write(
2026 self.wvfs.write(
2027 filename, data, backgroundclose=backgroundclose, **kwargs
2027 filename, data, backgroundclose=backgroundclose, **kwargs
2028 )
2028 )
2029 if b'x' in flags:
2029 if b'x' in flags:
2030 self.wvfs.setflags(filename, False, True)
2030 self.wvfs.setflags(filename, False, True)
2031 else:
2031 else:
2032 self.wvfs.setflags(filename, False, False)
2032 self.wvfs.setflags(filename, False, False)
2033 return len(data)
2033 return len(data)
2034
2034
2035 def wwritedata(self, filename, data):
2035 def wwritedata(self, filename, data):
2036 return self._filter(self._decodefilterpats, filename, data)
2036 return self._filter(self._decodefilterpats, filename, data)
2037
2037
2038 def currenttransaction(self):
2038 def currenttransaction(self):
2039 """return the current transaction or None if non exists"""
2039 """return the current transaction or None if non exists"""
2040 if self._transref:
2040 if self._transref:
2041 tr = self._transref()
2041 tr = self._transref()
2042 else:
2042 else:
2043 tr = None
2043 tr = None
2044
2044
2045 if tr and tr.running():
2045 if tr and tr.running():
2046 return tr
2046 return tr
2047 return None
2047 return None
2048
2048
2049 def transaction(self, desc, report=None):
2049 def transaction(self, desc, report=None):
2050 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2050 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2051 b'devel', b'check-locks'
2051 b'devel', b'check-locks'
2052 ):
2052 ):
2053 if self._currentlock(self._lockref) is None:
2053 if self._currentlock(self._lockref) is None:
2054 raise error.ProgrammingError(b'transaction requires locking')
2054 raise error.ProgrammingError(b'transaction requires locking')
2055 tr = self.currenttransaction()
2055 tr = self.currenttransaction()
2056 if tr is not None:
2056 if tr is not None:
2057 return tr.nest(name=desc)
2057 return tr.nest(name=desc)
2058
2058
2059 # abort here if the journal already exists
2059 # abort here if the journal already exists
2060 if self.svfs.exists(b"journal"):
2060 if self.svfs.exists(b"journal"):
2061 raise error.RepoError(
2061 raise error.RepoError(
2062 _(b"abandoned transaction found"),
2062 _(b"abandoned transaction found"),
2063 hint=_(b"run 'hg recover' to clean up transaction"),
2063 hint=_(b"run 'hg recover' to clean up transaction"),
2064 )
2064 )
2065
2065
2066 idbase = b"%.40f#%f" % (random.random(), time.time())
2066 idbase = b"%.40f#%f" % (random.random(), time.time())
2067 ha = hex(hashutil.sha1(idbase).digest())
2067 ha = hex(hashutil.sha1(idbase).digest())
2068 txnid = b'TXN:' + ha
2068 txnid = b'TXN:' + ha
2069 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2069 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2070
2070
2071 self._writejournal(desc)
2071 self._writejournal(desc)
2072 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2072 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2073 if report:
2073 if report:
2074 rp = report
2074 rp = report
2075 else:
2075 else:
2076 rp = self.ui.warn
2076 rp = self.ui.warn
2077 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2077 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2078 # we must avoid cyclic reference between repo and transaction.
2078 # we must avoid cyclic reference between repo and transaction.
2079 reporef = weakref.ref(self)
2079 reporef = weakref.ref(self)
2080 # Code to track tag movement
2080 # Code to track tag movement
2081 #
2081 #
2082 # Since tags are all handled as file content, it is actually quite hard
2082 # Since tags are all handled as file content, it is actually quite hard
2083 # to track these movement from a code perspective. So we fallback to a
2083 # to track these movement from a code perspective. So we fallback to a
2084 # tracking at the repository level. One could envision to track changes
2084 # tracking at the repository level. One could envision to track changes
2085 # to the '.hgtags' file through changegroup apply but that fails to
2085 # to the '.hgtags' file through changegroup apply but that fails to
2086 # cope with case where transaction expose new heads without changegroup
2086 # cope with case where transaction expose new heads without changegroup
2087 # being involved (eg: phase movement).
2087 # being involved (eg: phase movement).
2088 #
2088 #
2089 # For now, We gate the feature behind a flag since this likely comes
2089 # For now, We gate the feature behind a flag since this likely comes
2090 # with performance impacts. The current code run more often than needed
2090 # with performance impacts. The current code run more often than needed
2091 # and do not use caches as much as it could. The current focus is on
2091 # and do not use caches as much as it could. The current focus is on
2092 # the behavior of the feature so we disable it by default. The flag
2092 # the behavior of the feature so we disable it by default. The flag
2093 # will be removed when we are happy with the performance impact.
2093 # will be removed when we are happy with the performance impact.
2094 #
2094 #
2095 # Once this feature is no longer experimental move the following
2095 # Once this feature is no longer experimental move the following
2096 # documentation to the appropriate help section:
2096 # documentation to the appropriate help section:
2097 #
2097 #
2098 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2098 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2099 # tags (new or changed or deleted tags). In addition the details of
2099 # tags (new or changed or deleted tags). In addition the details of
2100 # these changes are made available in a file at:
2100 # these changes are made available in a file at:
2101 # ``REPOROOT/.hg/changes/tags.changes``.
2101 # ``REPOROOT/.hg/changes/tags.changes``.
2102 # Make sure you check for HG_TAG_MOVED before reading that file as it
2102 # Make sure you check for HG_TAG_MOVED before reading that file as it
2103 # might exist from a previous transaction even if no tag were touched
2103 # might exist from a previous transaction even if no tag were touched
2104 # in this one. Changes are recorded in a line base format::
2104 # in this one. Changes are recorded in a line base format::
2105 #
2105 #
2106 # <action> <hex-node> <tag-name>\n
2106 # <action> <hex-node> <tag-name>\n
2107 #
2107 #
2108 # Actions are defined as follow:
2108 # Actions are defined as follow:
2109 # "-R": tag is removed,
2109 # "-R": tag is removed,
2110 # "+A": tag is added,
2110 # "+A": tag is added,
2111 # "-M": tag is moved (old value),
2111 # "-M": tag is moved (old value),
2112 # "+M": tag is moved (new value),
2112 # "+M": tag is moved (new value),
2113 tracktags = lambda x: None
2113 tracktags = lambda x: None
2114 # experimental config: experimental.hook-track-tags
2114 # experimental config: experimental.hook-track-tags
2115 shouldtracktags = self.ui.configbool(
2115 shouldtracktags = self.ui.configbool(
2116 b'experimental', b'hook-track-tags'
2116 b'experimental', b'hook-track-tags'
2117 )
2117 )
2118 if desc != b'strip' and shouldtracktags:
2118 if desc != b'strip' and shouldtracktags:
2119 oldheads = self.changelog.headrevs()
2119 oldheads = self.changelog.headrevs()
2120
2120
2121 def tracktags(tr2):
2121 def tracktags(tr2):
2122 repo = reporef()
2122 repo = reporef()
2123 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2123 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2124 newheads = repo.changelog.headrevs()
2124 newheads = repo.changelog.headrevs()
2125 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2125 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2126 # notes: we compare lists here.
2126 # notes: we compare lists here.
2127 # As we do it only once buiding set would not be cheaper
2127 # As we do it only once buiding set would not be cheaper
2128 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2128 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2129 if changes:
2129 if changes:
2130 tr2.hookargs[b'tag_moved'] = b'1'
2130 tr2.hookargs[b'tag_moved'] = b'1'
2131 with repo.vfs(
2131 with repo.vfs(
2132 b'changes/tags.changes', b'w', atomictemp=True
2132 b'changes/tags.changes', b'w', atomictemp=True
2133 ) as changesfile:
2133 ) as changesfile:
2134 # note: we do not register the file to the transaction
2134 # note: we do not register the file to the transaction
2135 # because we needs it to still exist on the transaction
2135 # because we needs it to still exist on the transaction
2136 # is close (for txnclose hooks)
2136 # is close (for txnclose hooks)
2137 tagsmod.writediff(changesfile, changes)
2137 tagsmod.writediff(changesfile, changes)
2138
2138
2139 def validate(tr2):
2139 def validate(tr2):
2140 """will run pre-closing hooks"""
2140 """will run pre-closing hooks"""
2141 # XXX the transaction API is a bit lacking here so we take a hacky
2141 # XXX the transaction API is a bit lacking here so we take a hacky
2142 # path for now
2142 # path for now
2143 #
2143 #
2144 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2144 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2145 # dict is copied before these run. In addition we needs the data
2145 # dict is copied before these run. In addition we needs the data
2146 # available to in memory hooks too.
2146 # available to in memory hooks too.
2147 #
2147 #
2148 # Moreover, we also need to make sure this runs before txnclose
2148 # Moreover, we also need to make sure this runs before txnclose
2149 # hooks and there is no "pending" mechanism that would execute
2149 # hooks and there is no "pending" mechanism that would execute
2150 # logic only if hooks are about to run.
2150 # logic only if hooks are about to run.
2151 #
2151 #
2152 # Fixing this limitation of the transaction is also needed to track
2152 # Fixing this limitation of the transaction is also needed to track
2153 # other families of changes (bookmarks, phases, obsolescence).
2153 # other families of changes (bookmarks, phases, obsolescence).
2154 #
2154 #
2155 # This will have to be fixed before we remove the experimental
2155 # This will have to be fixed before we remove the experimental
2156 # gating.
2156 # gating.
2157 tracktags(tr2)
2157 tracktags(tr2)
2158 repo = reporef()
2158 repo = reporef()
2159
2159
2160 singleheadopt = (b'experimental', b'single-head-per-branch')
2160 singleheadopt = (b'experimental', b'single-head-per-branch')
2161 singlehead = repo.ui.configbool(*singleheadopt)
2161 singlehead = repo.ui.configbool(*singleheadopt)
2162 if singlehead:
2162 if singlehead:
2163 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2163 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2164 accountclosed = singleheadsub.get(
2164 accountclosed = singleheadsub.get(
2165 b"account-closed-heads", False
2165 b"account-closed-heads", False
2166 )
2166 )
2167 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2167 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2168 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2168 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2169 for name, (old, new) in sorted(
2169 for name, (old, new) in sorted(
2170 tr.changes[b'bookmarks'].items()
2170 tr.changes[b'bookmarks'].items()
2171 ):
2171 ):
2172 args = tr.hookargs.copy()
2172 args = tr.hookargs.copy()
2173 args.update(bookmarks.preparehookargs(name, old, new))
2173 args.update(bookmarks.preparehookargs(name, old, new))
2174 repo.hook(
2174 repo.hook(
2175 b'pretxnclose-bookmark',
2175 b'pretxnclose-bookmark',
2176 throw=True,
2176 throw=True,
2177 **pycompat.strkwargs(args)
2177 **pycompat.strkwargs(args)
2178 )
2178 )
2179 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2179 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2180 cl = repo.unfiltered().changelog
2180 cl = repo.unfiltered().changelog
2181 for revs, (old, new) in tr.changes[b'phases']:
2181 for revs, (old, new) in tr.changes[b'phases']:
2182 for rev in revs:
2182 for rev in revs:
2183 args = tr.hookargs.copy()
2183 args = tr.hookargs.copy()
2184 node = hex(cl.node(rev))
2184 node = hex(cl.node(rev))
2185 args.update(phases.preparehookargs(node, old, new))
2185 args.update(phases.preparehookargs(node, old, new))
2186 repo.hook(
2186 repo.hook(
2187 b'pretxnclose-phase',
2187 b'pretxnclose-phase',
2188 throw=True,
2188 throw=True,
2189 **pycompat.strkwargs(args)
2189 **pycompat.strkwargs(args)
2190 )
2190 )
2191
2191
2192 repo.hook(
2192 repo.hook(
2193 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2193 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2194 )
2194 )
2195
2195
2196 def releasefn(tr, success):
2196 def releasefn(tr, success):
2197 repo = reporef()
2197 repo = reporef()
2198 if repo is None:
2198 if repo is None:
2199 # If the repo has been GC'd (and this release function is being
2199 # If the repo has been GC'd (and this release function is being
2200 # called from transaction.__del__), there's not much we can do,
2200 # called from transaction.__del__), there's not much we can do,
2201 # so just leave the unfinished transaction there and let the
2201 # so just leave the unfinished transaction there and let the
2202 # user run `hg recover`.
2202 # user run `hg recover`.
2203 return
2203 return
2204 if success:
2204 if success:
2205 # this should be explicitly invoked here, because
2205 # this should be explicitly invoked here, because
2206 # in-memory changes aren't written out at closing
2206 # in-memory changes aren't written out at closing
2207 # transaction, if tr.addfilegenerator (via
2207 # transaction, if tr.addfilegenerator (via
2208 # dirstate.write or so) isn't invoked while
2208 # dirstate.write or so) isn't invoked while
2209 # transaction running
2209 # transaction running
2210 repo.dirstate.write(None)
2210 repo.dirstate.write(None)
2211 else:
2211 else:
2212 # discard all changes (including ones already written
2212 # discard all changes (including ones already written
2213 # out) in this transaction
2213 # out) in this transaction
2214 narrowspec.restorebackup(self, b'journal.narrowspec')
2214 narrowspec.restorebackup(self, b'journal.narrowspec')
2215 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2215 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2216 repo.dirstate.restorebackup(None, b'journal.dirstate')
2216 repo.dirstate.restorebackup(None, b'journal.dirstate')
2217
2217
2218 repo.invalidate(clearfilecache=True)
2218 repo.invalidate(clearfilecache=True)
2219
2219
2220 tr = transaction.transaction(
2220 tr = transaction.transaction(
2221 rp,
2221 rp,
2222 self.svfs,
2222 self.svfs,
2223 vfsmap,
2223 vfsmap,
2224 b"journal",
2224 b"journal",
2225 b"undo",
2225 b"undo",
2226 aftertrans(renames),
2226 aftertrans(renames),
2227 self.store.createmode,
2227 self.store.createmode,
2228 validator=validate,
2228 validator=validate,
2229 releasefn=releasefn,
2229 releasefn=releasefn,
2230 checkambigfiles=_cachedfiles,
2230 checkambigfiles=_cachedfiles,
2231 name=desc,
2231 name=desc,
2232 )
2232 )
2233 tr.changes[b'origrepolen'] = len(self)
2233 tr.changes[b'origrepolen'] = len(self)
2234 tr.changes[b'obsmarkers'] = set()
2234 tr.changes[b'obsmarkers'] = set()
2235 tr.changes[b'phases'] = []
2235 tr.changes[b'phases'] = []
2236 tr.changes[b'bookmarks'] = {}
2236 tr.changes[b'bookmarks'] = {}
2237
2237
2238 tr.hookargs[b'txnid'] = txnid
2238 tr.hookargs[b'txnid'] = txnid
2239 tr.hookargs[b'txnname'] = desc
2239 tr.hookargs[b'txnname'] = desc
2240 # note: writing the fncache only during finalize mean that the file is
2240 # note: writing the fncache only during finalize mean that the file is
2241 # outdated when running hooks. As fncache is used for streaming clone,
2241 # outdated when running hooks. As fncache is used for streaming clone,
2242 # this is not expected to break anything that happen during the hooks.
2242 # this is not expected to break anything that happen during the hooks.
2243 tr.addfinalize(b'flush-fncache', self.store.write)
2243 tr.addfinalize(b'flush-fncache', self.store.write)
2244
2244
2245 def txnclosehook(tr2):
2245 def txnclosehook(tr2):
2246 """To be run if transaction is successful, will schedule a hook run
2246 """To be run if transaction is successful, will schedule a hook run
2247 """
2247 """
2248 # Don't reference tr2 in hook() so we don't hold a reference.
2248 # Don't reference tr2 in hook() so we don't hold a reference.
2249 # This reduces memory consumption when there are multiple
2249 # This reduces memory consumption when there are multiple
2250 # transactions per lock. This can likely go away if issue5045
2250 # transactions per lock. This can likely go away if issue5045
2251 # fixes the function accumulation.
2251 # fixes the function accumulation.
2252 hookargs = tr2.hookargs
2252 hookargs = tr2.hookargs
2253
2253
2254 def hookfunc(unused_success):
2254 def hookfunc(unused_success):
2255 repo = reporef()
2255 repo = reporef()
2256 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2256 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2257 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2257 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2258 for name, (old, new) in bmchanges:
2258 for name, (old, new) in bmchanges:
2259 args = tr.hookargs.copy()
2259 args = tr.hookargs.copy()
2260 args.update(bookmarks.preparehookargs(name, old, new))
2260 args.update(bookmarks.preparehookargs(name, old, new))
2261 repo.hook(
2261 repo.hook(
2262 b'txnclose-bookmark',
2262 b'txnclose-bookmark',
2263 throw=False,
2263 throw=False,
2264 **pycompat.strkwargs(args)
2264 **pycompat.strkwargs(args)
2265 )
2265 )
2266
2266
2267 if hook.hashook(repo.ui, b'txnclose-phase'):
2267 if hook.hashook(repo.ui, b'txnclose-phase'):
2268 cl = repo.unfiltered().changelog
2268 cl = repo.unfiltered().changelog
2269 phasemv = sorted(
2269 phasemv = sorted(
2270 tr.changes[b'phases'], key=lambda r: r[0][0]
2270 tr.changes[b'phases'], key=lambda r: r[0][0]
2271 )
2271 )
2272 for revs, (old, new) in phasemv:
2272 for revs, (old, new) in phasemv:
2273 for rev in revs:
2273 for rev in revs:
2274 args = tr.hookargs.copy()
2274 args = tr.hookargs.copy()
2275 node = hex(cl.node(rev))
2275 node = hex(cl.node(rev))
2276 args.update(phases.preparehookargs(node, old, new))
2276 args.update(phases.preparehookargs(node, old, new))
2277 repo.hook(
2277 repo.hook(
2278 b'txnclose-phase',
2278 b'txnclose-phase',
2279 throw=False,
2279 throw=False,
2280 **pycompat.strkwargs(args)
2280 **pycompat.strkwargs(args)
2281 )
2281 )
2282
2282
2283 repo.hook(
2283 repo.hook(
2284 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2284 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2285 )
2285 )
2286
2286
2287 reporef()._afterlock(hookfunc)
2287 reporef()._afterlock(hookfunc)
2288
2288
2289 tr.addfinalize(b'txnclose-hook', txnclosehook)
2289 tr.addfinalize(b'txnclose-hook', txnclosehook)
2290 # Include a leading "-" to make it happen before the transaction summary
2290 # Include a leading "-" to make it happen before the transaction summary
2291 # reports registered via scmutil.registersummarycallback() whose names
2291 # reports registered via scmutil.registersummarycallback() whose names
2292 # are 00-txnreport etc. That way, the caches will be warm when the
2292 # are 00-txnreport etc. That way, the caches will be warm when the
2293 # callbacks run.
2293 # callbacks run.
2294 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2294 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2295
2295
2296 def txnaborthook(tr2):
2296 def txnaborthook(tr2):
2297 """To be run if transaction is aborted
2297 """To be run if transaction is aborted
2298 """
2298 """
2299 reporef().hook(
2299 reporef().hook(
2300 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2300 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2301 )
2301 )
2302
2302
2303 tr.addabort(b'txnabort-hook', txnaborthook)
2303 tr.addabort(b'txnabort-hook', txnaborthook)
2304 # avoid eager cache invalidation. in-memory data should be identical
2304 # avoid eager cache invalidation. in-memory data should be identical
2305 # to stored data if transaction has no error.
2305 # to stored data if transaction has no error.
2306 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2306 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2307 self._transref = weakref.ref(tr)
2307 self._transref = weakref.ref(tr)
2308 scmutil.registersummarycallback(self, tr, desc)
2308 scmutil.registersummarycallback(self, tr, desc)
2309 return tr
2309 return tr
2310
2310
2311 def _journalfiles(self):
2311 def _journalfiles(self):
2312 return (
2312 return (
2313 (self.svfs, b'journal'),
2313 (self.svfs, b'journal'),
2314 (self.svfs, b'journal.narrowspec'),
2314 (self.svfs, b'journal.narrowspec'),
2315 (self.vfs, b'journal.narrowspec.dirstate'),
2315 (self.vfs, b'journal.narrowspec.dirstate'),
2316 (self.vfs, b'journal.dirstate'),
2316 (self.vfs, b'journal.dirstate'),
2317 (self.vfs, b'journal.branch'),
2317 (self.vfs, b'journal.branch'),
2318 (self.vfs, b'journal.desc'),
2318 (self.vfs, b'journal.desc'),
2319 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2319 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2320 (self.svfs, b'journal.phaseroots'),
2320 (self.svfs, b'journal.phaseroots'),
2321 )
2321 )
2322
2322
2323 def undofiles(self):
2323 def undofiles(self):
2324 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2324 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2325
2325
2326 @unfilteredmethod
2326 @unfilteredmethod
2327 def _writejournal(self, desc):
2327 def _writejournal(self, desc):
2328 self.dirstate.savebackup(None, b'journal.dirstate')
2328 self.dirstate.savebackup(None, b'journal.dirstate')
2329 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2329 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2330 narrowspec.savebackup(self, b'journal.narrowspec')
2330 narrowspec.savebackup(self, b'journal.narrowspec')
2331 self.vfs.write(
2331 self.vfs.write(
2332 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2332 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2333 )
2333 )
2334 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2334 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2335 bookmarksvfs = bookmarks.bookmarksvfs(self)
2335 bookmarksvfs = bookmarks.bookmarksvfs(self)
2336 bookmarksvfs.write(
2336 bookmarksvfs.write(
2337 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2337 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2338 )
2338 )
2339 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2339 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2340
2340
2341 def recover(self):
2341 def recover(self):
2342 with self.lock():
2342 with self.lock():
2343 if self.svfs.exists(b"journal"):
2343 if self.svfs.exists(b"journal"):
2344 self.ui.status(_(b"rolling back interrupted transaction\n"))
2344 self.ui.status(_(b"rolling back interrupted transaction\n"))
2345 vfsmap = {
2345 vfsmap = {
2346 b'': self.svfs,
2346 b'': self.svfs,
2347 b'plain': self.vfs,
2347 b'plain': self.vfs,
2348 }
2348 }
2349 transaction.rollback(
2349 transaction.rollback(
2350 self.svfs,
2350 self.svfs,
2351 vfsmap,
2351 vfsmap,
2352 b"journal",
2352 b"journal",
2353 self.ui.warn,
2353 self.ui.warn,
2354 checkambigfiles=_cachedfiles,
2354 checkambigfiles=_cachedfiles,
2355 )
2355 )
2356 self.invalidate()
2356 self.invalidate()
2357 return True
2357 return True
2358 else:
2358 else:
2359 self.ui.warn(_(b"no interrupted transaction available\n"))
2359 self.ui.warn(_(b"no interrupted transaction available\n"))
2360 return False
2360 return False
2361
2361
2362 def rollback(self, dryrun=False, force=False):
2362 def rollback(self, dryrun=False, force=False):
2363 wlock = lock = dsguard = None
2363 wlock = lock = dsguard = None
2364 try:
2364 try:
2365 wlock = self.wlock()
2365 wlock = self.wlock()
2366 lock = self.lock()
2366 lock = self.lock()
2367 if self.svfs.exists(b"undo"):
2367 if self.svfs.exists(b"undo"):
2368 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2368 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2369
2369
2370 return self._rollback(dryrun, force, dsguard)
2370 return self._rollback(dryrun, force, dsguard)
2371 else:
2371 else:
2372 self.ui.warn(_(b"no rollback information available\n"))
2372 self.ui.warn(_(b"no rollback information available\n"))
2373 return 1
2373 return 1
2374 finally:
2374 finally:
2375 release(dsguard, lock, wlock)
2375 release(dsguard, lock, wlock)
2376
2376
2377 @unfilteredmethod # Until we get smarter cache management
2377 @unfilteredmethod # Until we get smarter cache management
2378 def _rollback(self, dryrun, force, dsguard):
2378 def _rollback(self, dryrun, force, dsguard):
2379 ui = self.ui
2379 ui = self.ui
2380 try:
2380 try:
2381 args = self.vfs.read(b'undo.desc').splitlines()
2381 args = self.vfs.read(b'undo.desc').splitlines()
2382 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2382 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2383 if len(args) >= 3:
2383 if len(args) >= 3:
2384 detail = args[2]
2384 detail = args[2]
2385 oldtip = oldlen - 1
2385 oldtip = oldlen - 1
2386
2386
2387 if detail and ui.verbose:
2387 if detail and ui.verbose:
2388 msg = _(
2388 msg = _(
2389 b'repository tip rolled back to revision %d'
2389 b'repository tip rolled back to revision %d'
2390 b' (undo %s: %s)\n'
2390 b' (undo %s: %s)\n'
2391 ) % (oldtip, desc, detail)
2391 ) % (oldtip, desc, detail)
2392 else:
2392 else:
2393 msg = _(
2393 msg = _(
2394 b'repository tip rolled back to revision %d (undo %s)\n'
2394 b'repository tip rolled back to revision %d (undo %s)\n'
2395 ) % (oldtip, desc)
2395 ) % (oldtip, desc)
2396 except IOError:
2396 except IOError:
2397 msg = _(b'rolling back unknown transaction\n')
2397 msg = _(b'rolling back unknown transaction\n')
2398 desc = None
2398 desc = None
2399
2399
2400 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2400 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2401 raise error.Abort(
2401 raise error.Abort(
2402 _(
2402 _(
2403 b'rollback of last commit while not checked out '
2403 b'rollback of last commit while not checked out '
2404 b'may lose data'
2404 b'may lose data'
2405 ),
2405 ),
2406 hint=_(b'use -f to force'),
2406 hint=_(b'use -f to force'),
2407 )
2407 )
2408
2408
2409 ui.status(msg)
2409 ui.status(msg)
2410 if dryrun:
2410 if dryrun:
2411 return 0
2411 return 0
2412
2412
2413 parents = self.dirstate.parents()
2413 parents = self.dirstate.parents()
2414 self.destroying()
2414 self.destroying()
2415 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2415 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2416 transaction.rollback(
2416 transaction.rollback(
2417 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2417 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2418 )
2418 )
2419 bookmarksvfs = bookmarks.bookmarksvfs(self)
2419 bookmarksvfs = bookmarks.bookmarksvfs(self)
2420 if bookmarksvfs.exists(b'undo.bookmarks'):
2420 if bookmarksvfs.exists(b'undo.bookmarks'):
2421 bookmarksvfs.rename(
2421 bookmarksvfs.rename(
2422 b'undo.bookmarks', b'bookmarks', checkambig=True
2422 b'undo.bookmarks', b'bookmarks', checkambig=True
2423 )
2423 )
2424 if self.svfs.exists(b'undo.phaseroots'):
2424 if self.svfs.exists(b'undo.phaseroots'):
2425 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2425 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2426 self.invalidate()
2426 self.invalidate()
2427
2427
2428 has_node = self.changelog.index.has_node
2428 has_node = self.changelog.index.has_node
2429 parentgone = any(not has_node(p) for p in parents)
2429 parentgone = any(not has_node(p) for p in parents)
2430 if parentgone:
2430 if parentgone:
2431 # prevent dirstateguard from overwriting already restored one
2431 # prevent dirstateguard from overwriting already restored one
2432 dsguard.close()
2432 dsguard.close()
2433
2433
2434 narrowspec.restorebackup(self, b'undo.narrowspec')
2434 narrowspec.restorebackup(self, b'undo.narrowspec')
2435 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2435 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2436 self.dirstate.restorebackup(None, b'undo.dirstate')
2436 self.dirstate.restorebackup(None, b'undo.dirstate')
2437 try:
2437 try:
2438 branch = self.vfs.read(b'undo.branch')
2438 branch = self.vfs.read(b'undo.branch')
2439 self.dirstate.setbranch(encoding.tolocal(branch))
2439 self.dirstate.setbranch(encoding.tolocal(branch))
2440 except IOError:
2440 except IOError:
2441 ui.warn(
2441 ui.warn(
2442 _(
2442 _(
2443 b'named branch could not be reset: '
2443 b'named branch could not be reset: '
2444 b'current branch is still \'%s\'\n'
2444 b'current branch is still \'%s\'\n'
2445 )
2445 )
2446 % self.dirstate.branch()
2446 % self.dirstate.branch()
2447 )
2447 )
2448
2448
2449 parents = tuple([p.rev() for p in self[None].parents()])
2449 parents = tuple([p.rev() for p in self[None].parents()])
2450 if len(parents) > 1:
2450 if len(parents) > 1:
2451 ui.status(
2451 ui.status(
2452 _(
2452 _(
2453 b'working directory now based on '
2453 b'working directory now based on '
2454 b'revisions %d and %d\n'
2454 b'revisions %d and %d\n'
2455 )
2455 )
2456 % parents
2456 % parents
2457 )
2457 )
2458 else:
2458 else:
2459 ui.status(
2459 ui.status(
2460 _(b'working directory now based on revision %d\n') % parents
2460 _(b'working directory now based on revision %d\n') % parents
2461 )
2461 )
2462 mergemod.mergestate.clean(self, self[b'.'].node())
2462 mergemod.mergestate.clean(self, self[b'.'].node())
2463
2463
2464 # TODO: if we know which new heads may result from this rollback, pass
2464 # TODO: if we know which new heads may result from this rollback, pass
2465 # them to destroy(), which will prevent the branchhead cache from being
2465 # them to destroy(), which will prevent the branchhead cache from being
2466 # invalidated.
2466 # invalidated.
2467 self.destroyed()
2467 self.destroyed()
2468 return 0
2468 return 0
2469
2469
2470 def _buildcacheupdater(self, newtransaction):
2470 def _buildcacheupdater(self, newtransaction):
2471 """called during transaction to build the callback updating cache
2471 """called during transaction to build the callback updating cache
2472
2472
2473 Lives on the repository to help extension who might want to augment
2473 Lives on the repository to help extension who might want to augment
2474 this logic. For this purpose, the created transaction is passed to the
2474 this logic. For this purpose, the created transaction is passed to the
2475 method.
2475 method.
2476 """
2476 """
2477 # we must avoid cyclic reference between repo and transaction.
2477 # we must avoid cyclic reference between repo and transaction.
2478 reporef = weakref.ref(self)
2478 reporef = weakref.ref(self)
2479
2479
2480 def updater(tr):
2480 def updater(tr):
2481 repo = reporef()
2481 repo = reporef()
2482 repo.updatecaches(tr)
2482 repo.updatecaches(tr)
2483
2483
2484 return updater
2484 return updater
2485
2485
2486 @unfilteredmethod
2486 @unfilteredmethod
2487 def updatecaches(self, tr=None, full=False):
2487 def updatecaches(self, tr=None, full=False):
2488 """warm appropriate caches
2488 """warm appropriate caches
2489
2489
2490 If this function is called after a transaction closed. The transaction
2490 If this function is called after a transaction closed. The transaction
2491 will be available in the 'tr' argument. This can be used to selectively
2491 will be available in the 'tr' argument. This can be used to selectively
2492 update caches relevant to the changes in that transaction.
2492 update caches relevant to the changes in that transaction.
2493
2493
2494 If 'full' is set, make sure all caches the function knows about have
2494 If 'full' is set, make sure all caches the function knows about have
2495 up-to-date data. Even the ones usually loaded more lazily.
2495 up-to-date data. Even the ones usually loaded more lazily.
2496 """
2496 """
2497 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2497 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2498 # During strip, many caches are invalid but
2498 # During strip, many caches are invalid but
2499 # later call to `destroyed` will refresh them.
2499 # later call to `destroyed` will refresh them.
2500 return
2500 return
2501
2501
2502 if tr is None or tr.changes[b'origrepolen'] < len(self):
2502 if tr is None or tr.changes[b'origrepolen'] < len(self):
2503 # accessing the 'ser ved' branchmap should refresh all the others,
2503 # accessing the 'ser ved' branchmap should refresh all the others,
2504 self.ui.debug(b'updating the branch cache\n')
2504 self.ui.debug(b'updating the branch cache\n')
2505 self.filtered(b'served').branchmap()
2505 self.filtered(b'served').branchmap()
2506 self.filtered(b'served.hidden').branchmap()
2506 self.filtered(b'served.hidden').branchmap()
2507
2507
2508 if full:
2508 if full:
2509 unfi = self.unfiltered()
2509 unfi = self.unfiltered()
2510
2510
2511 self.changelog.update_caches(transaction=tr)
2511 self.changelog.update_caches(transaction=tr)
2512
2512
2513 rbc = unfi.revbranchcache()
2513 rbc = unfi.revbranchcache()
2514 for r in unfi.changelog:
2514 for r in unfi.changelog:
2515 rbc.branchinfo(r)
2515 rbc.branchinfo(r)
2516 rbc.write()
2516 rbc.write()
2517
2517
2518 # ensure the working copy parents are in the manifestfulltextcache
2518 # ensure the working copy parents are in the manifestfulltextcache
2519 for ctx in self[b'.'].parents():
2519 for ctx in self[b'.'].parents():
2520 ctx.manifest() # accessing the manifest is enough
2520 ctx.manifest() # accessing the manifest is enough
2521
2521
2522 # accessing fnode cache warms the cache
2522 # accessing fnode cache warms the cache
2523 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2523 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2524 # accessing tags warm the cache
2524 # accessing tags warm the cache
2525 self.tags()
2525 self.tags()
2526 self.filtered(b'served').tags()
2526 self.filtered(b'served').tags()
2527
2527
2528 # The `full` arg is documented as updating even the lazily-loaded
2528 # The `full` arg is documented as updating even the lazily-loaded
2529 # caches immediately, so we're forcing a write to cause these caches
2529 # caches immediately, so we're forcing a write to cause these caches
2530 # to be warmed up even if they haven't explicitly been requested
2530 # to be warmed up even if they haven't explicitly been requested
2531 # yet (if they've never been used by hg, they won't ever have been
2531 # yet (if they've never been used by hg, they won't ever have been
2532 # written, even if they're a subset of another kind of cache that
2532 # written, even if they're a subset of another kind of cache that
2533 # *has* been used).
2533 # *has* been used).
2534 for filt in repoview.filtertable.keys():
2534 for filt in repoview.filtertable.keys():
2535 filtered = self.filtered(filt)
2535 filtered = self.filtered(filt)
2536 filtered.branchmap().write(filtered)
2536 filtered.branchmap().write(filtered)
2537
2537
2538 def invalidatecaches(self):
2538 def invalidatecaches(self):
2539
2539
2540 if '_tagscache' in vars(self):
2540 if '_tagscache' in vars(self):
2541 # can't use delattr on proxy
2541 # can't use delattr on proxy
2542 del self.__dict__['_tagscache']
2542 del self.__dict__['_tagscache']
2543
2543
2544 self._branchcaches.clear()
2544 self._branchcaches.clear()
2545 self.invalidatevolatilesets()
2545 self.invalidatevolatilesets()
2546 self._sparsesignaturecache.clear()
2546 self._sparsesignaturecache.clear()
2547
2547
2548 def invalidatevolatilesets(self):
2548 def invalidatevolatilesets(self):
2549 self.filteredrevcache.clear()
2549 self.filteredrevcache.clear()
2550 obsolete.clearobscaches(self)
2550 obsolete.clearobscaches(self)
2551 self._quick_access_changeid_invalidate()
2551 self._quick_access_changeid_invalidate()
2552
2552
2553 def invalidatedirstate(self):
2553 def invalidatedirstate(self):
2554 '''Invalidates the dirstate, causing the next call to dirstate
2554 '''Invalidates the dirstate, causing the next call to dirstate
2555 to check if it was modified since the last time it was read,
2555 to check if it was modified since the last time it was read,
2556 rereading it if it has.
2556 rereading it if it has.
2557
2557
2558 This is different to dirstate.invalidate() that it doesn't always
2558 This is different to dirstate.invalidate() that it doesn't always
2559 rereads the dirstate. Use dirstate.invalidate() if you want to
2559 rereads the dirstate. Use dirstate.invalidate() if you want to
2560 explicitly read the dirstate again (i.e. restoring it to a previous
2560 explicitly read the dirstate again (i.e. restoring it to a previous
2561 known good state).'''
2561 known good state).'''
2562 if hasunfilteredcache(self, 'dirstate'):
2562 if hasunfilteredcache(self, 'dirstate'):
2563 for k in self.dirstate._filecache:
2563 for k in self.dirstate._filecache:
2564 try:
2564 try:
2565 delattr(self.dirstate, k)
2565 delattr(self.dirstate, k)
2566 except AttributeError:
2566 except AttributeError:
2567 pass
2567 pass
2568 delattr(self.unfiltered(), 'dirstate')
2568 delattr(self.unfiltered(), 'dirstate')
2569
2569
2570 def invalidate(self, clearfilecache=False):
2570 def invalidate(self, clearfilecache=False):
2571 '''Invalidates both store and non-store parts other than dirstate
2571 '''Invalidates both store and non-store parts other than dirstate
2572
2572
2573 If a transaction is running, invalidation of store is omitted,
2573 If a transaction is running, invalidation of store is omitted,
2574 because discarding in-memory changes might cause inconsistency
2574 because discarding in-memory changes might cause inconsistency
2575 (e.g. incomplete fncache causes unintentional failure, but
2575 (e.g. incomplete fncache causes unintentional failure, but
2576 redundant one doesn't).
2576 redundant one doesn't).
2577 '''
2577 '''
2578 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2578 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2579 for k in list(self._filecache.keys()):
2579 for k in list(self._filecache.keys()):
2580 # dirstate is invalidated separately in invalidatedirstate()
2580 # dirstate is invalidated separately in invalidatedirstate()
2581 if k == b'dirstate':
2581 if k == b'dirstate':
2582 continue
2582 continue
2583 if (
2583 if (
2584 k == b'changelog'
2584 k == b'changelog'
2585 and self.currenttransaction()
2585 and self.currenttransaction()
2586 and self.changelog._delayed
2586 and self.changelog._delayed
2587 ):
2587 ):
2588 # The changelog object may store unwritten revisions. We don't
2588 # The changelog object may store unwritten revisions. We don't
2589 # want to lose them.
2589 # want to lose them.
2590 # TODO: Solve the problem instead of working around it.
2590 # TODO: Solve the problem instead of working around it.
2591 continue
2591 continue
2592
2592
2593 if clearfilecache:
2593 if clearfilecache:
2594 del self._filecache[k]
2594 del self._filecache[k]
2595 try:
2595 try:
2596 delattr(unfiltered, k)
2596 delattr(unfiltered, k)
2597 except AttributeError:
2597 except AttributeError:
2598 pass
2598 pass
2599 self.invalidatecaches()
2599 self.invalidatecaches()
2600 if not self.currenttransaction():
2600 if not self.currenttransaction():
2601 # TODO: Changing contents of store outside transaction
2601 # TODO: Changing contents of store outside transaction
2602 # causes inconsistency. We should make in-memory store
2602 # causes inconsistency. We should make in-memory store
2603 # changes detectable, and abort if changed.
2603 # changes detectable, and abort if changed.
2604 self.store.invalidatecaches()
2604 self.store.invalidatecaches()
2605
2605
2606 def invalidateall(self):
2606 def invalidateall(self):
2607 '''Fully invalidates both store and non-store parts, causing the
2607 '''Fully invalidates both store and non-store parts, causing the
2608 subsequent operation to reread any outside changes.'''
2608 subsequent operation to reread any outside changes.'''
2609 # extension should hook this to invalidate its caches
2609 # extension should hook this to invalidate its caches
2610 self.invalidate()
2610 self.invalidate()
2611 self.invalidatedirstate()
2611 self.invalidatedirstate()
2612
2612
2613 @unfilteredmethod
2613 @unfilteredmethod
2614 def _refreshfilecachestats(self, tr):
2614 def _refreshfilecachestats(self, tr):
2615 """Reload stats of cached files so that they are flagged as valid"""
2615 """Reload stats of cached files so that they are flagged as valid"""
2616 for k, ce in self._filecache.items():
2616 for k, ce in self._filecache.items():
2617 k = pycompat.sysstr(k)
2617 k = pycompat.sysstr(k)
2618 if k == 'dirstate' or k not in self.__dict__:
2618 if k == 'dirstate' or k not in self.__dict__:
2619 continue
2619 continue
2620 ce.refresh()
2620 ce.refresh()
2621
2621
2622 def _lock(
2622 def _lock(
2623 self,
2623 self,
2624 vfs,
2624 vfs,
2625 lockname,
2625 lockname,
2626 wait,
2626 wait,
2627 releasefn,
2627 releasefn,
2628 acquirefn,
2628 acquirefn,
2629 desc,
2629 desc,
2630 inheritchecker=None,
2630 inheritchecker=None,
2631 parentenvvar=None,
2631 parentenvvar=None,
2632 ):
2632 ):
2633 parentlock = None
2633 parentlock = None
2634 # the contents of parentenvvar are used by the underlying lock to
2634 # the contents of parentenvvar are used by the underlying lock to
2635 # determine whether it can be inherited
2635 # determine whether it can be inherited
2636 if parentenvvar is not None:
2636 if parentenvvar is not None:
2637 parentlock = encoding.environ.get(parentenvvar)
2637 parentlock = encoding.environ.get(parentenvvar)
2638
2638
2639 timeout = 0
2639 timeout = 0
2640 warntimeout = 0
2640 warntimeout = 0
2641 if wait:
2641 if wait:
2642 timeout = self.ui.configint(b"ui", b"timeout")
2642 timeout = self.ui.configint(b"ui", b"timeout")
2643 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2643 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2644 # internal config: ui.signal-safe-lock
2644 # internal config: ui.signal-safe-lock
2645 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2645 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2646
2646
2647 l = lockmod.trylock(
2647 l = lockmod.trylock(
2648 self.ui,
2648 self.ui,
2649 vfs,
2649 vfs,
2650 lockname,
2650 lockname,
2651 timeout,
2651 timeout,
2652 warntimeout,
2652 warntimeout,
2653 releasefn=releasefn,
2653 releasefn=releasefn,
2654 acquirefn=acquirefn,
2654 acquirefn=acquirefn,
2655 desc=desc,
2655 desc=desc,
2656 inheritchecker=inheritchecker,
2656 inheritchecker=inheritchecker,
2657 parentlock=parentlock,
2657 parentlock=parentlock,
2658 signalsafe=signalsafe,
2658 signalsafe=signalsafe,
2659 )
2659 )
2660 return l
2660 return l
2661
2661
2662 def _afterlock(self, callback):
2662 def _afterlock(self, callback):
2663 """add a callback to be run when the repository is fully unlocked
2663 """add a callback to be run when the repository is fully unlocked
2664
2664
2665 The callback will be executed when the outermost lock is released
2665 The callback will be executed when the outermost lock is released
2666 (with wlock being higher level than 'lock')."""
2666 (with wlock being higher level than 'lock')."""
2667 for ref in (self._wlockref, self._lockref):
2667 for ref in (self._wlockref, self._lockref):
2668 l = ref and ref()
2668 l = ref and ref()
2669 if l and l.held:
2669 if l and l.held:
2670 l.postrelease.append(callback)
2670 l.postrelease.append(callback)
2671 break
2671 break
2672 else: # no lock have been found.
2672 else: # no lock have been found.
2673 callback(True)
2673 callback(True)
2674
2674
2675 def lock(self, wait=True):
2675 def lock(self, wait=True):
2676 '''Lock the repository store (.hg/store) and return a weak reference
2676 '''Lock the repository store (.hg/store) and return a weak reference
2677 to the lock. Use this before modifying the store (e.g. committing or
2677 to the lock. Use this before modifying the store (e.g. committing or
2678 stripping). If you are opening a transaction, get a lock as well.)
2678 stripping). If you are opening a transaction, get a lock as well.)
2679
2679
2680 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2680 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2681 'wlock' first to avoid a dead-lock hazard.'''
2681 'wlock' first to avoid a dead-lock hazard.'''
2682 l = self._currentlock(self._lockref)
2682 l = self._currentlock(self._lockref)
2683 if l is not None:
2683 if l is not None:
2684 l.lock()
2684 l.lock()
2685 return l
2685 return l
2686
2686
2687 l = self._lock(
2687 l = self._lock(
2688 vfs=self.svfs,
2688 vfs=self.svfs,
2689 lockname=b"lock",
2689 lockname=b"lock",
2690 wait=wait,
2690 wait=wait,
2691 releasefn=None,
2691 releasefn=None,
2692 acquirefn=self.invalidate,
2692 acquirefn=self.invalidate,
2693 desc=_(b'repository %s') % self.origroot,
2693 desc=_(b'repository %s') % self.origroot,
2694 )
2694 )
2695 self._lockref = weakref.ref(l)
2695 self._lockref = weakref.ref(l)
2696 return l
2696 return l
2697
2697
2698 def _wlockchecktransaction(self):
2698 def _wlockchecktransaction(self):
2699 if self.currenttransaction() is not None:
2699 if self.currenttransaction() is not None:
2700 raise error.LockInheritanceContractViolation(
2700 raise error.LockInheritanceContractViolation(
2701 b'wlock cannot be inherited in the middle of a transaction'
2701 b'wlock cannot be inherited in the middle of a transaction'
2702 )
2702 )
2703
2703
2704 def wlock(self, wait=True):
2704 def wlock(self, wait=True):
2705 '''Lock the non-store parts of the repository (everything under
2705 '''Lock the non-store parts of the repository (everything under
2706 .hg except .hg/store) and return a weak reference to the lock.
2706 .hg except .hg/store) and return a weak reference to the lock.
2707
2707
2708 Use this before modifying files in .hg.
2708 Use this before modifying files in .hg.
2709
2709
2710 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2710 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2711 'wlock' first to avoid a dead-lock hazard.'''
2711 'wlock' first to avoid a dead-lock hazard.'''
2712 l = self._wlockref and self._wlockref()
2712 l = self._wlockref and self._wlockref()
2713 if l is not None and l.held:
2713 if l is not None and l.held:
2714 l.lock()
2714 l.lock()
2715 return l
2715 return l
2716
2716
2717 # We do not need to check for non-waiting lock acquisition. Such
2717 # We do not need to check for non-waiting lock acquisition. Such
2718 # acquisition would not cause dead-lock as they would just fail.
2718 # acquisition would not cause dead-lock as they would just fail.
2719 if wait and (
2719 if wait and (
2720 self.ui.configbool(b'devel', b'all-warnings')
2720 self.ui.configbool(b'devel', b'all-warnings')
2721 or self.ui.configbool(b'devel', b'check-locks')
2721 or self.ui.configbool(b'devel', b'check-locks')
2722 ):
2722 ):
2723 if self._currentlock(self._lockref) is not None:
2723 if self._currentlock(self._lockref) is not None:
2724 self.ui.develwarn(b'"wlock" acquired after "lock"')
2724 self.ui.develwarn(b'"wlock" acquired after "lock"')
2725
2725
2726 def unlock():
2726 def unlock():
2727 if self.dirstate.pendingparentchange():
2727 if self.dirstate.pendingparentchange():
2728 self.dirstate.invalidate()
2728 self.dirstate.invalidate()
2729 else:
2729 else:
2730 self.dirstate.write(None)
2730 self.dirstate.write(None)
2731
2731
2732 self._filecache[b'dirstate'].refresh()
2732 self._filecache[b'dirstate'].refresh()
2733
2733
2734 l = self._lock(
2734 l = self._lock(
2735 self.vfs,
2735 self.vfs,
2736 b"wlock",
2736 b"wlock",
2737 wait,
2737 wait,
2738 unlock,
2738 unlock,
2739 self.invalidatedirstate,
2739 self.invalidatedirstate,
2740 _(b'working directory of %s') % self.origroot,
2740 _(b'working directory of %s') % self.origroot,
2741 inheritchecker=self._wlockchecktransaction,
2741 inheritchecker=self._wlockchecktransaction,
2742 parentenvvar=b'HG_WLOCK_LOCKER',
2742 parentenvvar=b'HG_WLOCK_LOCKER',
2743 )
2743 )
2744 self._wlockref = weakref.ref(l)
2744 self._wlockref = weakref.ref(l)
2745 return l
2745 return l
2746
2746
2747 def _currentlock(self, lockref):
2747 def _currentlock(self, lockref):
2748 """Returns the lock if it's held, or None if it's not."""
2748 """Returns the lock if it's held, or None if it's not."""
2749 if lockref is None:
2749 if lockref is None:
2750 return None
2750 return None
2751 l = lockref()
2751 l = lockref()
2752 if l is None or not l.held:
2752 if l is None or not l.held:
2753 return None
2753 return None
2754 return l
2754 return l
2755
2755
2756 def currentwlock(self):
2756 def currentwlock(self):
2757 """Returns the wlock if it's held, or None if it's not."""
2757 """Returns the wlock if it's held, or None if it's not."""
2758 return self._currentlock(self._wlockref)
2758 return self._currentlock(self._wlockref)
2759
2759
2760 def _filecommit(
2760 def _filecommit(
2761 self,
2761 self,
2762 fctx,
2762 fctx,
2763 manifest1,
2763 manifest1,
2764 manifest2,
2764 manifest2,
2765 linkrev,
2765 linkrev,
2766 tr,
2766 tr,
2767 changelist,
2767 changelist,
2768 includecopymeta,
2768 includecopymeta,
2769 ):
2769 ):
2770 """
2770 """
2771 commit an individual file as part of a larger transaction
2771 commit an individual file as part of a larger transaction
2772 """
2772 """
2773
2773
2774 fname = fctx.path()
2774 fname = fctx.path()
2775 fparent1 = manifest1.get(fname, nullid)
2775 fparent1 = manifest1.get(fname, nullid)
2776 fparent2 = manifest2.get(fname, nullid)
2776 fparent2 = manifest2.get(fname, nullid)
2777 if isinstance(fctx, context.filectx):
2777 if isinstance(fctx, context.filectx):
2778 node = fctx.filenode()
2778 node = fctx.filenode()
2779 if node in [fparent1, fparent2]:
2779 if node in [fparent1, fparent2]:
2780 self.ui.debug(b'reusing %s filelog entry\n' % fname)
2780 self.ui.debug(b'reusing %s filelog entry\n' % fname)
2781 if (
2781 if (
2782 fparent1 != nullid
2782 fparent1 != nullid
2783 and manifest1.flags(fname) != fctx.flags()
2783 and manifest1.flags(fname) != fctx.flags()
2784 ) or (
2784 ) or (
2785 fparent2 != nullid
2785 fparent2 != nullid
2786 and manifest2.flags(fname) != fctx.flags()
2786 and manifest2.flags(fname) != fctx.flags()
2787 ):
2787 ):
2788 changelist.append(fname)
2788 changelist.append(fname)
2789 return node
2789 return node
2790
2790
2791 flog = self.file(fname)
2791 flog = self.file(fname)
2792 meta = {}
2792 meta = {}
2793 cfname = fctx.copysource()
2793 cfname = fctx.copysource()
2794 if cfname and cfname != fname:
2794 if cfname and cfname != fname:
2795 # Mark the new revision of this file as a copy of another
2795 # Mark the new revision of this file as a copy of another
2796 # file. This copy data will effectively act as a parent
2796 # file. This copy data will effectively act as a parent
2797 # of this new revision. If this is a merge, the first
2797 # of this new revision. If this is a merge, the first
2798 # parent will be the nullid (meaning "look up the copy data")
2798 # parent will be the nullid (meaning "look up the copy data")
2799 # and the second one will be the other parent. For example:
2799 # and the second one will be the other parent. For example:
2800 #
2800 #
2801 # 0 --- 1 --- 3 rev1 changes file foo
2801 # 0 --- 1 --- 3 rev1 changes file foo
2802 # \ / rev2 renames foo to bar and changes it
2802 # \ / rev2 renames foo to bar and changes it
2803 # \- 2 -/ rev3 should have bar with all changes and
2803 # \- 2 -/ rev3 should have bar with all changes and
2804 # should record that bar descends from
2804 # should record that bar descends from
2805 # bar in rev2 and foo in rev1
2805 # bar in rev2 and foo in rev1
2806 #
2806 #
2807 # this allows this merge to succeed:
2807 # this allows this merge to succeed:
2808 #
2808 #
2809 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2809 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2810 # \ / merging rev3 and rev4 should use bar@rev2
2810 # \ / merging rev3 and rev4 should use bar@rev2
2811 # \- 2 --- 4 as the merge base
2811 # \- 2 --- 4 as the merge base
2812 #
2812 #
2813
2813
2814 cnode = manifest1.get(cfname)
2814 cnode = manifest1.get(cfname)
2815 newfparent = fparent2
2815 newfparent = fparent2
2816
2816
2817 if manifest2: # branch merge
2817 if manifest2: # branch merge
2818 if fparent2 == nullid or cnode is None: # copied on remote side
2818 if fparent2 == nullid or cnode is None: # copied on remote side
2819 if cfname in manifest2:
2819 if cfname in manifest2:
2820 cnode = manifest2[cfname]
2820 cnode = manifest2[cfname]
2821 newfparent = fparent1
2821 newfparent = fparent1
2822
2822
2823 # Here, we used to search backwards through history to try to find
2823 # Here, we used to search backwards through history to try to find
2824 # where the file copy came from if the source of a copy was not in
2824 # where the file copy came from if the source of a copy was not in
2825 # the parent directory. However, this doesn't actually make sense to
2825 # the parent directory. However, this doesn't actually make sense to
2826 # do (what does a copy from something not in your working copy even
2826 # do (what does a copy from something not in your working copy even
2827 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2827 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2828 # the user that copy information was dropped, so if they didn't
2828 # the user that copy information was dropped, so if they didn't
2829 # expect this outcome it can be fixed, but this is the correct
2829 # expect this outcome it can be fixed, but this is the correct
2830 # behavior in this circumstance.
2830 # behavior in this circumstance.
2831
2831
2832 if cnode:
2832 if cnode:
2833 self.ui.debug(
2833 self.ui.debug(
2834 b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode))
2834 b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode))
2835 )
2835 )
2836 if includecopymeta:
2836 if includecopymeta:
2837 meta[b"copy"] = cfname
2837 meta[b"copy"] = cfname
2838 meta[b"copyrev"] = hex(cnode)
2838 meta[b"copyrev"] = hex(cnode)
2839 fparent1, fparent2 = nullid, newfparent
2839 fparent1, fparent2 = nullid, newfparent
2840 else:
2840 else:
2841 self.ui.warn(
2841 self.ui.warn(
2842 _(
2842 _(
2843 b"warning: can't find ancestor for '%s' "
2843 b"warning: can't find ancestor for '%s' "
2844 b"copied from '%s'!\n"
2844 b"copied from '%s'!\n"
2845 )
2845 )
2846 % (fname, cfname)
2846 % (fname, cfname)
2847 )
2847 )
2848
2848
2849 elif fparent1 == nullid:
2849 elif fparent1 == nullid:
2850 fparent1, fparent2 = fparent2, nullid
2850 fparent1, fparent2 = fparent2, nullid
2851 elif fparent2 != nullid:
2851 elif fparent2 != nullid:
2852 # is one parent an ancestor of the other?
2852 # is one parent an ancestor of the other?
2853 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2853 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2854 if fparent1 in fparentancestors:
2854 if fparent1 in fparentancestors:
2855 fparent1, fparent2 = fparent2, nullid
2855 fparent1, fparent2 = fparent2, nullid
2856 elif fparent2 in fparentancestors:
2856 elif fparent2 in fparentancestors:
2857 fparent2 = nullid
2857 fparent2 = nullid
2858 elif not fparentancestors:
2859 # TODO: this whole if-else might be simplified much more
2860 ms = mergemod.mergestate.read(self)
2861 if (
2862 fname in ms
2863 and ms[fname] == mergemod.MERGE_RECORD_MERGED_OTHER
2864 ):
2865 fparent1, fparent2 = fparent2, nullid
2858
2866
2859 # is the file changed?
2867 # is the file changed?
2860 text = fctx.data()
2868 text = fctx.data()
2861 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2869 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2862 changelist.append(fname)
2870 changelist.append(fname)
2863 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2871 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2864 # are just the flags changed during merge?
2872 # are just the flags changed during merge?
2865 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2873 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2866 changelist.append(fname)
2874 changelist.append(fname)
2867
2875
2868 return fparent1
2876 return fparent1
2869
2877
2870 def checkcommitpatterns(self, wctx, match, status, fail):
2878 def checkcommitpatterns(self, wctx, match, status, fail):
2871 """check for commit arguments that aren't committable"""
2879 """check for commit arguments that aren't committable"""
2872 if match.isexact() or match.prefix():
2880 if match.isexact() or match.prefix():
2873 matched = set(status.modified + status.added + status.removed)
2881 matched = set(status.modified + status.added + status.removed)
2874
2882
2875 for f in match.files():
2883 for f in match.files():
2876 f = self.dirstate.normalize(f)
2884 f = self.dirstate.normalize(f)
2877 if f == b'.' or f in matched or f in wctx.substate:
2885 if f == b'.' or f in matched or f in wctx.substate:
2878 continue
2886 continue
2879 if f in status.deleted:
2887 if f in status.deleted:
2880 fail(f, _(b'file not found!'))
2888 fail(f, _(b'file not found!'))
2881 # Is it a directory that exists or used to exist?
2889 # Is it a directory that exists or used to exist?
2882 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2890 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2883 d = f + b'/'
2891 d = f + b'/'
2884 for mf in matched:
2892 for mf in matched:
2885 if mf.startswith(d):
2893 if mf.startswith(d):
2886 break
2894 break
2887 else:
2895 else:
2888 fail(f, _(b"no match under directory!"))
2896 fail(f, _(b"no match under directory!"))
2889 elif f not in self.dirstate:
2897 elif f not in self.dirstate:
2890 fail(f, _(b"file not tracked!"))
2898 fail(f, _(b"file not tracked!"))
2891
2899
2892 @unfilteredmethod
2900 @unfilteredmethod
2893 def commit(
2901 def commit(
2894 self,
2902 self,
2895 text=b"",
2903 text=b"",
2896 user=None,
2904 user=None,
2897 date=None,
2905 date=None,
2898 match=None,
2906 match=None,
2899 force=False,
2907 force=False,
2900 editor=None,
2908 editor=None,
2901 extra=None,
2909 extra=None,
2902 ):
2910 ):
2903 """Add a new revision to current repository.
2911 """Add a new revision to current repository.
2904
2912
2905 Revision information is gathered from the working directory,
2913 Revision information is gathered from the working directory,
2906 match can be used to filter the committed files. If editor is
2914 match can be used to filter the committed files. If editor is
2907 supplied, it is called to get a commit message.
2915 supplied, it is called to get a commit message.
2908 """
2916 """
2909 if extra is None:
2917 if extra is None:
2910 extra = {}
2918 extra = {}
2911
2919
2912 def fail(f, msg):
2920 def fail(f, msg):
2913 raise error.Abort(b'%s: %s' % (f, msg))
2921 raise error.Abort(b'%s: %s' % (f, msg))
2914
2922
2915 if not match:
2923 if not match:
2916 match = matchmod.always()
2924 match = matchmod.always()
2917
2925
2918 if not force:
2926 if not force:
2919 match.bad = fail
2927 match.bad = fail
2920
2928
2921 # lock() for recent changelog (see issue4368)
2929 # lock() for recent changelog (see issue4368)
2922 with self.wlock(), self.lock():
2930 with self.wlock(), self.lock():
2923 wctx = self[None]
2931 wctx = self[None]
2924 merge = len(wctx.parents()) > 1
2932 merge = len(wctx.parents()) > 1
2925
2933
2926 if not force and merge and not match.always():
2934 if not force and merge and not match.always():
2927 raise error.Abort(
2935 raise error.Abort(
2928 _(
2936 _(
2929 b'cannot partially commit a merge '
2937 b'cannot partially commit a merge '
2930 b'(do not specify files or patterns)'
2938 b'(do not specify files or patterns)'
2931 )
2939 )
2932 )
2940 )
2933
2941
2934 status = self.status(match=match, clean=force)
2942 status = self.status(match=match, clean=force)
2935 if force:
2943 if force:
2936 status.modified.extend(
2944 status.modified.extend(
2937 status.clean
2945 status.clean
2938 ) # mq may commit clean files
2946 ) # mq may commit clean files
2939
2947
2940 # check subrepos
2948 # check subrepos
2941 subs, commitsubs, newstate = subrepoutil.precommit(
2949 subs, commitsubs, newstate = subrepoutil.precommit(
2942 self.ui, wctx, status, match, force=force
2950 self.ui, wctx, status, match, force=force
2943 )
2951 )
2944
2952
2945 # make sure all explicit patterns are matched
2953 # make sure all explicit patterns are matched
2946 if not force:
2954 if not force:
2947 self.checkcommitpatterns(wctx, match, status, fail)
2955 self.checkcommitpatterns(wctx, match, status, fail)
2948
2956
2949 cctx = context.workingcommitctx(
2957 cctx = context.workingcommitctx(
2950 self, status, text, user, date, extra
2958 self, status, text, user, date, extra
2951 )
2959 )
2952
2960
2953 ms = mergemod.mergestate.read(self)
2961 ms = mergemod.mergestate.read(self)
2954 mergeutil.checkunresolved(ms)
2962 mergeutil.checkunresolved(ms)
2955
2963
2956 # internal config: ui.allowemptycommit
2964 # internal config: ui.allowemptycommit
2957 allowemptycommit = (
2965 allowemptycommit = (
2958 wctx.branch() != wctx.p1().branch()
2966 wctx.branch() != wctx.p1().branch()
2959 or extra.get(b'close')
2967 or extra.get(b'close')
2960 or merge
2968 or merge
2961 or cctx.files()
2969 or cctx.files()
2962 or self.ui.configbool(b'ui', b'allowemptycommit')
2970 or self.ui.configbool(b'ui', b'allowemptycommit')
2963 )
2971 )
2964 if not allowemptycommit:
2972 if not allowemptycommit:
2965 self.ui.debug(b'nothing to commit, clearing merge state\n')
2973 self.ui.debug(b'nothing to commit, clearing merge state\n')
2966 ms.reset()
2974 ms.reset()
2967 return None
2975 return None
2968
2976
2969 if merge and cctx.deleted():
2977 if merge and cctx.deleted():
2970 raise error.Abort(_(b"cannot commit merge with missing files"))
2978 raise error.Abort(_(b"cannot commit merge with missing files"))
2971
2979
2972 if editor:
2980 if editor:
2973 cctx._text = editor(self, cctx, subs)
2981 cctx._text = editor(self, cctx, subs)
2974 edited = text != cctx._text
2982 edited = text != cctx._text
2975
2983
2976 # Save commit message in case this transaction gets rolled back
2984 # Save commit message in case this transaction gets rolled back
2977 # (e.g. by a pretxncommit hook). Leave the content alone on
2985 # (e.g. by a pretxncommit hook). Leave the content alone on
2978 # the assumption that the user will use the same editor again.
2986 # the assumption that the user will use the same editor again.
2979 msgfn = self.savecommitmessage(cctx._text)
2987 msgfn = self.savecommitmessage(cctx._text)
2980
2988
2981 # commit subs and write new state
2989 # commit subs and write new state
2982 if subs:
2990 if subs:
2983 uipathfn = scmutil.getuipathfn(self)
2991 uipathfn = scmutil.getuipathfn(self)
2984 for s in sorted(commitsubs):
2992 for s in sorted(commitsubs):
2985 sub = wctx.sub(s)
2993 sub = wctx.sub(s)
2986 self.ui.status(
2994 self.ui.status(
2987 _(b'committing subrepository %s\n')
2995 _(b'committing subrepository %s\n')
2988 % uipathfn(subrepoutil.subrelpath(sub))
2996 % uipathfn(subrepoutil.subrelpath(sub))
2989 )
2997 )
2990 sr = sub.commit(cctx._text, user, date)
2998 sr = sub.commit(cctx._text, user, date)
2991 newstate[s] = (newstate[s][0], sr)
2999 newstate[s] = (newstate[s][0], sr)
2992 subrepoutil.writestate(self, newstate)
3000 subrepoutil.writestate(self, newstate)
2993
3001
2994 p1, p2 = self.dirstate.parents()
3002 p1, p2 = self.dirstate.parents()
2995 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
3003 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
2996 try:
3004 try:
2997 self.hook(
3005 self.hook(
2998 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3006 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
2999 )
3007 )
3000 with self.transaction(b'commit'):
3008 with self.transaction(b'commit'):
3001 ret = self.commitctx(cctx, True)
3009 ret = self.commitctx(cctx, True)
3002 # update bookmarks, dirstate and mergestate
3010 # update bookmarks, dirstate and mergestate
3003 bookmarks.update(self, [p1, p2], ret)
3011 bookmarks.update(self, [p1, p2], ret)
3004 cctx.markcommitted(ret)
3012 cctx.markcommitted(ret)
3005 ms.reset()
3013 ms.reset()
3006 except: # re-raises
3014 except: # re-raises
3007 if edited:
3015 if edited:
3008 self.ui.write(
3016 self.ui.write(
3009 _(b'note: commit message saved in %s\n') % msgfn
3017 _(b'note: commit message saved in %s\n') % msgfn
3010 )
3018 )
3011 raise
3019 raise
3012
3020
3013 def commithook(unused_success):
3021 def commithook(unused_success):
3014 # hack for command that use a temporary commit (eg: histedit)
3022 # hack for command that use a temporary commit (eg: histedit)
3015 # temporary commit got stripped before hook release
3023 # temporary commit got stripped before hook release
3016 if self.changelog.hasnode(ret):
3024 if self.changelog.hasnode(ret):
3017 self.hook(
3025 self.hook(
3018 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3026 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3019 )
3027 )
3020
3028
3021 self._afterlock(commithook)
3029 self._afterlock(commithook)
3022 return ret
3030 return ret
3023
3031
3024 @unfilteredmethod
3032 @unfilteredmethod
3025 def commitctx(self, ctx, error=False, origctx=None):
3033 def commitctx(self, ctx, error=False, origctx=None):
3026 """Add a new revision to current repository.
3034 """Add a new revision to current repository.
3027 Revision information is passed via the context argument.
3035 Revision information is passed via the context argument.
3028
3036
3029 ctx.files() should list all files involved in this commit, i.e.
3037 ctx.files() should list all files involved in this commit, i.e.
3030 modified/added/removed files. On merge, it may be wider than the
3038 modified/added/removed files. On merge, it may be wider than the
3031 ctx.files() to be committed, since any file nodes derived directly
3039 ctx.files() to be committed, since any file nodes derived directly
3032 from p1 or p2 are excluded from the committed ctx.files().
3040 from p1 or p2 are excluded from the committed ctx.files().
3033
3041
3034 origctx is for convert to work around the problem that bug
3042 origctx is for convert to work around the problem that bug
3035 fixes to the files list in changesets change hashes. For
3043 fixes to the files list in changesets change hashes. For
3036 convert to be the identity, it can pass an origctx and this
3044 convert to be the identity, it can pass an origctx and this
3037 function will use the same files list when it makes sense to
3045 function will use the same files list when it makes sense to
3038 do so.
3046 do so.
3039 """
3047 """
3040
3048
3041 p1, p2 = ctx.p1(), ctx.p2()
3049 p1, p2 = ctx.p1(), ctx.p2()
3042 user = ctx.user()
3050 user = ctx.user()
3043
3051
3044 if self.filecopiesmode == b'changeset-sidedata':
3052 if self.filecopiesmode == b'changeset-sidedata':
3045 writechangesetcopy = True
3053 writechangesetcopy = True
3046 writefilecopymeta = True
3054 writefilecopymeta = True
3047 writecopiesto = None
3055 writecopiesto = None
3048 else:
3056 else:
3049 writecopiesto = self.ui.config(b'experimental', b'copies.write-to')
3057 writecopiesto = self.ui.config(b'experimental', b'copies.write-to')
3050 writefilecopymeta = writecopiesto != b'changeset-only'
3058 writefilecopymeta = writecopiesto != b'changeset-only'
3051 writechangesetcopy = writecopiesto in (
3059 writechangesetcopy = writecopiesto in (
3052 b'changeset-only',
3060 b'changeset-only',
3053 b'compatibility',
3061 b'compatibility',
3054 )
3062 )
3055 p1copies, p2copies = None, None
3063 p1copies, p2copies = None, None
3056 if writechangesetcopy:
3064 if writechangesetcopy:
3057 p1copies = ctx.p1copies()
3065 p1copies = ctx.p1copies()
3058 p2copies = ctx.p2copies()
3066 p2copies = ctx.p2copies()
3059 filesadded, filesremoved = None, None
3067 filesadded, filesremoved = None, None
3060 with self.lock(), self.transaction(b"commit") as tr:
3068 with self.lock(), self.transaction(b"commit") as tr:
3061 trp = weakref.proxy(tr)
3069 trp = weakref.proxy(tr)
3062
3070
3063 if ctx.manifestnode():
3071 if ctx.manifestnode():
3064 # reuse an existing manifest revision
3072 # reuse an existing manifest revision
3065 self.ui.debug(b'reusing known manifest\n')
3073 self.ui.debug(b'reusing known manifest\n')
3066 mn = ctx.manifestnode()
3074 mn = ctx.manifestnode()
3067 files = ctx.files()
3075 files = ctx.files()
3068 if writechangesetcopy:
3076 if writechangesetcopy:
3069 filesadded = ctx.filesadded()
3077 filesadded = ctx.filesadded()
3070 filesremoved = ctx.filesremoved()
3078 filesremoved = ctx.filesremoved()
3071 elif ctx.files():
3079 elif ctx.files():
3072 m1ctx = p1.manifestctx()
3080 m1ctx = p1.manifestctx()
3073 m2ctx = p2.manifestctx()
3081 m2ctx = p2.manifestctx()
3074 mctx = m1ctx.copy()
3082 mctx = m1ctx.copy()
3075
3083
3076 m = mctx.read()
3084 m = mctx.read()
3077 m1 = m1ctx.read()
3085 m1 = m1ctx.read()
3078 m2 = m2ctx.read()
3086 m2 = m2ctx.read()
3079
3087
3080 # check in files
3088 # check in files
3081 added = []
3089 added = []
3082 changed = []
3090 changed = []
3083 removed = list(ctx.removed())
3091 removed = list(ctx.removed())
3084 linkrev = len(self)
3092 linkrev = len(self)
3085 self.ui.note(_(b"committing files:\n"))
3093 self.ui.note(_(b"committing files:\n"))
3086 uipathfn = scmutil.getuipathfn(self)
3094 uipathfn = scmutil.getuipathfn(self)
3087 for f in sorted(ctx.modified() + ctx.added()):
3095 for f in sorted(ctx.modified() + ctx.added()):
3088 self.ui.note(uipathfn(f) + b"\n")
3096 self.ui.note(uipathfn(f) + b"\n")
3089 try:
3097 try:
3090 fctx = ctx[f]
3098 fctx = ctx[f]
3091 if fctx is None:
3099 if fctx is None:
3092 removed.append(f)
3100 removed.append(f)
3093 else:
3101 else:
3094 added.append(f)
3102 added.append(f)
3095 m[f] = self._filecommit(
3103 m[f] = self._filecommit(
3096 fctx,
3104 fctx,
3097 m1,
3105 m1,
3098 m2,
3106 m2,
3099 linkrev,
3107 linkrev,
3100 trp,
3108 trp,
3101 changed,
3109 changed,
3102 writefilecopymeta,
3110 writefilecopymeta,
3103 )
3111 )
3104 m.setflag(f, fctx.flags())
3112 m.setflag(f, fctx.flags())
3105 except OSError:
3113 except OSError:
3106 self.ui.warn(
3114 self.ui.warn(
3107 _(b"trouble committing %s!\n") % uipathfn(f)
3115 _(b"trouble committing %s!\n") % uipathfn(f)
3108 )
3116 )
3109 raise
3117 raise
3110 except IOError as inst:
3118 except IOError as inst:
3111 errcode = getattr(inst, 'errno', errno.ENOENT)
3119 errcode = getattr(inst, 'errno', errno.ENOENT)
3112 if error or errcode and errcode != errno.ENOENT:
3120 if error or errcode and errcode != errno.ENOENT:
3113 self.ui.warn(
3121 self.ui.warn(
3114 _(b"trouble committing %s!\n") % uipathfn(f)
3122 _(b"trouble committing %s!\n") % uipathfn(f)
3115 )
3123 )
3116 raise
3124 raise
3117
3125
3118 # update manifest
3126 # update manifest
3119 removed = [f for f in removed if f in m1 or f in m2]
3127 removed = [f for f in removed if f in m1 or f in m2]
3120 drop = sorted([f for f in removed if f in m])
3128 drop = sorted([f for f in removed if f in m])
3121 for f in drop:
3129 for f in drop:
3122 del m[f]
3130 del m[f]
3123 if p2.rev() != nullrev:
3131 if p2.rev() != nullrev:
3124
3132
3125 @util.cachefunc
3133 @util.cachefunc
3126 def mas():
3134 def mas():
3127 p1n = p1.node()
3135 p1n = p1.node()
3128 p2n = p2.node()
3136 p2n = p2.node()
3129 cahs = self.changelog.commonancestorsheads(p1n, p2n)
3137 cahs = self.changelog.commonancestorsheads(p1n, p2n)
3130 if not cahs:
3138 if not cahs:
3131 cahs = [nullrev]
3139 cahs = [nullrev]
3132 return [self[r].manifest() for r in cahs]
3140 return [self[r].manifest() for r in cahs]
3133
3141
3134 def deletionfromparent(f):
3142 def deletionfromparent(f):
3135 # When a file is removed relative to p1 in a merge, this
3143 # When a file is removed relative to p1 in a merge, this
3136 # function determines whether the absence is due to a
3144 # function determines whether the absence is due to a
3137 # deletion from a parent, or whether the merge commit
3145 # deletion from a parent, or whether the merge commit
3138 # itself deletes the file. We decide this by doing a
3146 # itself deletes the file. We decide this by doing a
3139 # simplified three way merge of the manifest entry for
3147 # simplified three way merge of the manifest entry for
3140 # the file. There are two ways we decide the merge
3148 # the file. There are two ways we decide the merge
3141 # itself didn't delete a file:
3149 # itself didn't delete a file:
3142 # - neither parent (nor the merge) contain the file
3150 # - neither parent (nor the merge) contain the file
3143 # - exactly one parent contains the file, and that
3151 # - exactly one parent contains the file, and that
3144 # parent has the same filelog entry as the merge
3152 # parent has the same filelog entry as the merge
3145 # ancestor (or all of them if there two). In other
3153 # ancestor (or all of them if there two). In other
3146 # words, that parent left the file unchanged while the
3154 # words, that parent left the file unchanged while the
3147 # other one deleted it.
3155 # other one deleted it.
3148 # One way to think about this is that deleting a file is
3156 # One way to think about this is that deleting a file is
3149 # similar to emptying it, so the list of changed files
3157 # similar to emptying it, so the list of changed files
3150 # should be similar either way. The computation
3158 # should be similar either way. The computation
3151 # described above is not done directly in _filecommit
3159 # described above is not done directly in _filecommit
3152 # when creating the list of changed files, however
3160 # when creating the list of changed files, however
3153 # it does something very similar by comparing filelog
3161 # it does something very similar by comparing filelog
3154 # nodes.
3162 # nodes.
3155 if f in m1:
3163 if f in m1:
3156 return f not in m2 and all(
3164 return f not in m2 and all(
3157 f in ma and ma.find(f) == m1.find(f)
3165 f in ma and ma.find(f) == m1.find(f)
3158 for ma in mas()
3166 for ma in mas()
3159 )
3167 )
3160 elif f in m2:
3168 elif f in m2:
3161 return all(
3169 return all(
3162 f in ma and ma.find(f) == m2.find(f)
3170 f in ma and ma.find(f) == m2.find(f)
3163 for ma in mas()
3171 for ma in mas()
3164 )
3172 )
3165 else:
3173 else:
3166 return True
3174 return True
3167
3175
3168 removed = [f for f in removed if not deletionfromparent(f)]
3176 removed = [f for f in removed if not deletionfromparent(f)]
3169
3177
3170 files = changed + removed
3178 files = changed + removed
3171 md = None
3179 md = None
3172 if not files:
3180 if not files:
3173 # if no "files" actually changed in terms of the changelog,
3181 # if no "files" actually changed in terms of the changelog,
3174 # try hard to detect unmodified manifest entry so that the
3182 # try hard to detect unmodified manifest entry so that the
3175 # exact same commit can be reproduced later on convert.
3183 # exact same commit can be reproduced later on convert.
3176 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
3184 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
3177 if not files and md:
3185 if not files and md:
3178 self.ui.debug(
3186 self.ui.debug(
3179 b'not reusing manifest (no file change in '
3187 b'not reusing manifest (no file change in '
3180 b'changelog, but manifest differs)\n'
3188 b'changelog, but manifest differs)\n'
3181 )
3189 )
3182 if files or md:
3190 if files or md:
3183 self.ui.note(_(b"committing manifest\n"))
3191 self.ui.note(_(b"committing manifest\n"))
3184 # we're using narrowmatch here since it's already applied at
3192 # we're using narrowmatch here since it's already applied at
3185 # other stages (such as dirstate.walk), so we're already
3193 # other stages (such as dirstate.walk), so we're already
3186 # ignoring things outside of narrowspec in most cases. The
3194 # ignoring things outside of narrowspec in most cases. The
3187 # one case where we might have files outside the narrowspec
3195 # one case where we might have files outside the narrowspec
3188 # at this point is merges, and we already error out in the
3196 # at this point is merges, and we already error out in the
3189 # case where the merge has files outside of the narrowspec,
3197 # case where the merge has files outside of the narrowspec,
3190 # so this is safe.
3198 # so this is safe.
3191 mn = mctx.write(
3199 mn = mctx.write(
3192 trp,
3200 trp,
3193 linkrev,
3201 linkrev,
3194 p1.manifestnode(),
3202 p1.manifestnode(),
3195 p2.manifestnode(),
3203 p2.manifestnode(),
3196 added,
3204 added,
3197 drop,
3205 drop,
3198 match=self.narrowmatch(),
3206 match=self.narrowmatch(),
3199 )
3207 )
3200
3208
3201 if writechangesetcopy:
3209 if writechangesetcopy:
3202 filesadded = [
3210 filesadded = [
3203 f for f in changed if not (f in m1 or f in m2)
3211 f for f in changed if not (f in m1 or f in m2)
3204 ]
3212 ]
3205 filesremoved = removed
3213 filesremoved = removed
3206 else:
3214 else:
3207 self.ui.debug(
3215 self.ui.debug(
3208 b'reusing manifest from p1 (listed files '
3216 b'reusing manifest from p1 (listed files '
3209 b'actually unchanged)\n'
3217 b'actually unchanged)\n'
3210 )
3218 )
3211 mn = p1.manifestnode()
3219 mn = p1.manifestnode()
3212 else:
3220 else:
3213 self.ui.debug(b'reusing manifest from p1 (no file change)\n')
3221 self.ui.debug(b'reusing manifest from p1 (no file change)\n')
3214 mn = p1.manifestnode()
3222 mn = p1.manifestnode()
3215 files = []
3223 files = []
3216
3224
3217 if writecopiesto == b'changeset-only':
3225 if writecopiesto == b'changeset-only':
3218 # If writing only to changeset extras, use None to indicate that
3226 # If writing only to changeset extras, use None to indicate that
3219 # no entry should be written. If writing to both, write an empty
3227 # no entry should be written. If writing to both, write an empty
3220 # entry to prevent the reader from falling back to reading
3228 # entry to prevent the reader from falling back to reading
3221 # filelogs.
3229 # filelogs.
3222 p1copies = p1copies or None
3230 p1copies = p1copies or None
3223 p2copies = p2copies or None
3231 p2copies = p2copies or None
3224 filesadded = filesadded or None
3232 filesadded = filesadded or None
3225 filesremoved = filesremoved or None
3233 filesremoved = filesremoved or None
3226
3234
3227 if origctx and origctx.manifestnode() == mn:
3235 if origctx and origctx.manifestnode() == mn:
3228 files = origctx.files()
3236 files = origctx.files()
3229
3237
3230 # update changelog
3238 # update changelog
3231 self.ui.note(_(b"committing changelog\n"))
3239 self.ui.note(_(b"committing changelog\n"))
3232 self.changelog.delayupdate(tr)
3240 self.changelog.delayupdate(tr)
3233 n = self.changelog.add(
3241 n = self.changelog.add(
3234 mn,
3242 mn,
3235 files,
3243 files,
3236 ctx.description(),
3244 ctx.description(),
3237 trp,
3245 trp,
3238 p1.node(),
3246 p1.node(),
3239 p2.node(),
3247 p2.node(),
3240 user,
3248 user,
3241 ctx.date(),
3249 ctx.date(),
3242 ctx.extra().copy(),
3250 ctx.extra().copy(),
3243 p1copies,
3251 p1copies,
3244 p2copies,
3252 p2copies,
3245 filesadded,
3253 filesadded,
3246 filesremoved,
3254 filesremoved,
3247 )
3255 )
3248 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
3256 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
3249 self.hook(
3257 self.hook(
3250 b'pretxncommit',
3258 b'pretxncommit',
3251 throw=True,
3259 throw=True,
3252 node=hex(n),
3260 node=hex(n),
3253 parent1=xp1,
3261 parent1=xp1,
3254 parent2=xp2,
3262 parent2=xp2,
3255 )
3263 )
3256 # set the new commit is proper phase
3264 # set the new commit is proper phase
3257 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
3265 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
3258 if targetphase:
3266 if targetphase:
3259 # retract boundary do not alter parent changeset.
3267 # retract boundary do not alter parent changeset.
3260 # if a parent have higher the resulting phase will
3268 # if a parent have higher the resulting phase will
3261 # be compliant anyway
3269 # be compliant anyway
3262 #
3270 #
3263 # if minimal phase was 0 we don't need to retract anything
3271 # if minimal phase was 0 we don't need to retract anything
3264 phases.registernew(self, tr, targetphase, [n])
3272 phases.registernew(self, tr, targetphase, [n])
3265 return n
3273 return n
3266
3274
3267 @unfilteredmethod
3275 @unfilteredmethod
3268 def destroying(self):
3276 def destroying(self):
3269 '''Inform the repository that nodes are about to be destroyed.
3277 '''Inform the repository that nodes are about to be destroyed.
3270 Intended for use by strip and rollback, so there's a common
3278 Intended for use by strip and rollback, so there's a common
3271 place for anything that has to be done before destroying history.
3279 place for anything that has to be done before destroying history.
3272
3280
3273 This is mostly useful for saving state that is in memory and waiting
3281 This is mostly useful for saving state that is in memory and waiting
3274 to be flushed when the current lock is released. Because a call to
3282 to be flushed when the current lock is released. Because a call to
3275 destroyed is imminent, the repo will be invalidated causing those
3283 destroyed is imminent, the repo will be invalidated causing those
3276 changes to stay in memory (waiting for the next unlock), or vanish
3284 changes to stay in memory (waiting for the next unlock), or vanish
3277 completely.
3285 completely.
3278 '''
3286 '''
3279 # When using the same lock to commit and strip, the phasecache is left
3287 # When using the same lock to commit and strip, the phasecache is left
3280 # dirty after committing. Then when we strip, the repo is invalidated,
3288 # dirty after committing. Then when we strip, the repo is invalidated,
3281 # causing those changes to disappear.
3289 # causing those changes to disappear.
3282 if '_phasecache' in vars(self):
3290 if '_phasecache' in vars(self):
3283 self._phasecache.write()
3291 self._phasecache.write()
3284
3292
3285 @unfilteredmethod
3293 @unfilteredmethod
3286 def destroyed(self):
3294 def destroyed(self):
3287 '''Inform the repository that nodes have been destroyed.
3295 '''Inform the repository that nodes have been destroyed.
3288 Intended for use by strip and rollback, so there's a common
3296 Intended for use by strip and rollback, so there's a common
3289 place for anything that has to be done after destroying history.
3297 place for anything that has to be done after destroying history.
3290 '''
3298 '''
3291 # When one tries to:
3299 # When one tries to:
3292 # 1) destroy nodes thus calling this method (e.g. strip)
3300 # 1) destroy nodes thus calling this method (e.g. strip)
3293 # 2) use phasecache somewhere (e.g. commit)
3301 # 2) use phasecache somewhere (e.g. commit)
3294 #
3302 #
3295 # then 2) will fail because the phasecache contains nodes that were
3303 # then 2) will fail because the phasecache contains nodes that were
3296 # removed. We can either remove phasecache from the filecache,
3304 # removed. We can either remove phasecache from the filecache,
3297 # causing it to reload next time it is accessed, or simply filter
3305 # causing it to reload next time it is accessed, or simply filter
3298 # the removed nodes now and write the updated cache.
3306 # the removed nodes now and write the updated cache.
3299 self._phasecache.filterunknown(self)
3307 self._phasecache.filterunknown(self)
3300 self._phasecache.write()
3308 self._phasecache.write()
3301
3309
3302 # refresh all repository caches
3310 # refresh all repository caches
3303 self.updatecaches()
3311 self.updatecaches()
3304
3312
3305 # Ensure the persistent tag cache is updated. Doing it now
3313 # Ensure the persistent tag cache is updated. Doing it now
3306 # means that the tag cache only has to worry about destroyed
3314 # means that the tag cache only has to worry about destroyed
3307 # heads immediately after a strip/rollback. That in turn
3315 # heads immediately after a strip/rollback. That in turn
3308 # guarantees that "cachetip == currenttip" (comparing both rev
3316 # guarantees that "cachetip == currenttip" (comparing both rev
3309 # and node) always means no nodes have been added or destroyed.
3317 # and node) always means no nodes have been added or destroyed.
3310
3318
3311 # XXX this is suboptimal when qrefresh'ing: we strip the current
3319 # XXX this is suboptimal when qrefresh'ing: we strip the current
3312 # head, refresh the tag cache, then immediately add a new head.
3320 # head, refresh the tag cache, then immediately add a new head.
3313 # But I think doing it this way is necessary for the "instant
3321 # But I think doing it this way is necessary for the "instant
3314 # tag cache retrieval" case to work.
3322 # tag cache retrieval" case to work.
3315 self.invalidate()
3323 self.invalidate()
3316
3324
3317 def status(
3325 def status(
3318 self,
3326 self,
3319 node1=b'.',
3327 node1=b'.',
3320 node2=None,
3328 node2=None,
3321 match=None,
3329 match=None,
3322 ignored=False,
3330 ignored=False,
3323 clean=False,
3331 clean=False,
3324 unknown=False,
3332 unknown=False,
3325 listsubrepos=False,
3333 listsubrepos=False,
3326 ):
3334 ):
3327 '''a convenience method that calls node1.status(node2)'''
3335 '''a convenience method that calls node1.status(node2)'''
3328 return self[node1].status(
3336 return self[node1].status(
3329 node2, match, ignored, clean, unknown, listsubrepos
3337 node2, match, ignored, clean, unknown, listsubrepos
3330 )
3338 )
3331
3339
3332 def addpostdsstatus(self, ps):
3340 def addpostdsstatus(self, ps):
3333 """Add a callback to run within the wlock, at the point at which status
3341 """Add a callback to run within the wlock, at the point at which status
3334 fixups happen.
3342 fixups happen.
3335
3343
3336 On status completion, callback(wctx, status) will be called with the
3344 On status completion, callback(wctx, status) will be called with the
3337 wlock held, unless the dirstate has changed from underneath or the wlock
3345 wlock held, unless the dirstate has changed from underneath or the wlock
3338 couldn't be grabbed.
3346 couldn't be grabbed.
3339
3347
3340 Callbacks should not capture and use a cached copy of the dirstate --
3348 Callbacks should not capture and use a cached copy of the dirstate --
3341 it might change in the meanwhile. Instead, they should access the
3349 it might change in the meanwhile. Instead, they should access the
3342 dirstate via wctx.repo().dirstate.
3350 dirstate via wctx.repo().dirstate.
3343
3351
3344 This list is emptied out after each status run -- extensions should
3352 This list is emptied out after each status run -- extensions should
3345 make sure it adds to this list each time dirstate.status is called.
3353 make sure it adds to this list each time dirstate.status is called.
3346 Extensions should also make sure they don't call this for statuses
3354 Extensions should also make sure they don't call this for statuses
3347 that don't involve the dirstate.
3355 that don't involve the dirstate.
3348 """
3356 """
3349
3357
3350 # The list is located here for uniqueness reasons -- it is actually
3358 # The list is located here for uniqueness reasons -- it is actually
3351 # managed by the workingctx, but that isn't unique per-repo.
3359 # managed by the workingctx, but that isn't unique per-repo.
3352 self._postdsstatus.append(ps)
3360 self._postdsstatus.append(ps)
3353
3361
3354 def postdsstatus(self):
3362 def postdsstatus(self):
3355 """Used by workingctx to get the list of post-dirstate-status hooks."""
3363 """Used by workingctx to get the list of post-dirstate-status hooks."""
3356 return self._postdsstatus
3364 return self._postdsstatus
3357
3365
3358 def clearpostdsstatus(self):
3366 def clearpostdsstatus(self):
3359 """Used by workingctx to clear post-dirstate-status hooks."""
3367 """Used by workingctx to clear post-dirstate-status hooks."""
3360 del self._postdsstatus[:]
3368 del self._postdsstatus[:]
3361
3369
3362 def heads(self, start=None):
3370 def heads(self, start=None):
3363 if start is None:
3371 if start is None:
3364 cl = self.changelog
3372 cl = self.changelog
3365 headrevs = reversed(cl.headrevs())
3373 headrevs = reversed(cl.headrevs())
3366 return [cl.node(rev) for rev in headrevs]
3374 return [cl.node(rev) for rev in headrevs]
3367
3375
3368 heads = self.changelog.heads(start)
3376 heads = self.changelog.heads(start)
3369 # sort the output in rev descending order
3377 # sort the output in rev descending order
3370 return sorted(heads, key=self.changelog.rev, reverse=True)
3378 return sorted(heads, key=self.changelog.rev, reverse=True)
3371
3379
3372 def branchheads(self, branch=None, start=None, closed=False):
3380 def branchheads(self, branch=None, start=None, closed=False):
3373 '''return a (possibly filtered) list of heads for the given branch
3381 '''return a (possibly filtered) list of heads for the given branch
3374
3382
3375 Heads are returned in topological order, from newest to oldest.
3383 Heads are returned in topological order, from newest to oldest.
3376 If branch is None, use the dirstate branch.
3384 If branch is None, use the dirstate branch.
3377 If start is not None, return only heads reachable from start.
3385 If start is not None, return only heads reachable from start.
3378 If closed is True, return heads that are marked as closed as well.
3386 If closed is True, return heads that are marked as closed as well.
3379 '''
3387 '''
3380 if branch is None:
3388 if branch is None:
3381 branch = self[None].branch()
3389 branch = self[None].branch()
3382 branches = self.branchmap()
3390 branches = self.branchmap()
3383 if not branches.hasbranch(branch):
3391 if not branches.hasbranch(branch):
3384 return []
3392 return []
3385 # the cache returns heads ordered lowest to highest
3393 # the cache returns heads ordered lowest to highest
3386 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3394 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3387 if start is not None:
3395 if start is not None:
3388 # filter out the heads that cannot be reached from startrev
3396 # filter out the heads that cannot be reached from startrev
3389 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3397 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3390 bheads = [h for h in bheads if h in fbheads]
3398 bheads = [h for h in bheads if h in fbheads]
3391 return bheads
3399 return bheads
3392
3400
3393 def branches(self, nodes):
3401 def branches(self, nodes):
3394 if not nodes:
3402 if not nodes:
3395 nodes = [self.changelog.tip()]
3403 nodes = [self.changelog.tip()]
3396 b = []
3404 b = []
3397 for n in nodes:
3405 for n in nodes:
3398 t = n
3406 t = n
3399 while True:
3407 while True:
3400 p = self.changelog.parents(n)
3408 p = self.changelog.parents(n)
3401 if p[1] != nullid or p[0] == nullid:
3409 if p[1] != nullid or p[0] == nullid:
3402 b.append((t, n, p[0], p[1]))
3410 b.append((t, n, p[0], p[1]))
3403 break
3411 break
3404 n = p[0]
3412 n = p[0]
3405 return b
3413 return b
3406
3414
3407 def between(self, pairs):
3415 def between(self, pairs):
3408 r = []
3416 r = []
3409
3417
3410 for top, bottom in pairs:
3418 for top, bottom in pairs:
3411 n, l, i = top, [], 0
3419 n, l, i = top, [], 0
3412 f = 1
3420 f = 1
3413
3421
3414 while n != bottom and n != nullid:
3422 while n != bottom and n != nullid:
3415 p = self.changelog.parents(n)[0]
3423 p = self.changelog.parents(n)[0]
3416 if i == f:
3424 if i == f:
3417 l.append(n)
3425 l.append(n)
3418 f = f * 2
3426 f = f * 2
3419 n = p
3427 n = p
3420 i += 1
3428 i += 1
3421
3429
3422 r.append(l)
3430 r.append(l)
3423
3431
3424 return r
3432 return r
3425
3433
3426 def checkpush(self, pushop):
3434 def checkpush(self, pushop):
3427 """Extensions can override this function if additional checks have
3435 """Extensions can override this function if additional checks have
3428 to be performed before pushing, or call it if they override push
3436 to be performed before pushing, or call it if they override push
3429 command.
3437 command.
3430 """
3438 """
3431
3439
3432 @unfilteredpropertycache
3440 @unfilteredpropertycache
3433 def prepushoutgoinghooks(self):
3441 def prepushoutgoinghooks(self):
3434 """Return util.hooks consists of a pushop with repo, remote, outgoing
3442 """Return util.hooks consists of a pushop with repo, remote, outgoing
3435 methods, which are called before pushing changesets.
3443 methods, which are called before pushing changesets.
3436 """
3444 """
3437 return util.hooks()
3445 return util.hooks()
3438
3446
3439 def pushkey(self, namespace, key, old, new):
3447 def pushkey(self, namespace, key, old, new):
3440 try:
3448 try:
3441 tr = self.currenttransaction()
3449 tr = self.currenttransaction()
3442 hookargs = {}
3450 hookargs = {}
3443 if tr is not None:
3451 if tr is not None:
3444 hookargs.update(tr.hookargs)
3452 hookargs.update(tr.hookargs)
3445 hookargs = pycompat.strkwargs(hookargs)
3453 hookargs = pycompat.strkwargs(hookargs)
3446 hookargs['namespace'] = namespace
3454 hookargs['namespace'] = namespace
3447 hookargs['key'] = key
3455 hookargs['key'] = key
3448 hookargs['old'] = old
3456 hookargs['old'] = old
3449 hookargs['new'] = new
3457 hookargs['new'] = new
3450 self.hook(b'prepushkey', throw=True, **hookargs)
3458 self.hook(b'prepushkey', throw=True, **hookargs)
3451 except error.HookAbort as exc:
3459 except error.HookAbort as exc:
3452 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3460 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3453 if exc.hint:
3461 if exc.hint:
3454 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3462 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3455 return False
3463 return False
3456 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3464 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3457 ret = pushkey.push(self, namespace, key, old, new)
3465 ret = pushkey.push(self, namespace, key, old, new)
3458
3466
3459 def runhook(unused_success):
3467 def runhook(unused_success):
3460 self.hook(
3468 self.hook(
3461 b'pushkey',
3469 b'pushkey',
3462 namespace=namespace,
3470 namespace=namespace,
3463 key=key,
3471 key=key,
3464 old=old,
3472 old=old,
3465 new=new,
3473 new=new,
3466 ret=ret,
3474 ret=ret,
3467 )
3475 )
3468
3476
3469 self._afterlock(runhook)
3477 self._afterlock(runhook)
3470 return ret
3478 return ret
3471
3479
3472 def listkeys(self, namespace):
3480 def listkeys(self, namespace):
3473 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3481 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3474 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3482 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3475 values = pushkey.list(self, namespace)
3483 values = pushkey.list(self, namespace)
3476 self.hook(b'listkeys', namespace=namespace, values=values)
3484 self.hook(b'listkeys', namespace=namespace, values=values)
3477 return values
3485 return values
3478
3486
3479 def debugwireargs(self, one, two, three=None, four=None, five=None):
3487 def debugwireargs(self, one, two, three=None, four=None, five=None):
3480 '''used to test argument passing over the wire'''
3488 '''used to test argument passing over the wire'''
3481 return b"%s %s %s %s %s" % (
3489 return b"%s %s %s %s %s" % (
3482 one,
3490 one,
3483 two,
3491 two,
3484 pycompat.bytestr(three),
3492 pycompat.bytestr(three),
3485 pycompat.bytestr(four),
3493 pycompat.bytestr(four),
3486 pycompat.bytestr(five),
3494 pycompat.bytestr(five),
3487 )
3495 )
3488
3496
3489 def savecommitmessage(self, text):
3497 def savecommitmessage(self, text):
3490 fp = self.vfs(b'last-message.txt', b'wb')
3498 fp = self.vfs(b'last-message.txt', b'wb')
3491 try:
3499 try:
3492 fp.write(text)
3500 fp.write(text)
3493 finally:
3501 finally:
3494 fp.close()
3502 fp.close()
3495 return self.pathto(fp.name[len(self.root) + 1 :])
3503 return self.pathto(fp.name[len(self.root) + 1 :])
3496
3504
3497
3505
3498 # used to avoid circular references so destructors work
3506 # used to avoid circular references so destructors work
3499 def aftertrans(files):
3507 def aftertrans(files):
3500 renamefiles = [tuple(t) for t in files]
3508 renamefiles = [tuple(t) for t in files]
3501
3509
3502 def a():
3510 def a():
3503 for vfs, src, dest in renamefiles:
3511 for vfs, src, dest in renamefiles:
3504 # if src and dest refer to a same file, vfs.rename is a no-op,
3512 # if src and dest refer to a same file, vfs.rename is a no-op,
3505 # leaving both src and dest on disk. delete dest to make sure
3513 # leaving both src and dest on disk. delete dest to make sure
3506 # the rename couldn't be such a no-op.
3514 # the rename couldn't be such a no-op.
3507 vfs.tryunlink(dest)
3515 vfs.tryunlink(dest)
3508 try:
3516 try:
3509 vfs.rename(src, dest)
3517 vfs.rename(src, dest)
3510 except OSError: # journal file does not yet exist
3518 except OSError: # journal file does not yet exist
3511 pass
3519 pass
3512
3520
3513 return a
3521 return a
3514
3522
3515
3523
3516 def undoname(fn):
3524 def undoname(fn):
3517 base, name = os.path.split(fn)
3525 base, name = os.path.split(fn)
3518 assert name.startswith(b'journal')
3526 assert name.startswith(b'journal')
3519 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3527 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3520
3528
3521
3529
3522 def instance(ui, path, create, intents=None, createopts=None):
3530 def instance(ui, path, create, intents=None, createopts=None):
3523 localpath = util.urllocalpath(path)
3531 localpath = util.urllocalpath(path)
3524 if create:
3532 if create:
3525 createrepository(ui, localpath, createopts=createopts)
3533 createrepository(ui, localpath, createopts=createopts)
3526
3534
3527 return makelocalrepository(ui, localpath, intents=intents)
3535 return makelocalrepository(ui, localpath, intents=intents)
3528
3536
3529
3537
3530 def islocal(path):
3538 def islocal(path):
3531 return True
3539 return True
3532
3540
3533
3541
3534 def defaultcreateopts(ui, createopts=None):
3542 def defaultcreateopts(ui, createopts=None):
3535 """Populate the default creation options for a repository.
3543 """Populate the default creation options for a repository.
3536
3544
3537 A dictionary of explicitly requested creation options can be passed
3545 A dictionary of explicitly requested creation options can be passed
3538 in. Missing keys will be populated.
3546 in. Missing keys will be populated.
3539 """
3547 """
3540 createopts = dict(createopts or {})
3548 createopts = dict(createopts or {})
3541
3549
3542 if b'backend' not in createopts:
3550 if b'backend' not in createopts:
3543 # experimental config: storage.new-repo-backend
3551 # experimental config: storage.new-repo-backend
3544 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3552 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3545
3553
3546 return createopts
3554 return createopts
3547
3555
3548
3556
3549 def newreporequirements(ui, createopts):
3557 def newreporequirements(ui, createopts):
3550 """Determine the set of requirements for a new local repository.
3558 """Determine the set of requirements for a new local repository.
3551
3559
3552 Extensions can wrap this function to specify custom requirements for
3560 Extensions can wrap this function to specify custom requirements for
3553 new repositories.
3561 new repositories.
3554 """
3562 """
3555 # If the repo is being created from a shared repository, we copy
3563 # If the repo is being created from a shared repository, we copy
3556 # its requirements.
3564 # its requirements.
3557 if b'sharedrepo' in createopts:
3565 if b'sharedrepo' in createopts:
3558 requirements = set(createopts[b'sharedrepo'].requirements)
3566 requirements = set(createopts[b'sharedrepo'].requirements)
3559 if createopts.get(b'sharedrelative'):
3567 if createopts.get(b'sharedrelative'):
3560 requirements.add(b'relshared')
3568 requirements.add(b'relshared')
3561 else:
3569 else:
3562 requirements.add(b'shared')
3570 requirements.add(b'shared')
3563
3571
3564 return requirements
3572 return requirements
3565
3573
3566 if b'backend' not in createopts:
3574 if b'backend' not in createopts:
3567 raise error.ProgrammingError(
3575 raise error.ProgrammingError(
3568 b'backend key not present in createopts; '
3576 b'backend key not present in createopts; '
3569 b'was defaultcreateopts() called?'
3577 b'was defaultcreateopts() called?'
3570 )
3578 )
3571
3579
3572 if createopts[b'backend'] != b'revlogv1':
3580 if createopts[b'backend'] != b'revlogv1':
3573 raise error.Abort(
3581 raise error.Abort(
3574 _(
3582 _(
3575 b'unable to determine repository requirements for '
3583 b'unable to determine repository requirements for '
3576 b'storage backend: %s'
3584 b'storage backend: %s'
3577 )
3585 )
3578 % createopts[b'backend']
3586 % createopts[b'backend']
3579 )
3587 )
3580
3588
3581 requirements = {b'revlogv1'}
3589 requirements = {b'revlogv1'}
3582 if ui.configbool(b'format', b'usestore'):
3590 if ui.configbool(b'format', b'usestore'):
3583 requirements.add(b'store')
3591 requirements.add(b'store')
3584 if ui.configbool(b'format', b'usefncache'):
3592 if ui.configbool(b'format', b'usefncache'):
3585 requirements.add(b'fncache')
3593 requirements.add(b'fncache')
3586 if ui.configbool(b'format', b'dotencode'):
3594 if ui.configbool(b'format', b'dotencode'):
3587 requirements.add(b'dotencode')
3595 requirements.add(b'dotencode')
3588
3596
3589 compengines = ui.configlist(b'format', b'revlog-compression')
3597 compengines = ui.configlist(b'format', b'revlog-compression')
3590 for compengine in compengines:
3598 for compengine in compengines:
3591 if compengine in util.compengines:
3599 if compengine in util.compengines:
3592 break
3600 break
3593 else:
3601 else:
3594 raise error.Abort(
3602 raise error.Abort(
3595 _(
3603 _(
3596 b'compression engines %s defined by '
3604 b'compression engines %s defined by '
3597 b'format.revlog-compression not available'
3605 b'format.revlog-compression not available'
3598 )
3606 )
3599 % b', '.join(b'"%s"' % e for e in compengines),
3607 % b', '.join(b'"%s"' % e for e in compengines),
3600 hint=_(
3608 hint=_(
3601 b'run "hg debuginstall" to list available '
3609 b'run "hg debuginstall" to list available '
3602 b'compression engines'
3610 b'compression engines'
3603 ),
3611 ),
3604 )
3612 )
3605
3613
3606 # zlib is the historical default and doesn't need an explicit requirement.
3614 # zlib is the historical default and doesn't need an explicit requirement.
3607 if compengine == b'zstd':
3615 if compengine == b'zstd':
3608 requirements.add(b'revlog-compression-zstd')
3616 requirements.add(b'revlog-compression-zstd')
3609 elif compengine != b'zlib':
3617 elif compengine != b'zlib':
3610 requirements.add(b'exp-compression-%s' % compengine)
3618 requirements.add(b'exp-compression-%s' % compengine)
3611
3619
3612 if scmutil.gdinitconfig(ui):
3620 if scmutil.gdinitconfig(ui):
3613 requirements.add(b'generaldelta')
3621 requirements.add(b'generaldelta')
3614 if ui.configbool(b'format', b'sparse-revlog'):
3622 if ui.configbool(b'format', b'sparse-revlog'):
3615 requirements.add(SPARSEREVLOG_REQUIREMENT)
3623 requirements.add(SPARSEREVLOG_REQUIREMENT)
3616
3624
3617 # experimental config: format.exp-use-side-data
3625 # experimental config: format.exp-use-side-data
3618 if ui.configbool(b'format', b'exp-use-side-data'):
3626 if ui.configbool(b'format', b'exp-use-side-data'):
3619 requirements.add(SIDEDATA_REQUIREMENT)
3627 requirements.add(SIDEDATA_REQUIREMENT)
3620 # experimental config: format.exp-use-copies-side-data-changeset
3628 # experimental config: format.exp-use-copies-side-data-changeset
3621 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3629 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3622 requirements.add(SIDEDATA_REQUIREMENT)
3630 requirements.add(SIDEDATA_REQUIREMENT)
3623 requirements.add(COPIESSDC_REQUIREMENT)
3631 requirements.add(COPIESSDC_REQUIREMENT)
3624 if ui.configbool(b'experimental', b'treemanifest'):
3632 if ui.configbool(b'experimental', b'treemanifest'):
3625 requirements.add(b'treemanifest')
3633 requirements.add(b'treemanifest')
3626
3634
3627 revlogv2 = ui.config(b'experimental', b'revlogv2')
3635 revlogv2 = ui.config(b'experimental', b'revlogv2')
3628 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3636 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3629 requirements.remove(b'revlogv1')
3637 requirements.remove(b'revlogv1')
3630 # generaldelta is implied by revlogv2.
3638 # generaldelta is implied by revlogv2.
3631 requirements.discard(b'generaldelta')
3639 requirements.discard(b'generaldelta')
3632 requirements.add(REVLOGV2_REQUIREMENT)
3640 requirements.add(REVLOGV2_REQUIREMENT)
3633 # experimental config: format.internal-phase
3641 # experimental config: format.internal-phase
3634 if ui.configbool(b'format', b'internal-phase'):
3642 if ui.configbool(b'format', b'internal-phase'):
3635 requirements.add(b'internal-phase')
3643 requirements.add(b'internal-phase')
3636
3644
3637 if createopts.get(b'narrowfiles'):
3645 if createopts.get(b'narrowfiles'):
3638 requirements.add(repository.NARROW_REQUIREMENT)
3646 requirements.add(repository.NARROW_REQUIREMENT)
3639
3647
3640 if createopts.get(b'lfs'):
3648 if createopts.get(b'lfs'):
3641 requirements.add(b'lfs')
3649 requirements.add(b'lfs')
3642
3650
3643 if ui.configbool(b'format', b'bookmarks-in-store'):
3651 if ui.configbool(b'format', b'bookmarks-in-store'):
3644 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3652 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3645
3653
3646 return requirements
3654 return requirements
3647
3655
3648
3656
3649 def filterknowncreateopts(ui, createopts):
3657 def filterknowncreateopts(ui, createopts):
3650 """Filters a dict of repo creation options against options that are known.
3658 """Filters a dict of repo creation options against options that are known.
3651
3659
3652 Receives a dict of repo creation options and returns a dict of those
3660 Receives a dict of repo creation options and returns a dict of those
3653 options that we don't know how to handle.
3661 options that we don't know how to handle.
3654
3662
3655 This function is called as part of repository creation. If the
3663 This function is called as part of repository creation. If the
3656 returned dict contains any items, repository creation will not
3664 returned dict contains any items, repository creation will not
3657 be allowed, as it means there was a request to create a repository
3665 be allowed, as it means there was a request to create a repository
3658 with options not recognized by loaded code.
3666 with options not recognized by loaded code.
3659
3667
3660 Extensions can wrap this function to filter out creation options
3668 Extensions can wrap this function to filter out creation options
3661 they know how to handle.
3669 they know how to handle.
3662 """
3670 """
3663 known = {
3671 known = {
3664 b'backend',
3672 b'backend',
3665 b'lfs',
3673 b'lfs',
3666 b'narrowfiles',
3674 b'narrowfiles',
3667 b'sharedrepo',
3675 b'sharedrepo',
3668 b'sharedrelative',
3676 b'sharedrelative',
3669 b'shareditems',
3677 b'shareditems',
3670 b'shallowfilestore',
3678 b'shallowfilestore',
3671 }
3679 }
3672
3680
3673 return {k: v for k, v in createopts.items() if k not in known}
3681 return {k: v for k, v in createopts.items() if k not in known}
3674
3682
3675
3683
3676 def createrepository(ui, path, createopts=None):
3684 def createrepository(ui, path, createopts=None):
3677 """Create a new repository in a vfs.
3685 """Create a new repository in a vfs.
3678
3686
3679 ``path`` path to the new repo's working directory.
3687 ``path`` path to the new repo's working directory.
3680 ``createopts`` options for the new repository.
3688 ``createopts`` options for the new repository.
3681
3689
3682 The following keys for ``createopts`` are recognized:
3690 The following keys for ``createopts`` are recognized:
3683
3691
3684 backend
3692 backend
3685 The storage backend to use.
3693 The storage backend to use.
3686 lfs
3694 lfs
3687 Repository will be created with ``lfs`` requirement. The lfs extension
3695 Repository will be created with ``lfs`` requirement. The lfs extension
3688 will automatically be loaded when the repository is accessed.
3696 will automatically be loaded when the repository is accessed.
3689 narrowfiles
3697 narrowfiles
3690 Set up repository to support narrow file storage.
3698 Set up repository to support narrow file storage.
3691 sharedrepo
3699 sharedrepo
3692 Repository object from which storage should be shared.
3700 Repository object from which storage should be shared.
3693 sharedrelative
3701 sharedrelative
3694 Boolean indicating if the path to the shared repo should be
3702 Boolean indicating if the path to the shared repo should be
3695 stored as relative. By default, the pointer to the "parent" repo
3703 stored as relative. By default, the pointer to the "parent" repo
3696 is stored as an absolute path.
3704 is stored as an absolute path.
3697 shareditems
3705 shareditems
3698 Set of items to share to the new repository (in addition to storage).
3706 Set of items to share to the new repository (in addition to storage).
3699 shallowfilestore
3707 shallowfilestore
3700 Indicates that storage for files should be shallow (not all ancestor
3708 Indicates that storage for files should be shallow (not all ancestor
3701 revisions are known).
3709 revisions are known).
3702 """
3710 """
3703 createopts = defaultcreateopts(ui, createopts=createopts)
3711 createopts = defaultcreateopts(ui, createopts=createopts)
3704
3712
3705 unknownopts = filterknowncreateopts(ui, createopts)
3713 unknownopts = filterknowncreateopts(ui, createopts)
3706
3714
3707 if not isinstance(unknownopts, dict):
3715 if not isinstance(unknownopts, dict):
3708 raise error.ProgrammingError(
3716 raise error.ProgrammingError(
3709 b'filterknowncreateopts() did not return a dict'
3717 b'filterknowncreateopts() did not return a dict'
3710 )
3718 )
3711
3719
3712 if unknownopts:
3720 if unknownopts:
3713 raise error.Abort(
3721 raise error.Abort(
3714 _(
3722 _(
3715 b'unable to create repository because of unknown '
3723 b'unable to create repository because of unknown '
3716 b'creation option: %s'
3724 b'creation option: %s'
3717 )
3725 )
3718 % b', '.join(sorted(unknownopts)),
3726 % b', '.join(sorted(unknownopts)),
3719 hint=_(b'is a required extension not loaded?'),
3727 hint=_(b'is a required extension not loaded?'),
3720 )
3728 )
3721
3729
3722 requirements = newreporequirements(ui, createopts=createopts)
3730 requirements = newreporequirements(ui, createopts=createopts)
3723
3731
3724 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3732 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3725
3733
3726 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3734 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3727 if hgvfs.exists():
3735 if hgvfs.exists():
3728 raise error.RepoError(_(b'repository %s already exists') % path)
3736 raise error.RepoError(_(b'repository %s already exists') % path)
3729
3737
3730 if b'sharedrepo' in createopts:
3738 if b'sharedrepo' in createopts:
3731 sharedpath = createopts[b'sharedrepo'].sharedpath
3739 sharedpath = createopts[b'sharedrepo'].sharedpath
3732
3740
3733 if createopts.get(b'sharedrelative'):
3741 if createopts.get(b'sharedrelative'):
3734 try:
3742 try:
3735 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3743 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3736 except (IOError, ValueError) as e:
3744 except (IOError, ValueError) as e:
3737 # ValueError is raised on Windows if the drive letters differ
3745 # ValueError is raised on Windows if the drive letters differ
3738 # on each path.
3746 # on each path.
3739 raise error.Abort(
3747 raise error.Abort(
3740 _(b'cannot calculate relative path'),
3748 _(b'cannot calculate relative path'),
3741 hint=stringutil.forcebytestr(e),
3749 hint=stringutil.forcebytestr(e),
3742 )
3750 )
3743
3751
3744 if not wdirvfs.exists():
3752 if not wdirvfs.exists():
3745 wdirvfs.makedirs()
3753 wdirvfs.makedirs()
3746
3754
3747 hgvfs.makedir(notindexed=True)
3755 hgvfs.makedir(notindexed=True)
3748 if b'sharedrepo' not in createopts:
3756 if b'sharedrepo' not in createopts:
3749 hgvfs.mkdir(b'cache')
3757 hgvfs.mkdir(b'cache')
3750 hgvfs.mkdir(b'wcache')
3758 hgvfs.mkdir(b'wcache')
3751
3759
3752 if b'store' in requirements and b'sharedrepo' not in createopts:
3760 if b'store' in requirements and b'sharedrepo' not in createopts:
3753 hgvfs.mkdir(b'store')
3761 hgvfs.mkdir(b'store')
3754
3762
3755 # We create an invalid changelog outside the store so very old
3763 # We create an invalid changelog outside the store so very old
3756 # Mercurial versions (which didn't know about the requirements
3764 # Mercurial versions (which didn't know about the requirements
3757 # file) encounter an error on reading the changelog. This
3765 # file) encounter an error on reading the changelog. This
3758 # effectively locks out old clients and prevents them from
3766 # effectively locks out old clients and prevents them from
3759 # mucking with a repo in an unknown format.
3767 # mucking with a repo in an unknown format.
3760 #
3768 #
3761 # The revlog header has version 2, which won't be recognized by
3769 # The revlog header has version 2, which won't be recognized by
3762 # such old clients.
3770 # such old clients.
3763 hgvfs.append(
3771 hgvfs.append(
3764 b'00changelog.i',
3772 b'00changelog.i',
3765 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3773 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3766 b'layout',
3774 b'layout',
3767 )
3775 )
3768
3776
3769 scmutil.writerequires(hgvfs, requirements)
3777 scmutil.writerequires(hgvfs, requirements)
3770
3778
3771 # Write out file telling readers where to find the shared store.
3779 # Write out file telling readers where to find the shared store.
3772 if b'sharedrepo' in createopts:
3780 if b'sharedrepo' in createopts:
3773 hgvfs.write(b'sharedpath', sharedpath)
3781 hgvfs.write(b'sharedpath', sharedpath)
3774
3782
3775 if createopts.get(b'shareditems'):
3783 if createopts.get(b'shareditems'):
3776 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3784 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3777 hgvfs.write(b'shared', shared)
3785 hgvfs.write(b'shared', shared)
3778
3786
3779
3787
3780 def poisonrepository(repo):
3788 def poisonrepository(repo):
3781 """Poison a repository instance so it can no longer be used."""
3789 """Poison a repository instance so it can no longer be used."""
3782 # Perform any cleanup on the instance.
3790 # Perform any cleanup on the instance.
3783 repo.close()
3791 repo.close()
3784
3792
3785 # Our strategy is to replace the type of the object with one that
3793 # Our strategy is to replace the type of the object with one that
3786 # has all attribute lookups result in error.
3794 # has all attribute lookups result in error.
3787 #
3795 #
3788 # But we have to allow the close() method because some constructors
3796 # But we have to allow the close() method because some constructors
3789 # of repos call close() on repo references.
3797 # of repos call close() on repo references.
3790 class poisonedrepository(object):
3798 class poisonedrepository(object):
3791 def __getattribute__(self, item):
3799 def __getattribute__(self, item):
3792 if item == 'close':
3800 if item == 'close':
3793 return object.__getattribute__(self, item)
3801 return object.__getattribute__(self, item)
3794
3802
3795 raise error.ProgrammingError(
3803 raise error.ProgrammingError(
3796 b'repo instances should not be used after unshare'
3804 b'repo instances should not be used after unshare'
3797 )
3805 )
3798
3806
3799 def close(self):
3807 def close(self):
3800 pass
3808 pass
3801
3809
3802 # We may have a repoview, which intercepts __setattr__. So be sure
3810 # We may have a repoview, which intercepts __setattr__. So be sure
3803 # we operate at the lowest level possible.
3811 # we operate at the lowest level possible.
3804 object.__setattr__(repo, '__class__', poisonedrepository)
3812 object.__setattr__(repo, '__class__', poisonedrepository)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now