##// END OF EJS Templates
hidden: add support to explicitly access hidden changesets with SSH peers...
Manuel Jacob -
r51316:45c7bada default
parent child Browse files
Show More
@@ -1,8038 +1,8057 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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
8
9 import os
9 import os
10 import re
10 import re
11 import sys
11 import sys
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 nullid,
16 nullid,
17 nullrev,
17 nullrev,
18 short,
18 short,
19 wdirrev,
19 wdirrev,
20 )
20 )
21 from .pycompat import open
21 from .pycompat import open
22 from . import (
22 from . import (
23 archival,
23 archival,
24 bookmarks,
24 bookmarks,
25 bundle2,
25 bundle2,
26 bundlecaches,
26 bundlecaches,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 discovery,
32 discovery,
33 encoding,
33 encoding,
34 error,
34 error,
35 exchange,
35 exchange,
36 extensions,
36 extensions,
37 filemerge,
37 filemerge,
38 formatter,
38 formatter,
39 graphmod,
39 graphmod,
40 grep as grepmod,
40 grep as grepmod,
41 hbisect,
41 hbisect,
42 help,
42 help,
43 hg,
43 hg,
44 logcmdutil,
44 logcmdutil,
45 merge as mergemod,
45 merge as mergemod,
46 mergestate as mergestatemod,
46 mergestate as mergestatemod,
47 narrowspec,
47 narrowspec,
48 obsolete,
48 obsolete,
49 obsutil,
49 obsutil,
50 patch,
50 patch,
51 phases,
51 phases,
52 pycompat,
52 pycompat,
53 rcutil,
53 rcutil,
54 registrar,
54 registrar,
55 requirements,
55 requirements,
56 revsetlang,
56 revsetlang,
57 rewriteutil,
57 rewriteutil,
58 scmutil,
58 scmutil,
59 server,
59 server,
60 shelve as shelvemod,
60 shelve as shelvemod,
61 state as statemod,
61 state as statemod,
62 streamclone,
62 streamclone,
63 tags as tagsmod,
63 tags as tagsmod,
64 ui as uimod,
64 ui as uimod,
65 util,
65 util,
66 verify as verifymod,
66 verify as verifymod,
67 vfs as vfsmod,
67 vfs as vfsmod,
68 wireprotoserver,
68 wireprotoserver,
69 )
69 )
70 from .utils import (
70 from .utils import (
71 dateutil,
71 dateutil,
72 procutil,
72 stringutil,
73 stringutil,
73 urlutil,
74 urlutil,
74 )
75 )
75
76
76 table = {}
77 table = {}
77 table.update(debugcommandsmod.command._table)
78 table.update(debugcommandsmod.command._table)
78
79
79 command = registrar.command(table)
80 command = registrar.command(table)
80 INTENT_READONLY = registrar.INTENT_READONLY
81 INTENT_READONLY = registrar.INTENT_READONLY
81
82
82 # common command options
83 # common command options
83
84
84 globalopts = [
85 globalopts = [
85 (
86 (
86 b'R',
87 b'R',
87 b'repository',
88 b'repository',
88 b'',
89 b'',
89 _(b'repository root directory or name of overlay bundle file'),
90 _(b'repository root directory or name of overlay bundle file'),
90 _(b'REPO'),
91 _(b'REPO'),
91 ),
92 ),
92 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
93 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
93 (
94 (
94 b'y',
95 b'y',
95 b'noninteractive',
96 b'noninteractive',
96 None,
97 None,
97 _(
98 _(
98 b'do not prompt, automatically pick the first choice for all prompts'
99 b'do not prompt, automatically pick the first choice for all prompts'
99 ),
100 ),
100 ),
101 ),
101 (b'q', b'quiet', None, _(b'suppress output')),
102 (b'q', b'quiet', None, _(b'suppress output')),
102 (b'v', b'verbose', None, _(b'enable additional output')),
103 (b'v', b'verbose', None, _(b'enable additional output')),
103 (
104 (
104 b'',
105 b'',
105 b'color',
106 b'color',
106 b'',
107 b'',
107 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
108 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
108 # and should not be translated
109 # and should not be translated
109 _(b"when to colorize (boolean, always, auto, never, or debug)"),
110 _(b"when to colorize (boolean, always, auto, never, or debug)"),
110 _(b'TYPE'),
111 _(b'TYPE'),
111 ),
112 ),
112 (
113 (
113 b'',
114 b'',
114 b'config',
115 b'config',
115 [],
116 [],
116 _(b'set/override config option (use \'section.name=value\')'),
117 _(b'set/override config option (use \'section.name=value\')'),
117 _(b'CONFIG'),
118 _(b'CONFIG'),
118 ),
119 ),
119 (b'', b'debug', None, _(b'enable debugging output')),
120 (b'', b'debug', None, _(b'enable debugging output')),
120 (b'', b'debugger', None, _(b'start debugger')),
121 (b'', b'debugger', None, _(b'start debugger')),
121 (
122 (
122 b'',
123 b'',
123 b'encoding',
124 b'encoding',
124 encoding.encoding,
125 encoding.encoding,
125 _(b'set the charset encoding'),
126 _(b'set the charset encoding'),
126 _(b'ENCODE'),
127 _(b'ENCODE'),
127 ),
128 ),
128 (
129 (
129 b'',
130 b'',
130 b'encodingmode',
131 b'encodingmode',
131 encoding.encodingmode,
132 encoding.encodingmode,
132 _(b'set the charset encoding mode'),
133 _(b'set the charset encoding mode'),
133 _(b'MODE'),
134 _(b'MODE'),
134 ),
135 ),
135 (b'', b'traceback', None, _(b'always print a traceback on exception')),
136 (b'', b'traceback', None, _(b'always print a traceback on exception')),
136 (b'', b'time', None, _(b'time how long the command takes')),
137 (b'', b'time', None, _(b'time how long the command takes')),
137 (b'', b'profile', None, _(b'print command execution profile')),
138 (b'', b'profile', None, _(b'print command execution profile')),
138 (b'', b'version', None, _(b'output version information and exit')),
139 (b'', b'version', None, _(b'output version information and exit')),
139 (b'h', b'help', None, _(b'display help and exit')),
140 (b'h', b'help', None, _(b'display help and exit')),
140 (b'', b'hidden', False, _(b'consider hidden changesets')),
141 (b'', b'hidden', False, _(b'consider hidden changesets')),
141 (
142 (
142 b'',
143 b'',
143 b'pager',
144 b'pager',
144 b'auto',
145 b'auto',
145 _(b"when to paginate (boolean, always, auto, or never)"),
146 _(b"when to paginate (boolean, always, auto, or never)"),
146 _(b'TYPE'),
147 _(b'TYPE'),
147 ),
148 ),
148 ]
149 ]
149
150
150 dryrunopts = cmdutil.dryrunopts
151 dryrunopts = cmdutil.dryrunopts
151 remoteopts = cmdutil.remoteopts
152 remoteopts = cmdutil.remoteopts
152 walkopts = cmdutil.walkopts
153 walkopts = cmdutil.walkopts
153 commitopts = cmdutil.commitopts
154 commitopts = cmdutil.commitopts
154 commitopts2 = cmdutil.commitopts2
155 commitopts2 = cmdutil.commitopts2
155 commitopts3 = cmdutil.commitopts3
156 commitopts3 = cmdutil.commitopts3
156 formatteropts = cmdutil.formatteropts
157 formatteropts = cmdutil.formatteropts
157 templateopts = cmdutil.templateopts
158 templateopts = cmdutil.templateopts
158 logopts = cmdutil.logopts
159 logopts = cmdutil.logopts
159 diffopts = cmdutil.diffopts
160 diffopts = cmdutil.diffopts
160 diffwsopts = cmdutil.diffwsopts
161 diffwsopts = cmdutil.diffwsopts
161 diffopts2 = cmdutil.diffopts2
162 diffopts2 = cmdutil.diffopts2
162 mergetoolopts = cmdutil.mergetoolopts
163 mergetoolopts = cmdutil.mergetoolopts
163 similarityopts = cmdutil.similarityopts
164 similarityopts = cmdutil.similarityopts
164 subrepoopts = cmdutil.subrepoopts
165 subrepoopts = cmdutil.subrepoopts
165 debugrevlogopts = cmdutil.debugrevlogopts
166 debugrevlogopts = cmdutil.debugrevlogopts
166
167
167 # Commands start here, listed alphabetically
168 # Commands start here, listed alphabetically
168
169
169
170
170 @command(
171 @command(
171 b'abort',
172 b'abort',
172 dryrunopts,
173 dryrunopts,
173 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
174 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
174 helpbasic=True,
175 helpbasic=True,
175 )
176 )
176 def abort(ui, repo, **opts):
177 def abort(ui, repo, **opts):
177 """abort an unfinished operation (EXPERIMENTAL)
178 """abort an unfinished operation (EXPERIMENTAL)
178
179
179 Aborts a multistep operation like graft, histedit, rebase, merge,
180 Aborts a multistep operation like graft, histedit, rebase, merge,
180 and unshelve if they are in an unfinished state.
181 and unshelve if they are in an unfinished state.
181
182
182 use --dry-run/-n to dry run the command.
183 use --dry-run/-n to dry run the command.
183 """
184 """
184 dryrun = opts.get('dry_run')
185 dryrun = opts.get('dry_run')
185 abortstate = cmdutil.getunfinishedstate(repo)
186 abortstate = cmdutil.getunfinishedstate(repo)
186 if not abortstate:
187 if not abortstate:
187 raise error.StateError(_(b'no operation in progress'))
188 raise error.StateError(_(b'no operation in progress'))
188 if not abortstate.abortfunc:
189 if not abortstate.abortfunc:
189 raise error.InputError(
190 raise error.InputError(
190 (
191 (
191 _(b"%s in progress but does not support 'hg abort'")
192 _(b"%s in progress but does not support 'hg abort'")
192 % (abortstate._opname)
193 % (abortstate._opname)
193 ),
194 ),
194 hint=abortstate.hint(),
195 hint=abortstate.hint(),
195 )
196 )
196 if dryrun:
197 if dryrun:
197 ui.status(
198 ui.status(
198 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
199 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
199 )
200 )
200 return
201 return
201 return abortstate.abortfunc(ui, repo)
202 return abortstate.abortfunc(ui, repo)
202
203
203
204
204 @command(
205 @command(
205 b'add',
206 b'add',
206 walkopts + subrepoopts + dryrunopts,
207 walkopts + subrepoopts + dryrunopts,
207 _(b'[OPTION]... [FILE]...'),
208 _(b'[OPTION]... [FILE]...'),
208 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
209 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
209 helpbasic=True,
210 helpbasic=True,
210 inferrepo=True,
211 inferrepo=True,
211 )
212 )
212 def add(ui, repo, *pats, **opts):
213 def add(ui, repo, *pats, **opts):
213 """add the specified files on the next commit
214 """add the specified files on the next commit
214
215
215 Schedule files to be version controlled and added to the
216 Schedule files to be version controlled and added to the
216 repository.
217 repository.
217
218
218 The files will be added to the repository at the next commit. To
219 The files will be added to the repository at the next commit. To
219 undo an add before that, see :hg:`forget`.
220 undo an add before that, see :hg:`forget`.
220
221
221 If no names are given, add all files to the repository (except
222 If no names are given, add all files to the repository (except
222 files matching ``.hgignore``).
223 files matching ``.hgignore``).
223
224
224 .. container:: verbose
225 .. container:: verbose
225
226
226 Examples:
227 Examples:
227
228
228 - New (unknown) files are added
229 - New (unknown) files are added
229 automatically by :hg:`add`::
230 automatically by :hg:`add`::
230
231
231 $ ls
232 $ ls
232 foo.c
233 foo.c
233 $ hg status
234 $ hg status
234 ? foo.c
235 ? foo.c
235 $ hg add
236 $ hg add
236 adding foo.c
237 adding foo.c
237 $ hg status
238 $ hg status
238 A foo.c
239 A foo.c
239
240
240 - Specific files to be added can be specified::
241 - Specific files to be added can be specified::
241
242
242 $ ls
243 $ ls
243 bar.c foo.c
244 bar.c foo.c
244 $ hg status
245 $ hg status
245 ? bar.c
246 ? bar.c
246 ? foo.c
247 ? foo.c
247 $ hg add bar.c
248 $ hg add bar.c
248 $ hg status
249 $ hg status
249 A bar.c
250 A bar.c
250 ? foo.c
251 ? foo.c
251
252
252 Returns 0 if all files are successfully added.
253 Returns 0 if all files are successfully added.
253 """
254 """
254
255
255 with repo.wlock(), repo.dirstate.changing_files(repo):
256 with repo.wlock(), repo.dirstate.changing_files(repo):
256 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 return rejected and 1 or 0
260 return rejected and 1 or 0
260
261
261
262
262 @command(
263 @command(
263 b'addremove',
264 b'addremove',
264 similarityopts + subrepoopts + walkopts + dryrunopts,
265 similarityopts + subrepoopts + walkopts + dryrunopts,
265 _(b'[OPTION]... [FILE]...'),
266 _(b'[OPTION]... [FILE]...'),
266 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 inferrepo=True,
268 inferrepo=True,
268 )
269 )
269 def addremove(ui, repo, *pats, **opts):
270 def addremove(ui, repo, *pats, **opts):
270 """add all new files, delete all missing files
271 """add all new files, delete all missing files
271
272
272 Add all new files and remove all missing files from the
273 Add all new files and remove all missing files from the
273 repository.
274 repository.
274
275
275 Unless names are given, new files are ignored if they match any of
276 Unless names are given, new files are ignored if they match any of
276 the patterns in ``.hgignore``. As with add, these changes take
277 the patterns in ``.hgignore``. As with add, these changes take
277 effect at the next commit.
278 effect at the next commit.
278
279
279 Use the -s/--similarity option to detect renamed files. This
280 Use the -s/--similarity option to detect renamed files. This
280 option takes a percentage between 0 (disabled) and 100 (files must
281 option takes a percentage between 0 (disabled) and 100 (files must
281 be identical) as its parameter. With a parameter greater than 0,
282 be identical) as its parameter. With a parameter greater than 0,
282 this compares every removed file with every added file and records
283 this compares every removed file with every added file and records
283 those similar enough as renames. Detecting renamed files this way
284 those similar enough as renames. Detecting renamed files this way
284 can be expensive. After using this option, :hg:`status -C` can be
285 can be expensive. After using this option, :hg:`status -C` can be
285 used to check which files were identified as moved or renamed. If
286 used to check which files were identified as moved or renamed. If
286 not specified, -s/--similarity defaults to 100 and only renames of
287 not specified, -s/--similarity defaults to 100 and only renames of
287 identical files are detected.
288 identical files are detected.
288
289
289 .. container:: verbose
290 .. container:: verbose
290
291
291 Examples:
292 Examples:
292
293
293 - A number of files (bar.c and foo.c) are new,
294 - A number of files (bar.c and foo.c) are new,
294 while foobar.c has been removed (without using :hg:`remove`)
295 while foobar.c has been removed (without using :hg:`remove`)
295 from the repository::
296 from the repository::
296
297
297 $ ls
298 $ ls
298 bar.c foo.c
299 bar.c foo.c
299 $ hg status
300 $ hg status
300 ! foobar.c
301 ! foobar.c
301 ? bar.c
302 ? bar.c
302 ? foo.c
303 ? foo.c
303 $ hg addremove
304 $ hg addremove
304 adding bar.c
305 adding bar.c
305 adding foo.c
306 adding foo.c
306 removing foobar.c
307 removing foobar.c
307 $ hg status
308 $ hg status
308 A bar.c
309 A bar.c
309 A foo.c
310 A foo.c
310 R foobar.c
311 R foobar.c
311
312
312 - A file foobar.c was moved to foo.c without using :hg:`rename`.
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
313 Afterwards, it was edited slightly::
314 Afterwards, it was edited slightly::
314
315
315 $ ls
316 $ ls
316 foo.c
317 foo.c
317 $ hg status
318 $ hg status
318 ! foobar.c
319 ! foobar.c
319 ? foo.c
320 ? foo.c
320 $ hg addremove --similarity 90
321 $ hg addremove --similarity 90
321 removing foobar.c
322 removing foobar.c
322 adding foo.c
323 adding foo.c
323 recording removal of foobar.c as rename to foo.c (94% similar)
324 recording removal of foobar.c as rename to foo.c (94% similar)
324 $ hg status -C
325 $ hg status -C
325 A foo.c
326 A foo.c
326 foobar.c
327 foobar.c
327 R foobar.c
328 R foobar.c
328
329
329 Returns 0 if all files are successfully added.
330 Returns 0 if all files are successfully added.
330 """
331 """
331 opts = pycompat.byteskwargs(opts)
332 opts = pycompat.byteskwargs(opts)
332 if not opts.get(b'similarity'):
333 if not opts.get(b'similarity'):
333 opts[b'similarity'] = b'100'
334 opts[b'similarity'] = b'100'
334 with repo.wlock(), repo.dirstate.changing_files(repo):
335 with repo.wlock(), repo.dirstate.changing_files(repo):
335 matcher = scmutil.match(repo[None], pats, opts)
336 matcher = scmutil.match(repo[None], pats, opts)
336 relative = scmutil.anypats(pats, opts)
337 relative = scmutil.anypats(pats, opts)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339
340
340
341
341 @command(
342 @command(
342 b'annotate|blame',
343 b'annotate|blame',
343 [
344 [
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (
346 (
346 b'',
347 b'',
347 b'follow',
348 b'follow',
348 None,
349 None,
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 ),
351 ),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'f', b'file', None, _(b'list the filename')),
355 (b'f', b'file', None, _(b'list the filename')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
358 (b'c', b'changeset', None, _(b'list the changeset')),
358 (
359 (
359 b'l',
360 b'l',
360 b'line-number',
361 b'line-number',
361 None,
362 None,
362 _(b'show line number at the first appearance'),
363 _(b'show line number at the first appearance'),
363 ),
364 ),
364 (
365 (
365 b'',
366 b'',
366 b'skip',
367 b'skip',
367 [],
368 [],
368 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'REV'),
370 _(b'REV'),
370 ),
371 ),
371 ]
372 ]
372 + diffwsopts
373 + diffwsopts
373 + walkopts
374 + walkopts
374 + formatteropts,
375 + formatteropts,
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpbasic=True,
378 helpbasic=True,
378 inferrepo=True,
379 inferrepo=True,
379 )
380 )
380 def annotate(ui, repo, *pats, **opts):
381 def annotate(ui, repo, *pats, **opts):
381 """show changeset information by line for each file
382 """show changeset information by line for each file
382
383
383 List changes in files, showing the revision id responsible for
384 List changes in files, showing the revision id responsible for
384 each line.
385 each line.
385
386
386 This command is useful for discovering when a change was made and
387 This command is useful for discovering when a change was made and
387 by whom.
388 by whom.
388
389
389 If you include --file, --user, or --date, the revision number is
390 If you include --file, --user, or --date, the revision number is
390 suppressed unless you also include --number.
391 suppressed unless you also include --number.
391
392
392 Without the -a/--text option, annotate will avoid processing files
393 Without the -a/--text option, annotate will avoid processing files
393 it detects as binary. With -a, annotate will annotate the file
394 it detects as binary. With -a, annotate will annotate the file
394 anyway, although the results will probably be neither useful
395 anyway, although the results will probably be neither useful
395 nor desirable.
396 nor desirable.
396
397
397 .. container:: verbose
398 .. container:: verbose
398
399
399 Template:
400 Template:
400
401
401 The following keywords are supported in addition to the common template
402 The following keywords are supported in addition to the common template
402 keywords and functions. See also :hg:`help templates`.
403 keywords and functions. See also :hg:`help templates`.
403
404
404 :lines: List of lines with annotation data.
405 :lines: List of lines with annotation data.
405 :path: String. Repository-absolute path of the specified file.
406 :path: String. Repository-absolute path of the specified file.
406
407
407 And each entry of ``{lines}`` provides the following sub-keywords in
408 And each entry of ``{lines}`` provides the following sub-keywords in
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409
410
410 :line: String. Line content.
411 :line: String. Line content.
411 :lineno: Integer. Line number at that revision.
412 :lineno: Integer. Line number at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
413 :path: String. Repository-absolute path of the file at that revision.
413
414
414 See :hg:`help templates.operators` for the list expansion syntax.
415 See :hg:`help templates.operators` for the list expansion syntax.
415
416
416 Returns 0 on success.
417 Returns 0 on success.
417 """
418 """
418 opts = pycompat.byteskwargs(opts)
419 opts = pycompat.byteskwargs(opts)
419 if not pats:
420 if not pats:
420 raise error.InputError(
421 raise error.InputError(
421 _(b'at least one filename or pattern is required')
422 _(b'at least one filename or pattern is required')
422 )
423 )
423
424
424 if opts.get(b'follow'):
425 if opts.get(b'follow'):
425 # --follow is deprecated and now just an alias for -f/--file
426 # --follow is deprecated and now just an alias for -f/--file
426 # to mimic the behavior of Mercurial before version 1.5
427 # to mimic the behavior of Mercurial before version 1.5
427 opts[b'file'] = True
428 opts[b'file'] = True
428
429
429 if (
430 if (
430 not opts.get(b'user')
431 not opts.get(b'user')
431 and not opts.get(b'changeset')
432 and not opts.get(b'changeset')
432 and not opts.get(b'date')
433 and not opts.get(b'date')
433 and not opts.get(b'file')
434 and not opts.get(b'file')
434 ):
435 ):
435 opts[b'number'] = True
436 opts[b'number'] = True
436
437
437 linenumber = opts.get(b'line_number') is not None
438 linenumber = opts.get(b'line_number') is not None
438 if (
439 if (
439 linenumber
440 linenumber
440 and (not opts.get(b'changeset'))
441 and (not opts.get(b'changeset'))
441 and (not opts.get(b'number'))
442 and (not opts.get(b'number'))
442 ):
443 ):
443 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
444 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
444
445
445 rev = opts.get(b'rev')
446 rev = opts.get(b'rev')
446 if rev:
447 if rev:
447 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
448 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
448 ctx = logcmdutil.revsingle(repo, rev)
449 ctx = logcmdutil.revsingle(repo, rev)
449
450
450 ui.pager(b'annotate')
451 ui.pager(b'annotate')
451 rootfm = ui.formatter(b'annotate', opts)
452 rootfm = ui.formatter(b'annotate', opts)
452 if ui.debugflag:
453 if ui.debugflag:
453 shorthex = pycompat.identity
454 shorthex = pycompat.identity
454 else:
455 else:
455
456
456 def shorthex(h):
457 def shorthex(h):
457 return h[:12]
458 return h[:12]
458
459
459 if ui.quiet:
460 if ui.quiet:
460 datefunc = dateutil.shortdate
461 datefunc = dateutil.shortdate
461 else:
462 else:
462 datefunc = dateutil.datestr
463 datefunc = dateutil.datestr
463 if ctx.rev() is None:
464 if ctx.rev() is None:
464 if opts.get(b'changeset'):
465 if opts.get(b'changeset'):
465 # omit "+" suffix which is appended to node hex
466 # omit "+" suffix which is appended to node hex
466 def formatrev(rev):
467 def formatrev(rev):
467 if rev == wdirrev:
468 if rev == wdirrev:
468 return b'%d' % ctx.p1().rev()
469 return b'%d' % ctx.p1().rev()
469 else:
470 else:
470 return b'%d' % rev
471 return b'%d' % rev
471
472
472 else:
473 else:
473
474
474 def formatrev(rev):
475 def formatrev(rev):
475 if rev == wdirrev:
476 if rev == wdirrev:
476 return b'%d+' % ctx.p1().rev()
477 return b'%d+' % ctx.p1().rev()
477 else:
478 else:
478 return b'%d ' % rev
479 return b'%d ' % rev
479
480
480 def formathex(h):
481 def formathex(h):
481 if h == repo.nodeconstants.wdirhex:
482 if h == repo.nodeconstants.wdirhex:
482 return b'%s+' % shorthex(hex(ctx.p1().node()))
483 return b'%s+' % shorthex(hex(ctx.p1().node()))
483 else:
484 else:
484 return b'%s ' % shorthex(h)
485 return b'%s ' % shorthex(h)
485
486
486 else:
487 else:
487 formatrev = b'%d'.__mod__
488 formatrev = b'%d'.__mod__
488 formathex = shorthex
489 formathex = shorthex
489
490
490 opmap = [
491 opmap = [
491 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
492 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
492 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
493 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
493 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
494 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
494 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
495 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
495 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
496 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
496 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
497 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
497 ]
498 ]
498 opnamemap = {
499 opnamemap = {
499 b'rev': b'number',
500 b'rev': b'number',
500 b'node': b'changeset',
501 b'node': b'changeset',
501 b'path': b'file',
502 b'path': b'file',
502 b'lineno': b'line_number',
503 b'lineno': b'line_number',
503 }
504 }
504
505
505 if rootfm.isplain():
506 if rootfm.isplain():
506
507
507 def makefunc(get, fmt):
508 def makefunc(get, fmt):
508 return lambda x: fmt(get(x))
509 return lambda x: fmt(get(x))
509
510
510 else:
511 else:
511
512
512 def makefunc(get, fmt):
513 def makefunc(get, fmt):
513 return get
514 return get
514
515
515 datahint = rootfm.datahint()
516 datahint = rootfm.datahint()
516 funcmap = [
517 funcmap = [
517 (makefunc(get, fmt), sep)
518 (makefunc(get, fmt), sep)
518 for fn, sep, get, fmt in opmap
519 for fn, sep, get, fmt in opmap
519 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 ]
521 ]
521 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
522 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
522 fields = b' '.join(
523 fields = b' '.join(
523 fn
524 fn
524 for fn, sep, get, fmt in opmap
525 for fn, sep, get, fmt in opmap
525 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
526 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
526 )
527 )
527
528
528 def bad(x, y):
529 def bad(x, y):
529 raise error.InputError(b"%s: %s" % (x, y))
530 raise error.InputError(b"%s: %s" % (x, y))
530
531
531 m = scmutil.match(ctx, pats, opts, badfn=bad)
532 m = scmutil.match(ctx, pats, opts, badfn=bad)
532
533
533 follow = not opts.get(b'no_follow')
534 follow = not opts.get(b'no_follow')
534 diffopts = patch.difffeatureopts(
535 diffopts = patch.difffeatureopts(
535 ui, opts, section=b'annotate', whitespace=True
536 ui, opts, section=b'annotate', whitespace=True
536 )
537 )
537 skiprevs = opts.get(b'skip')
538 skiprevs = opts.get(b'skip')
538 if skiprevs:
539 if skiprevs:
539 skiprevs = logcmdutil.revrange(repo, skiprevs)
540 skiprevs = logcmdutil.revrange(repo, skiprevs)
540
541
541 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
542 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
542 for abs in ctx.walk(m):
543 for abs in ctx.walk(m):
543 fctx = ctx[abs]
544 fctx = ctx[abs]
544 rootfm.startitem()
545 rootfm.startitem()
545 rootfm.data(path=abs)
546 rootfm.data(path=abs)
546 if not opts.get(b'text') and fctx.isbinary():
547 if not opts.get(b'text') and fctx.isbinary():
547 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
548 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
548 continue
549 continue
549
550
550 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
551 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
551 lines = fctx.annotate(
552 lines = fctx.annotate(
552 follow=follow, skiprevs=skiprevs, diffopts=diffopts
553 follow=follow, skiprevs=skiprevs, diffopts=diffopts
553 )
554 )
554 if not lines:
555 if not lines:
555 fm.end()
556 fm.end()
556 continue
557 continue
557 formats = []
558 formats = []
558 pieces = []
559 pieces = []
559
560
560 for f, sep in funcmap:
561 for f, sep in funcmap:
561 l = [f(n) for n in lines]
562 l = [f(n) for n in lines]
562 if fm.isplain():
563 if fm.isplain():
563 sizes = [encoding.colwidth(x) for x in l]
564 sizes = [encoding.colwidth(x) for x in l]
564 ml = max(sizes)
565 ml = max(sizes)
565 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
566 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
566 else:
567 else:
567 formats.append([b'%s'] * len(l))
568 formats.append([b'%s'] * len(l))
568 pieces.append(l)
569 pieces.append(l)
569
570
570 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
571 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
571 fm.startitem()
572 fm.startitem()
572 fm.context(fctx=n.fctx)
573 fm.context(fctx=n.fctx)
573 fm.write(fields, b"".join(f), *p)
574 fm.write(fields, b"".join(f), *p)
574 if n.skip:
575 if n.skip:
575 fmt = b"* %s"
576 fmt = b"* %s"
576 else:
577 else:
577 fmt = b": %s"
578 fmt = b": %s"
578 fm.write(b'line', fmt, n.text)
579 fm.write(b'line', fmt, n.text)
579
580
580 if not lines[-1].text.endswith(b'\n'):
581 if not lines[-1].text.endswith(b'\n'):
581 fm.plain(b'\n')
582 fm.plain(b'\n')
582 fm.end()
583 fm.end()
583
584
584 rootfm.end()
585 rootfm.end()
585
586
586
587
587 @command(
588 @command(
588 b'archive',
589 b'archive',
589 [
590 [
590 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
591 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
591 (
592 (
592 b'p',
593 b'p',
593 b'prefix',
594 b'prefix',
594 b'',
595 b'',
595 _(b'directory prefix for files in archive'),
596 _(b'directory prefix for files in archive'),
596 _(b'PREFIX'),
597 _(b'PREFIX'),
597 ),
598 ),
598 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
599 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
599 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
600 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
600 ]
601 ]
601 + subrepoopts
602 + subrepoopts
602 + walkopts,
603 + walkopts,
603 _(b'[OPTION]... DEST'),
604 _(b'[OPTION]... DEST'),
604 helpcategory=command.CATEGORY_IMPORT_EXPORT,
605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
605 )
606 )
606 def archive(ui, repo, dest, **opts):
607 def archive(ui, repo, dest, **opts):
607 """create an unversioned archive of a repository revision
608 """create an unversioned archive of a repository revision
608
609
609 By default, the revision used is the parent of the working
610 By default, the revision used is the parent of the working
610 directory; use -r/--rev to specify a different revision.
611 directory; use -r/--rev to specify a different revision.
611
612
612 The archive type is automatically detected based on file
613 The archive type is automatically detected based on file
613 extension (to override, use -t/--type).
614 extension (to override, use -t/--type).
614
615
615 .. container:: verbose
616 .. container:: verbose
616
617
617 Examples:
618 Examples:
618
619
619 - create a zip file containing the 1.0 release::
620 - create a zip file containing the 1.0 release::
620
621
621 hg archive -r 1.0 project-1.0.zip
622 hg archive -r 1.0 project-1.0.zip
622
623
623 - create a tarball excluding .hg files::
624 - create a tarball excluding .hg files::
624
625
625 hg archive project.tar.gz -X ".hg*"
626 hg archive project.tar.gz -X ".hg*"
626
627
627 Valid types are:
628 Valid types are:
628
629
629 :``files``: a directory full of files (default)
630 :``files``: a directory full of files (default)
630 :``tar``: tar archive, uncompressed
631 :``tar``: tar archive, uncompressed
631 :``tbz2``: tar archive, compressed using bzip2
632 :``tbz2``: tar archive, compressed using bzip2
632 :``tgz``: tar archive, compressed using gzip
633 :``tgz``: tar archive, compressed using gzip
633 :``txz``: tar archive, compressed using lzma (only in Python 3)
634 :``txz``: tar archive, compressed using lzma (only in Python 3)
634 :``uzip``: zip archive, uncompressed
635 :``uzip``: zip archive, uncompressed
635 :``zip``: zip archive, compressed using deflate
636 :``zip``: zip archive, compressed using deflate
636
637
637 The exact name of the destination archive or directory is given
638 The exact name of the destination archive or directory is given
638 using a format string; see :hg:`help export` for details.
639 using a format string; see :hg:`help export` for details.
639
640
640 Each member added to an archive file has a directory prefix
641 Each member added to an archive file has a directory prefix
641 prepended. Use -p/--prefix to specify a format string for the
642 prepended. Use -p/--prefix to specify a format string for the
642 prefix. The default is the basename of the archive, with suffixes
643 prefix. The default is the basename of the archive, with suffixes
643 removed.
644 removed.
644
645
645 Returns 0 on success.
646 Returns 0 on success.
646 """
647 """
647
648
648 opts = pycompat.byteskwargs(opts)
649 opts = pycompat.byteskwargs(opts)
649 rev = opts.get(b'rev')
650 rev = opts.get(b'rev')
650 if rev:
651 if rev:
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 ctx = logcmdutil.revsingle(repo, rev)
653 ctx = logcmdutil.revsingle(repo, rev)
653 if not ctx:
654 if not ctx:
654 raise error.InputError(
655 raise error.InputError(
655 _(b'no working directory: please specify a revision')
656 _(b'no working directory: please specify a revision')
656 )
657 )
657 node = ctx.node()
658 node = ctx.node()
658 dest = cmdutil.makefilename(ctx, dest)
659 dest = cmdutil.makefilename(ctx, dest)
659 if os.path.realpath(dest) == repo.root:
660 if os.path.realpath(dest) == repo.root:
660 raise error.InputError(_(b'repository root cannot be destination'))
661 raise error.InputError(_(b'repository root cannot be destination'))
661
662
662 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
663 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
663 prefix = opts.get(b'prefix')
664 prefix = opts.get(b'prefix')
664
665
665 if dest == b'-':
666 if dest == b'-':
666 if kind == b'files':
667 if kind == b'files':
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 dest = cmdutil.makefileobj(ctx, dest)
669 dest = cmdutil.makefileobj(ctx, dest)
669 if not prefix:
670 if not prefix:
670 prefix = os.path.basename(repo.root) + b'-%h'
671 prefix = os.path.basename(repo.root) + b'-%h'
671
672
672 prefix = cmdutil.makefilename(ctx, prefix)
673 prefix = cmdutil.makefilename(ctx, prefix)
673 match = scmutil.match(ctx, [], opts)
674 match = scmutil.match(ctx, [], opts)
674 archival.archive(
675 archival.archive(
675 repo,
676 repo,
676 dest,
677 dest,
677 node,
678 node,
678 kind,
679 kind,
679 not opts.get(b'no_decode'),
680 not opts.get(b'no_decode'),
680 match,
681 match,
681 prefix,
682 prefix,
682 subrepos=opts.get(b'subrepos'),
683 subrepos=opts.get(b'subrepos'),
683 )
684 )
684
685
685
686
686 @command(
687 @command(
687 b'backout',
688 b'backout',
688 [
689 [
689 (
690 (
690 b'',
691 b'',
691 b'merge',
692 b'merge',
692 None,
693 None,
693 _(b'merge with old dirstate parent after backout'),
694 _(b'merge with old dirstate parent after backout'),
694 ),
695 ),
695 (
696 (
696 b'',
697 b'',
697 b'commit',
698 b'commit',
698 None,
699 None,
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 ),
701 ),
701 (b'', b'no-commit', None, _(b'do not commit')),
702 (b'', b'no-commit', None, _(b'do not commit')),
702 (
703 (
703 b'',
704 b'',
704 b'parent',
705 b'parent',
705 b'',
706 b'',
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 _(b'REV'),
708 _(b'REV'),
708 ),
709 ),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 ]
712 ]
712 + mergetoolopts
713 + mergetoolopts
713 + walkopts
714 + walkopts
714 + commitopts
715 + commitopts
715 + commitopts2,
716 + commitopts2,
716 _(b'[OPTION]... [-r] REV'),
717 _(b'[OPTION]... [-r] REV'),
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 )
719 )
719 def backout(ui, repo, node=None, rev=None, **opts):
720 def backout(ui, repo, node=None, rev=None, **opts):
720 """reverse effect of earlier changeset
721 """reverse effect of earlier changeset
721
722
722 Prepare a new changeset with the effect of REV undone in the
723 Prepare a new changeset with the effect of REV undone in the
723 current working directory. If no conflicts were encountered,
724 current working directory. If no conflicts were encountered,
724 it will be committed immediately.
725 it will be committed immediately.
725
726
726 If REV is the parent of the working directory, then this new changeset
727 If REV is the parent of the working directory, then this new changeset
727 is committed automatically (unless --no-commit is specified).
728 is committed automatically (unless --no-commit is specified).
728
729
729 .. note::
730 .. note::
730
731
731 :hg:`backout` cannot be used to fix either an unwanted or
732 :hg:`backout` cannot be used to fix either an unwanted or
732 incorrect merge.
733 incorrect merge.
733
734
734 .. container:: verbose
735 .. container:: verbose
735
736
736 Examples:
737 Examples:
737
738
738 - Reverse the effect of the parent of the working directory.
739 - Reverse the effect of the parent of the working directory.
739 This backout will be committed immediately::
740 This backout will be committed immediately::
740
741
741 hg backout -r .
742 hg backout -r .
742
743
743 - Reverse the effect of previous bad revision 23::
744 - Reverse the effect of previous bad revision 23::
744
745
745 hg backout -r 23
746 hg backout -r 23
746
747
747 - Reverse the effect of previous bad revision 23 and
748 - Reverse the effect of previous bad revision 23 and
748 leave changes uncommitted::
749 leave changes uncommitted::
749
750
750 hg backout -r 23 --no-commit
751 hg backout -r 23 --no-commit
751 hg commit -m "Backout revision 23"
752 hg commit -m "Backout revision 23"
752
753
753 By default, the pending changeset will have one parent,
754 By default, the pending changeset will have one parent,
754 maintaining a linear history. With --merge, the pending
755 maintaining a linear history. With --merge, the pending
755 changeset will instead have two parents: the old parent of the
756 changeset will instead have two parents: the old parent of the
756 working directory and a new child of REV that simply undoes REV.
757 working directory and a new child of REV that simply undoes REV.
757
758
758 Before version 1.7, the behavior without --merge was equivalent
759 Before version 1.7, the behavior without --merge was equivalent
759 to specifying --merge followed by :hg:`update --clean .` to
760 to specifying --merge followed by :hg:`update --clean .` to
760 cancel the merge and leave the child of REV as a head to be
761 cancel the merge and leave the child of REV as a head to be
761 merged separately.
762 merged separately.
762
763
763 See :hg:`help dates` for a list of formats valid for -d/--date.
764 See :hg:`help dates` for a list of formats valid for -d/--date.
764
765
765 See :hg:`help revert` for a way to restore files to the state
766 See :hg:`help revert` for a way to restore files to the state
766 of another revision.
767 of another revision.
767
768
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 files.
770 files.
770 """
771 """
771 with repo.wlock(), repo.lock():
772 with repo.wlock(), repo.lock():
772 return _dobackout(ui, repo, node, rev, **opts)
773 return _dobackout(ui, repo, node, rev, **opts)
773
774
774
775
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777 opts = pycompat.byteskwargs(opts)
778 opts = pycompat.byteskwargs(opts)
778
779
779 if rev and node:
780 if rev and node:
780 raise error.InputError(_(b"please specify just one revision"))
781 raise error.InputError(_(b"please specify just one revision"))
781
782
782 if not rev:
783 if not rev:
783 rev = node
784 rev = node
784
785
785 if not rev:
786 if not rev:
786 raise error.InputError(_(b"please specify a revision to backout"))
787 raise error.InputError(_(b"please specify a revision to backout"))
787
788
788 date = opts.get(b'date')
789 date = opts.get(b'date')
789 if date:
790 if date:
790 opts[b'date'] = dateutil.parsedate(date)
791 opts[b'date'] = dateutil.parsedate(date)
791
792
792 cmdutil.checkunfinished(repo)
793 cmdutil.checkunfinished(repo)
793 cmdutil.bailifchanged(repo)
794 cmdutil.bailifchanged(repo)
794 ctx = logcmdutil.revsingle(repo, rev)
795 ctx = logcmdutil.revsingle(repo, rev)
795 node = ctx.node()
796 node = ctx.node()
796
797
797 op1, op2 = repo.dirstate.parents()
798 op1, op2 = repo.dirstate.parents()
798 if not repo.changelog.isancestor(node, op1):
799 if not repo.changelog.isancestor(node, op1):
799 raise error.InputError(
800 raise error.InputError(
800 _(b'cannot backout change that is not an ancestor')
801 _(b'cannot backout change that is not an ancestor')
801 )
802 )
802
803
803 p1, p2 = repo.changelog.parents(node)
804 p1, p2 = repo.changelog.parents(node)
804 if p1 == repo.nullid:
805 if p1 == repo.nullid:
805 raise error.InputError(_(b'cannot backout a change with no parents'))
806 raise error.InputError(_(b'cannot backout a change with no parents'))
806 if p2 != repo.nullid:
807 if p2 != repo.nullid:
807 if not opts.get(b'parent'):
808 if not opts.get(b'parent'):
808 raise error.InputError(_(b'cannot backout a merge changeset'))
809 raise error.InputError(_(b'cannot backout a merge changeset'))
809 p = repo.lookup(opts[b'parent'])
810 p = repo.lookup(opts[b'parent'])
810 if p not in (p1, p2):
811 if p not in (p1, p2):
811 raise error.InputError(
812 raise error.InputError(
812 _(b'%s is not a parent of %s') % (short(p), short(node))
813 _(b'%s is not a parent of %s') % (short(p), short(node))
813 )
814 )
814 parent = p
815 parent = p
815 else:
816 else:
816 if opts.get(b'parent'):
817 if opts.get(b'parent'):
817 raise error.InputError(
818 raise error.InputError(
818 _(b'cannot use --parent on non-merge changeset')
819 _(b'cannot use --parent on non-merge changeset')
819 )
820 )
820 parent = p1
821 parent = p1
821
822
822 # the backout should appear on the same branch
823 # the backout should appear on the same branch
823 branch = repo.dirstate.branch()
824 branch = repo.dirstate.branch()
824 bheads = repo.branchheads(branch)
825 bheads = repo.branchheads(branch)
825 rctx = scmutil.revsingle(repo, hex(parent))
826 rctx = scmutil.revsingle(repo, hex(parent))
826 if not opts.get(b'merge') and op1 != node:
827 if not opts.get(b'merge') and op1 != node:
827 with repo.transaction(b"backout"):
828 with repo.transaction(b"backout"):
828 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
829 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
829 with ui.configoverride(overrides, b'backout'):
830 with ui.configoverride(overrides, b'backout'):
830 stats = mergemod.back_out(ctx, parent=repo[parent])
831 stats = mergemod.back_out(ctx, parent=repo[parent])
831 repo.setparents(op1, op2)
832 repo.setparents(op1, op2)
832 hg._showstats(repo, stats)
833 hg._showstats(repo, stats)
833 if stats.unresolvedcount:
834 if stats.unresolvedcount:
834 repo.ui.status(
835 repo.ui.status(
835 _(b"use 'hg resolve' to retry unresolved file merges\n")
836 _(b"use 'hg resolve' to retry unresolved file merges\n")
836 )
837 )
837 return 1
838 return 1
838 else:
839 else:
839 hg.clean(repo, node, show_stats=False)
840 hg.clean(repo, node, show_stats=False)
840 repo.dirstate.setbranch(branch, repo.currenttransaction())
841 repo.dirstate.setbranch(branch, repo.currenttransaction())
841 cmdutil.revert(ui, repo, rctx)
842 cmdutil.revert(ui, repo, rctx)
842
843
843 if opts.get(b'no_commit'):
844 if opts.get(b'no_commit'):
844 msg = _(b"changeset %s backed out, don't forget to commit.\n")
845 msg = _(b"changeset %s backed out, don't forget to commit.\n")
845 ui.status(msg % short(node))
846 ui.status(msg % short(node))
846 return 0
847 return 0
847
848
848 def commitfunc(ui, repo, message, match, opts):
849 def commitfunc(ui, repo, message, match, opts):
849 editform = b'backout'
850 editform = b'backout'
850 e = cmdutil.getcommiteditor(
851 e = cmdutil.getcommiteditor(
851 editform=editform, **pycompat.strkwargs(opts)
852 editform=editform, **pycompat.strkwargs(opts)
852 )
853 )
853 if not message:
854 if not message:
854 # we don't translate commit messages
855 # we don't translate commit messages
855 message = b"Backed out changeset %s" % short(node)
856 message = b"Backed out changeset %s" % short(node)
856 e = cmdutil.getcommiteditor(edit=True, editform=editform)
857 e = cmdutil.getcommiteditor(edit=True, editform=editform)
857 return repo.commit(
858 return repo.commit(
858 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
859 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
859 )
860 )
860
861
861 # save to detect changes
862 # save to detect changes
862 tip = repo.changelog.tip()
863 tip = repo.changelog.tip()
863
864
864 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
865 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
865 if not newnode:
866 if not newnode:
866 ui.status(_(b"nothing changed\n"))
867 ui.status(_(b"nothing changed\n"))
867 return 1
868 return 1
868 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
869 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
869
870
870 def nice(node):
871 def nice(node):
871 return b'%d:%s' % (repo.changelog.rev(node), short(node))
872 return b'%d:%s' % (repo.changelog.rev(node), short(node))
872
873
873 ui.status(
874 ui.status(
874 _(b'changeset %s backs out changeset %s\n')
875 _(b'changeset %s backs out changeset %s\n')
875 % (nice(newnode), nice(node))
876 % (nice(newnode), nice(node))
876 )
877 )
877 if opts.get(b'merge') and op1 != node:
878 if opts.get(b'merge') and op1 != node:
878 hg.clean(repo, op1, show_stats=False)
879 hg.clean(repo, op1, show_stats=False)
879 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
880 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 with ui.configoverride(overrides, b'backout'):
882 with ui.configoverride(overrides, b'backout'):
882 return hg.merge(repo[b'tip'])
883 return hg.merge(repo[b'tip'])
883 return 0
884 return 0
884
885
885
886
886 @command(
887 @command(
887 b'bisect',
888 b'bisect',
888 [
889 [
889 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (
895 (
895 b'c',
896 b'c',
896 b'command',
897 b'command',
897 b'',
898 b'',
898 _(b'use command to check changeset state'),
899 _(b'use command to check changeset state'),
899 _(b'CMD'),
900 _(b'CMD'),
900 ),
901 ),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
902 (b'U', b'noupdate', False, _(b'do not update to target')),
902 ],
903 ],
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 )
906 )
906 def bisect(
907 def bisect(
907 ui,
908 ui,
908 repo,
909 repo,
909 positional_1=None,
910 positional_1=None,
910 positional_2=None,
911 positional_2=None,
911 command=None,
912 command=None,
912 reset=None,
913 reset=None,
913 good=None,
914 good=None,
914 bad=None,
915 bad=None,
915 skip=None,
916 skip=None,
916 extend=None,
917 extend=None,
917 noupdate=None,
918 noupdate=None,
918 ):
919 ):
919 """subdivision search of changesets
920 """subdivision search of changesets
920
921
921 This command helps to find changesets which introduce problems. To
922 This command helps to find changesets which introduce problems. To
922 use, mark the earliest changeset you know exhibits the problem as
923 use, mark the earliest changeset you know exhibits the problem as
923 bad, then mark the latest changeset which is free from the problem
924 bad, then mark the latest changeset which is free from the problem
924 as good. Bisect will update your working directory to a revision
925 as good. Bisect will update your working directory to a revision
925 for testing (unless the -U/--noupdate option is specified). Once
926 for testing (unless the -U/--noupdate option is specified). Once
926 you have performed tests, mark the working directory as good or
927 you have performed tests, mark the working directory as good or
927 bad, and bisect will either update to another candidate changeset
928 bad, and bisect will either update to another candidate changeset
928 or announce that it has found the bad revision.
929 or announce that it has found the bad revision.
929
930
930 As a shortcut, you can also use the revision argument to mark a
931 As a shortcut, you can also use the revision argument to mark a
931 revision as good or bad without checking it out first.
932 revision as good or bad without checking it out first.
932
933
933 If you supply a command, it will be used for automatic bisection.
934 If you supply a command, it will be used for automatic bisection.
934 The environment variable HG_NODE will contain the ID of the
935 The environment variable HG_NODE will contain the ID of the
935 changeset being tested. The exit status of the command will be
936 changeset being tested. The exit status of the command will be
936 used to mark revisions as good or bad: status 0 means good, 125
937 used to mark revisions as good or bad: status 0 means good, 125
937 means to skip the revision, 127 (command not found) will abort the
938 means to skip the revision, 127 (command not found) will abort the
938 bisection, and any other non-zero exit status means the revision
939 bisection, and any other non-zero exit status means the revision
939 is bad.
940 is bad.
940
941
941 .. container:: verbose
942 .. container:: verbose
942
943
943 Some examples:
944 Some examples:
944
945
945 - start a bisection with known bad revision 34, and good revision 12::
946 - start a bisection with known bad revision 34, and good revision 12::
946
947
947 hg bisect --bad 34
948 hg bisect --bad 34
948 hg bisect --good 12
949 hg bisect --good 12
949
950
950 - advance the current bisection by marking current revision as good or
951 - advance the current bisection by marking current revision as good or
951 bad::
952 bad::
952
953
953 hg bisect --good
954 hg bisect --good
954 hg bisect --bad
955 hg bisect --bad
955
956
956 - mark the current revision, or a known revision, to be skipped (e.g. if
957 - mark the current revision, or a known revision, to be skipped (e.g. if
957 that revision is not usable because of another issue)::
958 that revision is not usable because of another issue)::
958
959
959 hg bisect --skip
960 hg bisect --skip
960 hg bisect --skip 23
961 hg bisect --skip 23
961
962
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963
964
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965
966
966 - forget the current bisection::
967 - forget the current bisection::
967
968
968 hg bisect --reset
969 hg bisect --reset
969
970
970 - use 'make && make tests' to automatically find the first broken
971 - use 'make && make tests' to automatically find the first broken
971 revision::
972 revision::
972
973
973 hg bisect --reset
974 hg bisect --reset
974 hg bisect --bad 34
975 hg bisect --bad 34
975 hg bisect --good 12
976 hg bisect --good 12
976 hg bisect --command "make && make tests"
977 hg bisect --command "make && make tests"
977
978
978 - see all changesets whose states are already known in the current
979 - see all changesets whose states are already known in the current
979 bisection::
980 bisection::
980
981
981 hg log -r "bisect(pruned)"
982 hg log -r "bisect(pruned)"
982
983
983 - see the changeset currently being bisected (especially useful
984 - see the changeset currently being bisected (especially useful
984 if running with -U/--noupdate)::
985 if running with -U/--noupdate)::
985
986
986 hg log -r "bisect(current)"
987 hg log -r "bisect(current)"
987
988
988 - see all changesets that took part in the current bisection::
989 - see all changesets that took part in the current bisection::
989
990
990 hg log -r "bisect(range)"
991 hg log -r "bisect(range)"
991
992
992 - you can even get a nice graph::
993 - you can even get a nice graph::
993
994
994 hg log --graph -r "bisect(range)"
995 hg log --graph -r "bisect(range)"
995
996
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997
998
998 Returns 0 on success.
999 Returns 0 on success.
999 """
1000 """
1000 rev = []
1001 rev = []
1001 # backward compatibility
1002 # backward compatibility
1002 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1003 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1003 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1004 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1004 cmd = positional_1
1005 cmd = positional_1
1005 rev.append(positional_2)
1006 rev.append(positional_2)
1006 if cmd == b"good":
1007 if cmd == b"good":
1007 good = True
1008 good = True
1008 elif cmd == b"bad":
1009 elif cmd == b"bad":
1009 bad = True
1010 bad = True
1010 else:
1011 else:
1011 reset = True
1012 reset = True
1012 elif positional_2:
1013 elif positional_2:
1013 raise error.InputError(_(b'incompatible arguments'))
1014 raise error.InputError(_(b'incompatible arguments'))
1014 elif positional_1 is not None:
1015 elif positional_1 is not None:
1015 rev.append(positional_1)
1016 rev.append(positional_1)
1016
1017
1017 incompatibles = {
1018 incompatibles = {
1018 b'--bad': bad,
1019 b'--bad': bad,
1019 b'--command': bool(command),
1020 b'--command': bool(command),
1020 b'--extend': extend,
1021 b'--extend': extend,
1021 b'--good': good,
1022 b'--good': good,
1022 b'--reset': reset,
1023 b'--reset': reset,
1023 b'--skip': skip,
1024 b'--skip': skip,
1024 }
1025 }
1025
1026
1026 enabled = [x for x in incompatibles if incompatibles[x]]
1027 enabled = [x for x in incompatibles if incompatibles[x]]
1027
1028
1028 if len(enabled) > 1:
1029 if len(enabled) > 1:
1029 raise error.InputError(
1030 raise error.InputError(
1030 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1031 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1031 )
1032 )
1032
1033
1033 if reset:
1034 if reset:
1034 hbisect.resetstate(repo)
1035 hbisect.resetstate(repo)
1035 return
1036 return
1036
1037
1037 state = hbisect.load_state(repo)
1038 state = hbisect.load_state(repo)
1038
1039
1039 if rev:
1040 if rev:
1040 revs = logcmdutil.revrange(repo, rev)
1041 revs = logcmdutil.revrange(repo, rev)
1041 goodnodes = state[b'good']
1042 goodnodes = state[b'good']
1042 badnodes = state[b'bad']
1043 badnodes = state[b'bad']
1043 if goodnodes and badnodes:
1044 if goodnodes and badnodes:
1044 candidates = repo.revs(b'(%ln)::(%ln)', goodnodes, badnodes)
1045 candidates = repo.revs(b'(%ln)::(%ln)', goodnodes, badnodes)
1045 candidates += repo.revs(b'(%ln)::(%ln)', badnodes, goodnodes)
1046 candidates += repo.revs(b'(%ln)::(%ln)', badnodes, goodnodes)
1046 revs = candidates & revs
1047 revs = candidates & revs
1047 nodes = [repo.changelog.node(i) for i in revs]
1048 nodes = [repo.changelog.node(i) for i in revs]
1048 else:
1049 else:
1049 nodes = [repo.lookup(b'.')]
1050 nodes = [repo.lookup(b'.')]
1050
1051
1051 # update state
1052 # update state
1052 if good or bad or skip:
1053 if good or bad or skip:
1053 if good:
1054 if good:
1054 state[b'good'] += nodes
1055 state[b'good'] += nodes
1055 elif bad:
1056 elif bad:
1056 state[b'bad'] += nodes
1057 state[b'bad'] += nodes
1057 elif skip:
1058 elif skip:
1058 state[b'skip'] += nodes
1059 state[b'skip'] += nodes
1059 hbisect.save_state(repo, state)
1060 hbisect.save_state(repo, state)
1060 if not (state[b'good'] and state[b'bad']):
1061 if not (state[b'good'] and state[b'bad']):
1061 return
1062 return
1062
1063
1063 def mayupdate(repo, node, show_stats=True):
1064 def mayupdate(repo, node, show_stats=True):
1064 """common used update sequence"""
1065 """common used update sequence"""
1065 if noupdate:
1066 if noupdate:
1066 return
1067 return
1067 cmdutil.checkunfinished(repo)
1068 cmdutil.checkunfinished(repo)
1068 cmdutil.bailifchanged(repo)
1069 cmdutil.bailifchanged(repo)
1069 return hg.clean(repo, node, show_stats=show_stats)
1070 return hg.clean(repo, node, show_stats=show_stats)
1070
1071
1071 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1072 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1072
1073
1073 if command:
1074 if command:
1074 changesets = 1
1075 changesets = 1
1075 if noupdate:
1076 if noupdate:
1076 try:
1077 try:
1077 node = state[b'current'][0]
1078 node = state[b'current'][0]
1078 except LookupError:
1079 except LookupError:
1079 raise error.StateError(
1080 raise error.StateError(
1080 _(
1081 _(
1081 b'current bisect revision is unknown - '
1082 b'current bisect revision is unknown - '
1082 b'start a new bisect to fix'
1083 b'start a new bisect to fix'
1083 )
1084 )
1084 )
1085 )
1085 else:
1086 else:
1086 node, p2 = repo.dirstate.parents()
1087 node, p2 = repo.dirstate.parents()
1087 if p2 != repo.nullid:
1088 if p2 != repo.nullid:
1088 raise error.StateError(_(b'current bisect revision is a merge'))
1089 raise error.StateError(_(b'current bisect revision is a merge'))
1089 if rev:
1090 if rev:
1090 if not nodes:
1091 if not nodes:
1091 raise error.InputError(_(b'empty revision set'))
1092 raise error.InputError(_(b'empty revision set'))
1092 node = repo[nodes[-1]].node()
1093 node = repo[nodes[-1]].node()
1093 with hbisect.restore_state(repo, state, node):
1094 with hbisect.restore_state(repo, state, node):
1094 while changesets:
1095 while changesets:
1095 # update state
1096 # update state
1096 state[b'current'] = [node]
1097 state[b'current'] = [node]
1097 hbisect.save_state(repo, state)
1098 hbisect.save_state(repo, state)
1098 status = ui.system(
1099 status = ui.system(
1099 command,
1100 command,
1100 environ={b'HG_NODE': hex(node)},
1101 environ={b'HG_NODE': hex(node)},
1101 blockedtag=b'bisect_check',
1102 blockedtag=b'bisect_check',
1102 )
1103 )
1103 if status == 125:
1104 if status == 125:
1104 transition = b"skip"
1105 transition = b"skip"
1105 elif status == 0:
1106 elif status == 0:
1106 transition = b"good"
1107 transition = b"good"
1107 # status < 0 means process was killed
1108 # status < 0 means process was killed
1108 elif status == 127:
1109 elif status == 127:
1109 raise error.Abort(_(b"failed to execute %s") % command)
1110 raise error.Abort(_(b"failed to execute %s") % command)
1110 elif status < 0:
1111 elif status < 0:
1111 raise error.Abort(_(b"%s killed") % command)
1112 raise error.Abort(_(b"%s killed") % command)
1112 else:
1113 else:
1113 transition = b"bad"
1114 transition = b"bad"
1114 state[transition].append(node)
1115 state[transition].append(node)
1115 ctx = repo[node]
1116 ctx = repo[node]
1116 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1117 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1117 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1118 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1118 hbisect.checkstate(state)
1119 hbisect.checkstate(state)
1119 # bisect
1120 # bisect
1120 nodes, changesets, bgood = hbisect.bisect(repo, state)
1121 nodes, changesets, bgood = hbisect.bisect(repo, state)
1121 # update to next check
1122 # update to next check
1122 node = nodes[0]
1123 node = nodes[0]
1123 mayupdate(repo, node, show_stats=False)
1124 mayupdate(repo, node, show_stats=False)
1124 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1125 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1125 return
1126 return
1126
1127
1127 hbisect.checkstate(state)
1128 hbisect.checkstate(state)
1128
1129
1129 # actually bisect
1130 # actually bisect
1130 nodes, changesets, good = hbisect.bisect(repo, state)
1131 nodes, changesets, good = hbisect.bisect(repo, state)
1131 if extend:
1132 if extend:
1132 if not changesets:
1133 if not changesets:
1133 extendctx = hbisect.extendrange(repo, state, nodes, good)
1134 extendctx = hbisect.extendrange(repo, state, nodes, good)
1134 if extendctx is not None:
1135 if extendctx is not None:
1135 ui.write(
1136 ui.write(
1136 _(b"Extending search to changeset %s\n")
1137 _(b"Extending search to changeset %s\n")
1137 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1138 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1138 )
1139 )
1139 state[b'current'] = [extendctx.node()]
1140 state[b'current'] = [extendctx.node()]
1140 hbisect.save_state(repo, state)
1141 hbisect.save_state(repo, state)
1141 return mayupdate(repo, extendctx.node())
1142 return mayupdate(repo, extendctx.node())
1142 raise error.StateError(_(b"nothing to extend"))
1143 raise error.StateError(_(b"nothing to extend"))
1143
1144
1144 if changesets == 0:
1145 if changesets == 0:
1145 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1146 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1146 else:
1147 else:
1147 assert len(nodes) == 1 # only a single node can be tested next
1148 assert len(nodes) == 1 # only a single node can be tested next
1148 node = nodes[0]
1149 node = nodes[0]
1149 # compute the approximate number of remaining tests
1150 # compute the approximate number of remaining tests
1150 tests, size = 0, 2
1151 tests, size = 0, 2
1151 while size <= changesets:
1152 while size <= changesets:
1152 tests, size = tests + 1, size * 2
1153 tests, size = tests + 1, size * 2
1153 rev = repo.changelog.rev(node)
1154 rev = repo.changelog.rev(node)
1154 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1155 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1155 ui.write(
1156 ui.write(
1156 _(
1157 _(
1157 b"Testing changeset %s "
1158 b"Testing changeset %s "
1158 b"(%d changesets remaining, ~%d tests)\n"
1159 b"(%d changesets remaining, ~%d tests)\n"
1159 )
1160 )
1160 % (summary, changesets, tests)
1161 % (summary, changesets, tests)
1161 )
1162 )
1162 state[b'current'] = [node]
1163 state[b'current'] = [node]
1163 hbisect.save_state(repo, state)
1164 hbisect.save_state(repo, state)
1164 return mayupdate(repo, node)
1165 return mayupdate(repo, node)
1165
1166
1166
1167
1167 @command(
1168 @command(
1168 b'bookmarks|bookmark',
1169 b'bookmarks|bookmark',
1169 [
1170 [
1170 (b'f', b'force', False, _(b'force')),
1171 (b'f', b'force', False, _(b'force')),
1171 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1172 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1172 (b'd', b'delete', False, _(b'delete a given bookmark')),
1173 (b'd', b'delete', False, _(b'delete a given bookmark')),
1173 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1174 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1174 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1175 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1175 (b'l', b'list', False, _(b'list existing bookmarks')),
1176 (b'l', b'list', False, _(b'list existing bookmarks')),
1176 ]
1177 ]
1177 + formatteropts,
1178 + formatteropts,
1178 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1179 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1179 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1180 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1180 )
1181 )
1181 def bookmark(ui, repo, *names, **opts):
1182 def bookmark(ui, repo, *names, **opts):
1182 """create a new bookmark or list existing bookmarks
1183 """create a new bookmark or list existing bookmarks
1183
1184
1184 Bookmarks are labels on changesets to help track lines of development.
1185 Bookmarks are labels on changesets to help track lines of development.
1185 Bookmarks are unversioned and can be moved, renamed and deleted.
1186 Bookmarks are unversioned and can be moved, renamed and deleted.
1186 Deleting or moving a bookmark has no effect on the associated changesets.
1187 Deleting or moving a bookmark has no effect on the associated changesets.
1187
1188
1188 Creating or updating to a bookmark causes it to be marked as 'active'.
1189 Creating or updating to a bookmark causes it to be marked as 'active'.
1189 The active bookmark is indicated with a '*'.
1190 The active bookmark is indicated with a '*'.
1190 When a commit is made, the active bookmark will advance to the new commit.
1191 When a commit is made, the active bookmark will advance to the new commit.
1191 A plain :hg:`update` will also advance an active bookmark, if possible.
1192 A plain :hg:`update` will also advance an active bookmark, if possible.
1192 Updating away from a bookmark will cause it to be deactivated.
1193 Updating away from a bookmark will cause it to be deactivated.
1193
1194
1194 Bookmarks can be pushed and pulled between repositories (see
1195 Bookmarks can be pushed and pulled between repositories (see
1195 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1196 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1196 diverged, a new 'divergent bookmark' of the form 'name@path' will
1197 diverged, a new 'divergent bookmark' of the form 'name@path' will
1197 be created. Using :hg:`merge` will resolve the divergence.
1198 be created. Using :hg:`merge` will resolve the divergence.
1198
1199
1199 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1200 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1200 the active bookmark's name.
1201 the active bookmark's name.
1201
1202
1202 A bookmark named '@' has the special property that :hg:`clone` will
1203 A bookmark named '@' has the special property that :hg:`clone` will
1203 check it out by default if it exists.
1204 check it out by default if it exists.
1204
1205
1205 .. container:: verbose
1206 .. container:: verbose
1206
1207
1207 Template:
1208 Template:
1208
1209
1209 The following keywords are supported in addition to the common template
1210 The following keywords are supported in addition to the common template
1210 keywords and functions such as ``{bookmark}``. See also
1211 keywords and functions such as ``{bookmark}``. See also
1211 :hg:`help templates`.
1212 :hg:`help templates`.
1212
1213
1213 :active: Boolean. True if the bookmark is active.
1214 :active: Boolean. True if the bookmark is active.
1214
1215
1215 Examples:
1216 Examples:
1216
1217
1217 - create an active bookmark for a new line of development::
1218 - create an active bookmark for a new line of development::
1218
1219
1219 hg book new-feature
1220 hg book new-feature
1220
1221
1221 - create an inactive bookmark as a place marker::
1222 - create an inactive bookmark as a place marker::
1222
1223
1223 hg book -i reviewed
1224 hg book -i reviewed
1224
1225
1225 - create an inactive bookmark on another changeset::
1226 - create an inactive bookmark on another changeset::
1226
1227
1227 hg book -r .^ tested
1228 hg book -r .^ tested
1228
1229
1229 - rename bookmark turkey to dinner::
1230 - rename bookmark turkey to dinner::
1230
1231
1231 hg book -m turkey dinner
1232 hg book -m turkey dinner
1232
1233
1233 - move the '@' bookmark from another branch::
1234 - move the '@' bookmark from another branch::
1234
1235
1235 hg book -f @
1236 hg book -f @
1236
1237
1237 - print only the active bookmark name::
1238 - print only the active bookmark name::
1238
1239
1239 hg book -ql .
1240 hg book -ql .
1240 """
1241 """
1241 opts = pycompat.byteskwargs(opts)
1242 opts = pycompat.byteskwargs(opts)
1242 force = opts.get(b'force')
1243 force = opts.get(b'force')
1243 rev = opts.get(b'rev')
1244 rev = opts.get(b'rev')
1244 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1245 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1245
1246
1246 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1247 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1247 if action:
1248 if action:
1248 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1249 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1249 elif names or rev:
1250 elif names or rev:
1250 action = b'add'
1251 action = b'add'
1251 elif inactive:
1252 elif inactive:
1252 action = b'inactive' # meaning deactivate
1253 action = b'inactive' # meaning deactivate
1253 else:
1254 else:
1254 action = b'list'
1255 action = b'list'
1255
1256
1256 cmdutil.check_incompatible_arguments(
1257 cmdutil.check_incompatible_arguments(
1257 opts, b'inactive', [b'delete', b'list']
1258 opts, b'inactive', [b'delete', b'list']
1258 )
1259 )
1259 if not names and action in {b'add', b'delete'}:
1260 if not names and action in {b'add', b'delete'}:
1260 raise error.InputError(_(b"bookmark name required"))
1261 raise error.InputError(_(b"bookmark name required"))
1261
1262
1262 if action in {b'add', b'delete', b'rename', b'inactive'}:
1263 if action in {b'add', b'delete', b'rename', b'inactive'}:
1263 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1264 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1264 if action == b'delete':
1265 if action == b'delete':
1265 names = pycompat.maplist(repo._bookmarks.expandname, names)
1266 names = pycompat.maplist(repo._bookmarks.expandname, names)
1266 bookmarks.delete(repo, tr, names)
1267 bookmarks.delete(repo, tr, names)
1267 elif action == b'rename':
1268 elif action == b'rename':
1268 if not names:
1269 if not names:
1269 raise error.InputError(_(b"new bookmark name required"))
1270 raise error.InputError(_(b"new bookmark name required"))
1270 elif len(names) > 1:
1271 elif len(names) > 1:
1271 raise error.InputError(
1272 raise error.InputError(
1272 _(b"only one new bookmark name allowed")
1273 _(b"only one new bookmark name allowed")
1273 )
1274 )
1274 oldname = repo._bookmarks.expandname(opts[b'rename'])
1275 oldname = repo._bookmarks.expandname(opts[b'rename'])
1275 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1276 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1276 elif action == b'add':
1277 elif action == b'add':
1277 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1278 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1278 elif action == b'inactive':
1279 elif action == b'inactive':
1279 if len(repo._bookmarks) == 0:
1280 if len(repo._bookmarks) == 0:
1280 ui.status(_(b"no bookmarks set\n"))
1281 ui.status(_(b"no bookmarks set\n"))
1281 elif not repo._activebookmark:
1282 elif not repo._activebookmark:
1282 ui.status(_(b"no active bookmark\n"))
1283 ui.status(_(b"no active bookmark\n"))
1283 else:
1284 else:
1284 bookmarks.deactivate(repo)
1285 bookmarks.deactivate(repo)
1285 elif action == b'list':
1286 elif action == b'list':
1286 names = pycompat.maplist(repo._bookmarks.expandname, names)
1287 names = pycompat.maplist(repo._bookmarks.expandname, names)
1287 with ui.formatter(b'bookmarks', opts) as fm:
1288 with ui.formatter(b'bookmarks', opts) as fm:
1288 bookmarks.printbookmarks(ui, repo, fm, names)
1289 bookmarks.printbookmarks(ui, repo, fm, names)
1289 else:
1290 else:
1290 raise error.ProgrammingError(b'invalid action: %s' % action)
1291 raise error.ProgrammingError(b'invalid action: %s' % action)
1291
1292
1292
1293
1293 @command(
1294 @command(
1294 b'branch',
1295 b'branch',
1295 [
1296 [
1296 (
1297 (
1297 b'f',
1298 b'f',
1298 b'force',
1299 b'force',
1299 None,
1300 None,
1300 _(b'set branch name even if it shadows an existing branch'),
1301 _(b'set branch name even if it shadows an existing branch'),
1301 ),
1302 ),
1302 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1303 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1303 (
1304 (
1304 b'r',
1305 b'r',
1305 b'rev',
1306 b'rev',
1306 [],
1307 [],
1307 _(b'change branches of the given revs (EXPERIMENTAL)'),
1308 _(b'change branches of the given revs (EXPERIMENTAL)'),
1308 ),
1309 ),
1309 ],
1310 ],
1310 _(b'[-fC] [NAME]'),
1311 _(b'[-fC] [NAME]'),
1311 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1312 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1312 )
1313 )
1313 def branch(ui, repo, label=None, **opts):
1314 def branch(ui, repo, label=None, **opts):
1314 """set or show the current branch name
1315 """set or show the current branch name
1315
1316
1316 .. note::
1317 .. note::
1317
1318
1318 Branch names are permanent and global. Use :hg:`bookmark` to create a
1319 Branch names are permanent and global. Use :hg:`bookmark` to create a
1319 light-weight bookmark instead. See :hg:`help glossary` for more
1320 light-weight bookmark instead. See :hg:`help glossary` for more
1320 information about named branches and bookmarks.
1321 information about named branches and bookmarks.
1321
1322
1322 With no argument, show the current branch name. With one argument,
1323 With no argument, show the current branch name. With one argument,
1323 set the working directory branch name (the branch will not exist
1324 set the working directory branch name (the branch will not exist
1324 in the repository until the next commit). Standard practice
1325 in the repository until the next commit). Standard practice
1325 recommends that primary development take place on the 'default'
1326 recommends that primary development take place on the 'default'
1326 branch.
1327 branch.
1327
1328
1328 Unless -f/--force is specified, branch will not let you set a
1329 Unless -f/--force is specified, branch will not let you set a
1329 branch name that already exists.
1330 branch name that already exists.
1330
1331
1331 Use -C/--clean to reset the working directory branch to that of
1332 Use -C/--clean to reset the working directory branch to that of
1332 the parent of the working directory, negating a previous branch
1333 the parent of the working directory, negating a previous branch
1333 change.
1334 change.
1334
1335
1335 Use the command :hg:`update` to switch to an existing branch. Use
1336 Use the command :hg:`update` to switch to an existing branch. Use
1336 :hg:`commit --close-branch` to mark this branch head as closed.
1337 :hg:`commit --close-branch` to mark this branch head as closed.
1337 When all heads of a branch are closed, the branch will be
1338 When all heads of a branch are closed, the branch will be
1338 considered closed.
1339 considered closed.
1339
1340
1340 Returns 0 on success.
1341 Returns 0 on success.
1341 """
1342 """
1342 opts = pycompat.byteskwargs(opts)
1343 opts = pycompat.byteskwargs(opts)
1343 revs = opts.get(b'rev')
1344 revs = opts.get(b'rev')
1344 if label:
1345 if label:
1345 label = label.strip()
1346 label = label.strip()
1346
1347
1347 if not opts.get(b'clean') and not label:
1348 if not opts.get(b'clean') and not label:
1348 if revs:
1349 if revs:
1349 raise error.InputError(
1350 raise error.InputError(
1350 _(b"no branch name specified for the revisions")
1351 _(b"no branch name specified for the revisions")
1351 )
1352 )
1352 ui.write(b"%s\n" % repo.dirstate.branch())
1353 ui.write(b"%s\n" % repo.dirstate.branch())
1353 return
1354 return
1354
1355
1355 with repo.wlock():
1356 with repo.wlock():
1356 if opts.get(b'clean'):
1357 if opts.get(b'clean'):
1357 label = repo[b'.'].branch()
1358 label = repo[b'.'].branch()
1358 repo.dirstate.setbranch(label, repo.currenttransaction())
1359 repo.dirstate.setbranch(label, repo.currenttransaction())
1359 ui.status(_(b'reset working directory to branch %s\n') % label)
1360 ui.status(_(b'reset working directory to branch %s\n') % label)
1360 elif label:
1361 elif label:
1361
1362
1362 scmutil.checknewlabel(repo, label, b'branch')
1363 scmutil.checknewlabel(repo, label, b'branch')
1363 if revs:
1364 if revs:
1364 return cmdutil.changebranch(ui, repo, revs, label, opts)
1365 return cmdutil.changebranch(ui, repo, revs, label, opts)
1365
1366
1366 if not opts.get(b'force') and label in repo.branchmap():
1367 if not opts.get(b'force') and label in repo.branchmap():
1367 if label not in [p.branch() for p in repo[None].parents()]:
1368 if label not in [p.branch() for p in repo[None].parents()]:
1368 raise error.InputError(
1369 raise error.InputError(
1369 _(b'a branch of the same name already exists'),
1370 _(b'a branch of the same name already exists'),
1370 # i18n: "it" refers to an existing branch
1371 # i18n: "it" refers to an existing branch
1371 hint=_(b"use 'hg update' to switch to it"),
1372 hint=_(b"use 'hg update' to switch to it"),
1372 )
1373 )
1373
1374
1374 repo.dirstate.setbranch(label, repo.currenttransaction())
1375 repo.dirstate.setbranch(label, repo.currenttransaction())
1375 ui.status(_(b'marked working directory as branch %s\n') % label)
1376 ui.status(_(b'marked working directory as branch %s\n') % label)
1376
1377
1377 # find any open named branches aside from default
1378 # find any open named branches aside from default
1378 for n, h, t, c in repo.branchmap().iterbranches():
1379 for n, h, t, c in repo.branchmap().iterbranches():
1379 if n != b"default" and not c:
1380 if n != b"default" and not c:
1380 return 0
1381 return 0
1381 ui.status(
1382 ui.status(
1382 _(
1383 _(
1383 b'(branches are permanent and global, '
1384 b'(branches are permanent and global, '
1384 b'did you want a bookmark?)\n'
1385 b'did you want a bookmark?)\n'
1385 )
1386 )
1386 )
1387 )
1387
1388
1388
1389
1389 @command(
1390 @command(
1390 b'branches',
1391 b'branches',
1391 [
1392 [
1392 (
1393 (
1393 b'a',
1394 b'a',
1394 b'active',
1395 b'active',
1395 False,
1396 False,
1396 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1397 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1397 ),
1398 ),
1398 (b'c', b'closed', False, _(b'show normal and closed branches')),
1399 (b'c', b'closed', False, _(b'show normal and closed branches')),
1399 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1400 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1400 ]
1401 ]
1401 + formatteropts,
1402 + formatteropts,
1402 _(b'[-c]'),
1403 _(b'[-c]'),
1403 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1404 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1404 intents={INTENT_READONLY},
1405 intents={INTENT_READONLY},
1405 )
1406 )
1406 def branches(ui, repo, active=False, closed=False, **opts):
1407 def branches(ui, repo, active=False, closed=False, **opts):
1407 """list repository named branches
1408 """list repository named branches
1408
1409
1409 List the repository's named branches, indicating which ones are
1410 List the repository's named branches, indicating which ones are
1410 inactive. If -c/--closed is specified, also list branches which have
1411 inactive. If -c/--closed is specified, also list branches which have
1411 been marked closed (see :hg:`commit --close-branch`).
1412 been marked closed (see :hg:`commit --close-branch`).
1412
1413
1413 Use the command :hg:`update` to switch to an existing branch.
1414 Use the command :hg:`update` to switch to an existing branch.
1414
1415
1415 .. container:: verbose
1416 .. container:: verbose
1416
1417
1417 Template:
1418 Template:
1418
1419
1419 The following keywords are supported in addition to the common template
1420 The following keywords are supported in addition to the common template
1420 keywords and functions such as ``{branch}``. See also
1421 keywords and functions such as ``{branch}``. See also
1421 :hg:`help templates`.
1422 :hg:`help templates`.
1422
1423
1423 :active: Boolean. True if the branch is active.
1424 :active: Boolean. True if the branch is active.
1424 :closed: Boolean. True if the branch is closed.
1425 :closed: Boolean. True if the branch is closed.
1425 :current: Boolean. True if it is the current branch.
1426 :current: Boolean. True if it is the current branch.
1426
1427
1427 Returns 0.
1428 Returns 0.
1428 """
1429 """
1429
1430
1430 opts = pycompat.byteskwargs(opts)
1431 opts = pycompat.byteskwargs(opts)
1431 revs = opts.get(b'rev')
1432 revs = opts.get(b'rev')
1432 selectedbranches = None
1433 selectedbranches = None
1433 if revs:
1434 if revs:
1434 revs = logcmdutil.revrange(repo, revs)
1435 revs = logcmdutil.revrange(repo, revs)
1435 getbi = repo.revbranchcache().branchinfo
1436 getbi = repo.revbranchcache().branchinfo
1436 selectedbranches = {getbi(r)[0] for r in revs}
1437 selectedbranches = {getbi(r)[0] for r in revs}
1437
1438
1438 ui.pager(b'branches')
1439 ui.pager(b'branches')
1439 fm = ui.formatter(b'branches', opts)
1440 fm = ui.formatter(b'branches', opts)
1440 hexfunc = fm.hexfunc
1441 hexfunc = fm.hexfunc
1441
1442
1442 allheads = set(repo.heads())
1443 allheads = set(repo.heads())
1443 branches = []
1444 branches = []
1444 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1445 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1445 if selectedbranches is not None and tag not in selectedbranches:
1446 if selectedbranches is not None and tag not in selectedbranches:
1446 continue
1447 continue
1447 isactive = False
1448 isactive = False
1448 if not isclosed:
1449 if not isclosed:
1449 openheads = set(repo.branchmap().iteropen(heads))
1450 openheads = set(repo.branchmap().iteropen(heads))
1450 isactive = bool(openheads & allheads)
1451 isactive = bool(openheads & allheads)
1451 branches.append((tag, repo[tip], isactive, not isclosed))
1452 branches.append((tag, repo[tip], isactive, not isclosed))
1452 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1453 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1453
1454
1454 for tag, ctx, isactive, isopen in branches:
1455 for tag, ctx, isactive, isopen in branches:
1455 if active and not isactive:
1456 if active and not isactive:
1456 continue
1457 continue
1457 if isactive:
1458 if isactive:
1458 label = b'branches.active'
1459 label = b'branches.active'
1459 notice = b''
1460 notice = b''
1460 elif not isopen:
1461 elif not isopen:
1461 if not closed:
1462 if not closed:
1462 continue
1463 continue
1463 label = b'branches.closed'
1464 label = b'branches.closed'
1464 notice = _(b' (closed)')
1465 notice = _(b' (closed)')
1465 else:
1466 else:
1466 label = b'branches.inactive'
1467 label = b'branches.inactive'
1467 notice = _(b' (inactive)')
1468 notice = _(b' (inactive)')
1468 current = tag == repo.dirstate.branch()
1469 current = tag == repo.dirstate.branch()
1469 if current:
1470 if current:
1470 label = b'branches.current'
1471 label = b'branches.current'
1471
1472
1472 fm.startitem()
1473 fm.startitem()
1473 fm.write(b'branch', b'%s', tag, label=label)
1474 fm.write(b'branch', b'%s', tag, label=label)
1474 rev = ctx.rev()
1475 rev = ctx.rev()
1475 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1476 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1476 fmt = b' ' * padsize + b' %d:%s'
1477 fmt = b' ' * padsize + b' %d:%s'
1477 fm.condwrite(
1478 fm.condwrite(
1478 not ui.quiet,
1479 not ui.quiet,
1479 b'rev node',
1480 b'rev node',
1480 fmt,
1481 fmt,
1481 rev,
1482 rev,
1482 hexfunc(ctx.node()),
1483 hexfunc(ctx.node()),
1483 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1484 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1484 )
1485 )
1485 fm.context(ctx=ctx)
1486 fm.context(ctx=ctx)
1486 fm.data(active=isactive, closed=not isopen, current=current)
1487 fm.data(active=isactive, closed=not isopen, current=current)
1487 if not ui.quiet:
1488 if not ui.quiet:
1488 fm.plain(notice)
1489 fm.plain(notice)
1489 fm.plain(b'\n')
1490 fm.plain(b'\n')
1490 fm.end()
1491 fm.end()
1491
1492
1492
1493
1493 @command(
1494 @command(
1494 b'bundle',
1495 b'bundle',
1495 [
1496 [
1496 (
1497 (
1497 b'',
1498 b'',
1498 b'exact',
1499 b'exact',
1499 None,
1500 None,
1500 _(b'compute the base from the revision specified'),
1501 _(b'compute the base from the revision specified'),
1501 ),
1502 ),
1502 (
1503 (
1503 b'f',
1504 b'f',
1504 b'force',
1505 b'force',
1505 None,
1506 None,
1506 _(b'run even when the destination is unrelated'),
1507 _(b'run even when the destination is unrelated'),
1507 ),
1508 ),
1508 (
1509 (
1509 b'r',
1510 b'r',
1510 b'rev',
1511 b'rev',
1511 [],
1512 [],
1512 _(b'a changeset intended to be added to the destination'),
1513 _(b'a changeset intended to be added to the destination'),
1513 _(b'REV'),
1514 _(b'REV'),
1514 ),
1515 ),
1515 (
1516 (
1516 b'b',
1517 b'b',
1517 b'branch',
1518 b'branch',
1518 [],
1519 [],
1519 _(b'a specific branch you would like to bundle'),
1520 _(b'a specific branch you would like to bundle'),
1520 _(b'BRANCH'),
1521 _(b'BRANCH'),
1521 ),
1522 ),
1522 (
1523 (
1523 b'',
1524 b'',
1524 b'base',
1525 b'base',
1525 [],
1526 [],
1526 _(b'a base changeset assumed to be available at the destination'),
1527 _(b'a base changeset assumed to be available at the destination'),
1527 _(b'REV'),
1528 _(b'REV'),
1528 ),
1529 ),
1529 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1530 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1530 (
1531 (
1531 b't',
1532 b't',
1532 b'type',
1533 b'type',
1533 b'bzip2',
1534 b'bzip2',
1534 _(b'bundle compression type to use'),
1535 _(b'bundle compression type to use'),
1535 _(b'TYPE'),
1536 _(b'TYPE'),
1536 ),
1537 ),
1537 ]
1538 ]
1538 + remoteopts,
1539 + remoteopts,
1539 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1540 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1540 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1541 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1541 )
1542 )
1542 def bundle(ui, repo, fname, *dests, **opts):
1543 def bundle(ui, repo, fname, *dests, **opts):
1543 """create a bundle file
1544 """create a bundle file
1544
1545
1545 Generate a bundle file containing data to be transferred to another
1546 Generate a bundle file containing data to be transferred to another
1546 repository.
1547 repository.
1547
1548
1548 To create a bundle containing all changesets, use -a/--all
1549 To create a bundle containing all changesets, use -a/--all
1549 (or --base null). Otherwise, hg assumes the destination will have
1550 (or --base null). Otherwise, hg assumes the destination will have
1550 all the nodes you specify with --base parameters. Otherwise, hg
1551 all the nodes you specify with --base parameters. Otherwise, hg
1551 will assume the repository has all the nodes in destination, or
1552 will assume the repository has all the nodes in destination, or
1552 default-push/default if no destination is specified, where destination
1553 default-push/default if no destination is specified, where destination
1553 is the repositories you provide through DEST option.
1554 is the repositories you provide through DEST option.
1554
1555
1555 You can change bundle format with the -t/--type option. See
1556 You can change bundle format with the -t/--type option. See
1556 :hg:`help bundlespec` for documentation on this format. By default,
1557 :hg:`help bundlespec` for documentation on this format. By default,
1557 the most appropriate format is used and compression defaults to
1558 the most appropriate format is used and compression defaults to
1558 bzip2.
1559 bzip2.
1559
1560
1560 The bundle file can then be transferred using conventional means
1561 The bundle file can then be transferred using conventional means
1561 and applied to another repository with the unbundle or pull
1562 and applied to another repository with the unbundle or pull
1562 command. This is useful when direct push and pull are not
1563 command. This is useful when direct push and pull are not
1563 available or when exporting an entire repository is undesirable.
1564 available or when exporting an entire repository is undesirable.
1564
1565
1565 Applying bundles preserves all changeset contents including
1566 Applying bundles preserves all changeset contents including
1566 permissions, copy/rename information, and revision history.
1567 permissions, copy/rename information, and revision history.
1567
1568
1568 Returns 0 on success, 1 if no changes found.
1569 Returns 0 on success, 1 if no changes found.
1569 """
1570 """
1570 opts = pycompat.byteskwargs(opts)
1571 opts = pycompat.byteskwargs(opts)
1571
1572
1572 revs = None
1573 revs = None
1573 if b'rev' in opts:
1574 if b'rev' in opts:
1574 revstrings = opts[b'rev']
1575 revstrings = opts[b'rev']
1575 revs = logcmdutil.revrange(repo, revstrings)
1576 revs = logcmdutil.revrange(repo, revstrings)
1576 if revstrings and not revs:
1577 if revstrings and not revs:
1577 raise error.InputError(_(b'no commits to bundle'))
1578 raise error.InputError(_(b'no commits to bundle'))
1578
1579
1579 bundletype = opts.get(b'type', b'bzip2').lower()
1580 bundletype = opts.get(b'type', b'bzip2').lower()
1580 try:
1581 try:
1581 bundlespec = bundlecaches.parsebundlespec(
1582 bundlespec = bundlecaches.parsebundlespec(
1582 repo, bundletype, strict=False
1583 repo, bundletype, strict=False
1583 )
1584 )
1584 except error.UnsupportedBundleSpecification as e:
1585 except error.UnsupportedBundleSpecification as e:
1585 raise error.InputError(
1586 raise error.InputError(
1586 pycompat.bytestr(e),
1587 pycompat.bytestr(e),
1587 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1588 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1588 )
1589 )
1589 cgversion = bundlespec.params[b"cg.version"]
1590 cgversion = bundlespec.params[b"cg.version"]
1590
1591
1591 # Packed bundles are a pseudo bundle format for now.
1592 # Packed bundles are a pseudo bundle format for now.
1592 if cgversion == b's1':
1593 if cgversion == b's1':
1593 raise error.InputError(
1594 raise error.InputError(
1594 _(b'packed bundles cannot be produced by "hg bundle"'),
1595 _(b'packed bundles cannot be produced by "hg bundle"'),
1595 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1596 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1596 )
1597 )
1597
1598
1598 if opts.get(b'all'):
1599 if opts.get(b'all'):
1599 if dests:
1600 if dests:
1600 raise error.InputError(
1601 raise error.InputError(
1601 _(b"--all is incompatible with specifying destinations")
1602 _(b"--all is incompatible with specifying destinations")
1602 )
1603 )
1603 if opts.get(b'base'):
1604 if opts.get(b'base'):
1604 ui.warn(_(b"ignoring --base because --all was specified\n"))
1605 ui.warn(_(b"ignoring --base because --all was specified\n"))
1605 if opts.get(b'exact'):
1606 if opts.get(b'exact'):
1606 ui.warn(_(b"ignoring --exact because --all was specified\n"))
1607 ui.warn(_(b"ignoring --exact because --all was specified\n"))
1607 base = [nullrev]
1608 base = [nullrev]
1608 elif opts.get(b'exact'):
1609 elif opts.get(b'exact'):
1609 if dests:
1610 if dests:
1610 raise error.InputError(
1611 raise error.InputError(
1611 _(b"--exact is incompatible with specifying destinations")
1612 _(b"--exact is incompatible with specifying destinations")
1612 )
1613 )
1613 if opts.get(b'base'):
1614 if opts.get(b'base'):
1614 ui.warn(_(b"ignoring --base because --exact was specified\n"))
1615 ui.warn(_(b"ignoring --base because --exact was specified\n"))
1615 base = repo.revs(b'parents(%ld) - %ld', revs, revs)
1616 base = repo.revs(b'parents(%ld) - %ld', revs, revs)
1616 if not base:
1617 if not base:
1617 base = [nullrev]
1618 base = [nullrev]
1618 else:
1619 else:
1619 base = logcmdutil.revrange(repo, opts.get(b'base'))
1620 base = logcmdutil.revrange(repo, opts.get(b'base'))
1620 if cgversion not in changegroup.supportedoutgoingversions(repo):
1621 if cgversion not in changegroup.supportedoutgoingversions(repo):
1621 raise error.Abort(
1622 raise error.Abort(
1622 _(b"repository does not support bundle version %s") % cgversion
1623 _(b"repository does not support bundle version %s") % cgversion
1623 )
1624 )
1624
1625
1625 if base:
1626 if base:
1626 if dests:
1627 if dests:
1627 raise error.InputError(
1628 raise error.InputError(
1628 _(b"--base is incompatible with specifying destinations")
1629 _(b"--base is incompatible with specifying destinations")
1629 )
1630 )
1630 cl = repo.changelog
1631 cl = repo.changelog
1631 common = [cl.node(rev) for rev in base]
1632 common = [cl.node(rev) for rev in base]
1632 heads = [cl.node(r) for r in revs] if revs else None
1633 heads = [cl.node(r) for r in revs] if revs else None
1633 outgoing = discovery.outgoing(repo, common, heads)
1634 outgoing = discovery.outgoing(repo, common, heads)
1634 missing = outgoing.missing
1635 missing = outgoing.missing
1635 excluded = outgoing.excluded
1636 excluded = outgoing.excluded
1636 else:
1637 else:
1637 missing = set()
1638 missing = set()
1638 excluded = set()
1639 excluded = set()
1639 for path in urlutil.get_push_paths(repo, ui, dests):
1640 for path in urlutil.get_push_paths(repo, ui, dests):
1640 other = hg.peer(repo, opts, path)
1641 other = hg.peer(repo, opts, path)
1641 if revs is not None:
1642 if revs is not None:
1642 hex_revs = [repo[r].hex() for r in revs]
1643 hex_revs = [repo[r].hex() for r in revs]
1643 else:
1644 else:
1644 hex_revs = None
1645 hex_revs = None
1645 branches = (path.branch, [])
1646 branches = (path.branch, [])
1646 head_revs, checkout = hg.addbranchrevs(
1647 head_revs, checkout = hg.addbranchrevs(
1647 repo, repo, branches, hex_revs
1648 repo, repo, branches, hex_revs
1648 )
1649 )
1649 heads = (
1650 heads = (
1650 head_revs
1651 head_revs
1651 and pycompat.maplist(repo.lookup, head_revs)
1652 and pycompat.maplist(repo.lookup, head_revs)
1652 or head_revs
1653 or head_revs
1653 )
1654 )
1654 outgoing = discovery.findcommonoutgoing(
1655 outgoing = discovery.findcommonoutgoing(
1655 repo,
1656 repo,
1656 other,
1657 other,
1657 onlyheads=heads,
1658 onlyheads=heads,
1658 force=opts.get(b'force'),
1659 force=opts.get(b'force'),
1659 portable=True,
1660 portable=True,
1660 )
1661 )
1661 missing.update(outgoing.missing)
1662 missing.update(outgoing.missing)
1662 excluded.update(outgoing.excluded)
1663 excluded.update(outgoing.excluded)
1663
1664
1664 if not missing:
1665 if not missing:
1665 scmutil.nochangesfound(ui, repo, not base and excluded)
1666 scmutil.nochangesfound(ui, repo, not base and excluded)
1666 return 1
1667 return 1
1667
1668
1668 # internal changeset are internal implementation details that should not
1669 # internal changeset are internal implementation details that should not
1669 # leave the repository. Bundling with `hg bundle` create such risk.
1670 # leave the repository. Bundling with `hg bundle` create such risk.
1670 bundled_internal = repo.revs(b"%ln and _internal()", missing)
1671 bundled_internal = repo.revs(b"%ln and _internal()", missing)
1671 if bundled_internal:
1672 if bundled_internal:
1672 msg = _(b"cannot bundle internal changesets")
1673 msg = _(b"cannot bundle internal changesets")
1673 hint = _(b"%d internal changesets selected") % len(bundled_internal)
1674 hint = _(b"%d internal changesets selected") % len(bundled_internal)
1674 raise error.Abort(msg, hint=hint)
1675 raise error.Abort(msg, hint=hint)
1675
1676
1676 if heads:
1677 if heads:
1677 outgoing = discovery.outgoing(
1678 outgoing = discovery.outgoing(
1678 repo, missingroots=missing, ancestorsof=heads
1679 repo, missingroots=missing, ancestorsof=heads
1679 )
1680 )
1680 else:
1681 else:
1681 outgoing = discovery.outgoing(repo, missingroots=missing)
1682 outgoing = discovery.outgoing(repo, missingroots=missing)
1682 outgoing.excluded = sorted(excluded)
1683 outgoing.excluded = sorted(excluded)
1683
1684
1684 if cgversion == b'01': # bundle1
1685 if cgversion == b'01': # bundle1
1685 bversion = b'HG10' + bundlespec.wirecompression
1686 bversion = b'HG10' + bundlespec.wirecompression
1686 bcompression = None
1687 bcompression = None
1687 elif cgversion in (b'02', b'03'):
1688 elif cgversion in (b'02', b'03'):
1688 bversion = b'HG20'
1689 bversion = b'HG20'
1689 bcompression = bundlespec.wirecompression
1690 bcompression = bundlespec.wirecompression
1690 else:
1691 else:
1691 raise error.ProgrammingError(
1692 raise error.ProgrammingError(
1692 b'bundle: unexpected changegroup version %s' % cgversion
1693 b'bundle: unexpected changegroup version %s' % cgversion
1693 )
1694 )
1694
1695
1695 # TODO compression options should be derived from bundlespec parsing.
1696 # TODO compression options should be derived from bundlespec parsing.
1696 # This is a temporary hack to allow adjusting bundle compression
1697 # This is a temporary hack to allow adjusting bundle compression
1697 # level without a) formalizing the bundlespec changes to declare it
1698 # level without a) formalizing the bundlespec changes to declare it
1698 # b) introducing a command flag.
1699 # b) introducing a command flag.
1699 compopts = {}
1700 compopts = {}
1700 complevel = ui.configint(
1701 complevel = ui.configint(
1701 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1702 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1702 )
1703 )
1703 if complevel is None:
1704 if complevel is None:
1704 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1705 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1705 if complevel is not None:
1706 if complevel is not None:
1706 compopts[b'level'] = complevel
1707 compopts[b'level'] = complevel
1707
1708
1708 compthreads = ui.configint(
1709 compthreads = ui.configint(
1709 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1710 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1710 )
1711 )
1711 if compthreads is None:
1712 if compthreads is None:
1712 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1713 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1713 if compthreads is not None:
1714 if compthreads is not None:
1714 compopts[b'threads'] = compthreads
1715 compopts[b'threads'] = compthreads
1715
1716
1716 # Bundling of obsmarker and phases is optional as not all clients
1717 # Bundling of obsmarker and phases is optional as not all clients
1717 # support the necessary features.
1718 # support the necessary features.
1718 cfg = ui.configbool
1719 cfg = ui.configbool
1719 obsolescence_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker')
1720 obsolescence_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker')
1720 bundlespec.set_param(b'obsolescence', obsolescence_cfg, overwrite=False)
1721 bundlespec.set_param(b'obsolescence', obsolescence_cfg, overwrite=False)
1721 obs_mand_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker:mandatory')
1722 obs_mand_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker:mandatory')
1722 bundlespec.set_param(
1723 bundlespec.set_param(
1723 b'obsolescence-mandatory', obs_mand_cfg, overwrite=False
1724 b'obsolescence-mandatory', obs_mand_cfg, overwrite=False
1724 )
1725 )
1725 if not bundlespec.params.get(b'phases', False):
1726 if not bundlespec.params.get(b'phases', False):
1726 phases_cfg = cfg(b'experimental', b'bundle-phases')
1727 phases_cfg = cfg(b'experimental', b'bundle-phases')
1727 bundlespec.set_param(b'phases', phases_cfg, overwrite=False)
1728 bundlespec.set_param(b'phases', phases_cfg, overwrite=False)
1728
1729
1729 bundle2.writenewbundle(
1730 bundle2.writenewbundle(
1730 ui,
1731 ui,
1731 repo,
1732 repo,
1732 b'bundle',
1733 b'bundle',
1733 fname,
1734 fname,
1734 bversion,
1735 bversion,
1735 outgoing,
1736 outgoing,
1736 bundlespec.params,
1737 bundlespec.params,
1737 compression=bcompression,
1738 compression=bcompression,
1738 compopts=compopts,
1739 compopts=compopts,
1739 )
1740 )
1740
1741
1741
1742
1742 @command(
1743 @command(
1743 b'cat',
1744 b'cat',
1744 [
1745 [
1745 (
1746 (
1746 b'o',
1747 b'o',
1747 b'output',
1748 b'output',
1748 b'',
1749 b'',
1749 _(b'print output to file with formatted name'),
1750 _(b'print output to file with formatted name'),
1750 _(b'FORMAT'),
1751 _(b'FORMAT'),
1751 ),
1752 ),
1752 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1753 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1753 (b'', b'decode', None, _(b'apply any matching decode filter')),
1754 (b'', b'decode', None, _(b'apply any matching decode filter')),
1754 ]
1755 ]
1755 + walkopts
1756 + walkopts
1756 + formatteropts,
1757 + formatteropts,
1757 _(b'[OPTION]... FILE...'),
1758 _(b'[OPTION]... FILE...'),
1758 helpcategory=command.CATEGORY_FILE_CONTENTS,
1759 helpcategory=command.CATEGORY_FILE_CONTENTS,
1759 inferrepo=True,
1760 inferrepo=True,
1760 intents={INTENT_READONLY},
1761 intents={INTENT_READONLY},
1761 )
1762 )
1762 def cat(ui, repo, file1, *pats, **opts):
1763 def cat(ui, repo, file1, *pats, **opts):
1763 """output the current or given revision of files
1764 """output the current or given revision of files
1764
1765
1765 Print the specified files as they were at the given revision. If
1766 Print the specified files as they were at the given revision. If
1766 no revision is given, the parent of the working directory is used.
1767 no revision is given, the parent of the working directory is used.
1767
1768
1768 Output may be to a file, in which case the name of the file is
1769 Output may be to a file, in which case the name of the file is
1769 given using a template string. See :hg:`help templates`. In addition
1770 given using a template string. See :hg:`help templates`. In addition
1770 to the common template keywords, the following formatting rules are
1771 to the common template keywords, the following formatting rules are
1771 supported:
1772 supported:
1772
1773
1773 :``%%``: literal "%" character
1774 :``%%``: literal "%" character
1774 :``%s``: basename of file being printed
1775 :``%s``: basename of file being printed
1775 :``%d``: dirname of file being printed, or '.' if in repository root
1776 :``%d``: dirname of file being printed, or '.' if in repository root
1776 :``%p``: root-relative path name of file being printed
1777 :``%p``: root-relative path name of file being printed
1777 :``%H``: changeset hash (40 hexadecimal digits)
1778 :``%H``: changeset hash (40 hexadecimal digits)
1778 :``%R``: changeset revision number
1779 :``%R``: changeset revision number
1779 :``%h``: short-form changeset hash (12 hexadecimal digits)
1780 :``%h``: short-form changeset hash (12 hexadecimal digits)
1780 :``%r``: zero-padded changeset revision number
1781 :``%r``: zero-padded changeset revision number
1781 :``%b``: basename of the exporting repository
1782 :``%b``: basename of the exporting repository
1782 :``\\``: literal "\\" character
1783 :``\\``: literal "\\" character
1783
1784
1784 .. container:: verbose
1785 .. container:: verbose
1785
1786
1786 Template:
1787 Template:
1787
1788
1788 The following keywords are supported in addition to the common template
1789 The following keywords are supported in addition to the common template
1789 keywords and functions. See also :hg:`help templates`.
1790 keywords and functions. See also :hg:`help templates`.
1790
1791
1791 :data: String. File content.
1792 :data: String. File content.
1792 :path: String. Repository-absolute path of the file.
1793 :path: String. Repository-absolute path of the file.
1793
1794
1794 Returns 0 on success.
1795 Returns 0 on success.
1795 """
1796 """
1796 opts = pycompat.byteskwargs(opts)
1797 opts = pycompat.byteskwargs(opts)
1797 rev = opts.get(b'rev')
1798 rev = opts.get(b'rev')
1798 if rev:
1799 if rev:
1799 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1800 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1800 ctx = logcmdutil.revsingle(repo, rev)
1801 ctx = logcmdutil.revsingle(repo, rev)
1801 m = scmutil.match(ctx, (file1,) + pats, opts)
1802 m = scmutil.match(ctx, (file1,) + pats, opts)
1802 fntemplate = opts.pop(b'output', b'')
1803 fntemplate = opts.pop(b'output', b'')
1803 if cmdutil.isstdiofilename(fntemplate):
1804 if cmdutil.isstdiofilename(fntemplate):
1804 fntemplate = b''
1805 fntemplate = b''
1805
1806
1806 if fntemplate:
1807 if fntemplate:
1807 fm = formatter.nullformatter(ui, b'cat', opts)
1808 fm = formatter.nullformatter(ui, b'cat', opts)
1808 else:
1809 else:
1809 ui.pager(b'cat')
1810 ui.pager(b'cat')
1810 fm = ui.formatter(b'cat', opts)
1811 fm = ui.formatter(b'cat', opts)
1811 with fm:
1812 with fm:
1812 return cmdutil.cat(
1813 return cmdutil.cat(
1813 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1814 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1814 )
1815 )
1815
1816
1816
1817
1817 @command(
1818 @command(
1818 b'clone',
1819 b'clone',
1819 [
1820 [
1820 (
1821 (
1821 b'U',
1822 b'U',
1822 b'noupdate',
1823 b'noupdate',
1823 None,
1824 None,
1824 _(
1825 _(
1825 b'the clone will include an empty working '
1826 b'the clone will include an empty working '
1826 b'directory (only a repository)'
1827 b'directory (only a repository)'
1827 ),
1828 ),
1828 ),
1829 ),
1829 (
1830 (
1830 b'u',
1831 b'u',
1831 b'updaterev',
1832 b'updaterev',
1832 b'',
1833 b'',
1833 _(b'revision, tag, or branch to check out'),
1834 _(b'revision, tag, or branch to check out'),
1834 _(b'REV'),
1835 _(b'REV'),
1835 ),
1836 ),
1836 (
1837 (
1837 b'r',
1838 b'r',
1838 b'rev',
1839 b'rev',
1839 [],
1840 [],
1840 _(
1841 _(
1841 b'do not clone everything, but include this changeset'
1842 b'do not clone everything, but include this changeset'
1842 b' and its ancestors'
1843 b' and its ancestors'
1843 ),
1844 ),
1844 _(b'REV'),
1845 _(b'REV'),
1845 ),
1846 ),
1846 (
1847 (
1847 b'b',
1848 b'b',
1848 b'branch',
1849 b'branch',
1849 [],
1850 [],
1850 _(
1851 _(
1851 b'do not clone everything, but include this branch\'s'
1852 b'do not clone everything, but include this branch\'s'
1852 b' changesets and their ancestors'
1853 b' changesets and their ancestors'
1853 ),
1854 ),
1854 _(b'BRANCH'),
1855 _(b'BRANCH'),
1855 ),
1856 ),
1856 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1857 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1857 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1858 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1858 (b'', b'stream', None, _(b'clone with minimal data processing')),
1859 (b'', b'stream', None, _(b'clone with minimal data processing')),
1859 ]
1860 ]
1860 + remoteopts,
1861 + remoteopts,
1861 _(b'[OPTION]... SOURCE [DEST]'),
1862 _(b'[OPTION]... SOURCE [DEST]'),
1862 helpcategory=command.CATEGORY_REPO_CREATION,
1863 helpcategory=command.CATEGORY_REPO_CREATION,
1863 helpbasic=True,
1864 helpbasic=True,
1864 norepo=True,
1865 norepo=True,
1865 )
1866 )
1866 def clone(ui, source, dest=None, **opts):
1867 def clone(ui, source, dest=None, **opts):
1867 """make a copy of an existing repository
1868 """make a copy of an existing repository
1868
1869
1869 Create a copy of an existing repository in a new directory.
1870 Create a copy of an existing repository in a new directory.
1870
1871
1871 If no destination directory name is specified, it defaults to the
1872 If no destination directory name is specified, it defaults to the
1872 basename of the source.
1873 basename of the source.
1873
1874
1874 The location of the source is added to the new repository's
1875 The location of the source is added to the new repository's
1875 ``.hg/hgrc`` file, as the default to be used for future pulls.
1876 ``.hg/hgrc`` file, as the default to be used for future pulls.
1876
1877
1877 Only local paths and ``ssh://`` URLs are supported as
1878 Only local paths and ``ssh://`` URLs are supported as
1878 destinations. For ``ssh://`` destinations, no working directory or
1879 destinations. For ``ssh://`` destinations, no working directory or
1879 ``.hg/hgrc`` will be created on the remote side.
1880 ``.hg/hgrc`` will be created on the remote side.
1880
1881
1881 If the source repository has a bookmark called '@' set, that
1882 If the source repository has a bookmark called '@' set, that
1882 revision will be checked out in the new repository by default.
1883 revision will be checked out in the new repository by default.
1883
1884
1884 To check out a particular version, use -u/--update, or
1885 To check out a particular version, use -u/--update, or
1885 -U/--noupdate to create a clone with no working directory.
1886 -U/--noupdate to create a clone with no working directory.
1886
1887
1887 To pull only a subset of changesets, specify one or more revisions
1888 To pull only a subset of changesets, specify one or more revisions
1888 identifiers with -r/--rev or branches with -b/--branch. The
1889 identifiers with -r/--rev or branches with -b/--branch. The
1889 resulting clone will contain only the specified changesets and
1890 resulting clone will contain only the specified changesets and
1890 their ancestors. These options (or 'clone src#rev dest') imply
1891 their ancestors. These options (or 'clone src#rev dest') imply
1891 --pull, even for local source repositories.
1892 --pull, even for local source repositories.
1892
1893
1893 In normal clone mode, the remote normalizes repository data into a common
1894 In normal clone mode, the remote normalizes repository data into a common
1894 exchange format and the receiving end translates this data into its local
1895 exchange format and the receiving end translates this data into its local
1895 storage format. --stream activates a different clone mode that essentially
1896 storage format. --stream activates a different clone mode that essentially
1896 copies repository files from the remote with minimal data processing. This
1897 copies repository files from the remote with minimal data processing. This
1897 significantly reduces the CPU cost of a clone both remotely and locally.
1898 significantly reduces the CPU cost of a clone both remotely and locally.
1898 However, it often increases the transferred data size by 30-40%. This can
1899 However, it often increases the transferred data size by 30-40%. This can
1899 result in substantially faster clones where I/O throughput is plentiful,
1900 result in substantially faster clones where I/O throughput is plentiful,
1900 especially for larger repositories. A side-effect of --stream clones is
1901 especially for larger repositories. A side-effect of --stream clones is
1901 that storage settings and requirements on the remote are applied locally:
1902 that storage settings and requirements on the remote are applied locally:
1902 a modern client may inherit legacy or inefficient storage used by the
1903 a modern client may inherit legacy or inefficient storage used by the
1903 remote or a legacy Mercurial client may not be able to clone from a
1904 remote or a legacy Mercurial client may not be able to clone from a
1904 modern Mercurial remote.
1905 modern Mercurial remote.
1905
1906
1906 .. note::
1907 .. note::
1907
1908
1908 Specifying a tag will include the tagged changeset but not the
1909 Specifying a tag will include the tagged changeset but not the
1909 changeset containing the tag.
1910 changeset containing the tag.
1910
1911
1911 .. container:: verbose
1912 .. container:: verbose
1912
1913
1913 For efficiency, hardlinks are used for cloning whenever the
1914 For efficiency, hardlinks are used for cloning whenever the
1914 source and destination are on the same filesystem (note this
1915 source and destination are on the same filesystem (note this
1915 applies only to the repository data, not to the working
1916 applies only to the repository data, not to the working
1916 directory). Some filesystems, such as AFS, implement hardlinking
1917 directory). Some filesystems, such as AFS, implement hardlinking
1917 incorrectly, but do not report errors. In these cases, use the
1918 incorrectly, but do not report errors. In these cases, use the
1918 --pull option to avoid hardlinking.
1919 --pull option to avoid hardlinking.
1919
1920
1920 Mercurial will update the working directory to the first applicable
1921 Mercurial will update the working directory to the first applicable
1921 revision from this list:
1922 revision from this list:
1922
1923
1923 a) null if -U or the source repository has no changesets
1924 a) null if -U or the source repository has no changesets
1924 b) if -u . and the source repository is local, the first parent of
1925 b) if -u . and the source repository is local, the first parent of
1925 the source repository's working directory
1926 the source repository's working directory
1926 c) the changeset specified with -u (if a branch name, this means the
1927 c) the changeset specified with -u (if a branch name, this means the
1927 latest head of that branch)
1928 latest head of that branch)
1928 d) the changeset specified with -r
1929 d) the changeset specified with -r
1929 e) the tipmost head specified with -b
1930 e) the tipmost head specified with -b
1930 f) the tipmost head specified with the url#branch source syntax
1931 f) the tipmost head specified with the url#branch source syntax
1931 g) the revision marked with the '@' bookmark, if present
1932 g) the revision marked with the '@' bookmark, if present
1932 h) the tipmost head of the default branch
1933 h) the tipmost head of the default branch
1933 i) tip
1934 i) tip
1934
1935
1935 When cloning from servers that support it, Mercurial may fetch
1936 When cloning from servers that support it, Mercurial may fetch
1936 pre-generated data from a server-advertised URL or inline from the
1937 pre-generated data from a server-advertised URL or inline from the
1937 same stream. When this is done, hooks operating on incoming changesets
1938 same stream. When this is done, hooks operating on incoming changesets
1938 and changegroups may fire more than once, once for each pre-generated
1939 and changegroups may fire more than once, once for each pre-generated
1939 bundle and as well as for any additional remaining data. In addition,
1940 bundle and as well as for any additional remaining data. In addition,
1940 if an error occurs, the repository may be rolled back to a partial
1941 if an error occurs, the repository may be rolled back to a partial
1941 clone. This behavior may change in future releases.
1942 clone. This behavior may change in future releases.
1942 See :hg:`help -e clonebundles` for more.
1943 See :hg:`help -e clonebundles` for more.
1943
1944
1944 Examples:
1945 Examples:
1945
1946
1946 - clone a remote repository to a new directory named hg/::
1947 - clone a remote repository to a new directory named hg/::
1947
1948
1948 hg clone https://www.mercurial-scm.org/repo/hg/
1949 hg clone https://www.mercurial-scm.org/repo/hg/
1949
1950
1950 - create a lightweight local clone::
1951 - create a lightweight local clone::
1951
1952
1952 hg clone project/ project-feature/
1953 hg clone project/ project-feature/
1953
1954
1954 - clone from an absolute path on an ssh server (note double-slash)::
1955 - clone from an absolute path on an ssh server (note double-slash)::
1955
1956
1956 hg clone ssh://user@server//home/projects/alpha/
1957 hg clone ssh://user@server//home/projects/alpha/
1957
1958
1958 - do a streaming clone while checking out a specified version::
1959 - do a streaming clone while checking out a specified version::
1959
1960
1960 hg clone --stream http://server/repo -u 1.5
1961 hg clone --stream http://server/repo -u 1.5
1961
1962
1962 - create a repository without changesets after a particular revision::
1963 - create a repository without changesets after a particular revision::
1963
1964
1964 hg clone -r 04e544 experimental/ good/
1965 hg clone -r 04e544 experimental/ good/
1965
1966
1966 - clone (and track) a particular named branch::
1967 - clone (and track) a particular named branch::
1967
1968
1968 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1969 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1969
1970
1970 See :hg:`help urls` for details on specifying URLs.
1971 See :hg:`help urls` for details on specifying URLs.
1971
1972
1972 Returns 0 on success.
1973 Returns 0 on success.
1973 """
1974 """
1974 opts = pycompat.byteskwargs(opts)
1975 opts = pycompat.byteskwargs(opts)
1975 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1976 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1976
1977
1977 # --include/--exclude can come from narrow or sparse.
1978 # --include/--exclude can come from narrow or sparse.
1978 includepats, excludepats = None, None
1979 includepats, excludepats = None, None
1979
1980
1980 # hg.clone() differentiates between None and an empty set. So make sure
1981 # hg.clone() differentiates between None and an empty set. So make sure
1981 # patterns are sets if narrow is requested without patterns.
1982 # patterns are sets if narrow is requested without patterns.
1982 if opts.get(b'narrow'):
1983 if opts.get(b'narrow'):
1983 includepats = set()
1984 includepats = set()
1984 excludepats = set()
1985 excludepats = set()
1985
1986
1986 if opts.get(b'include'):
1987 if opts.get(b'include'):
1987 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1988 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1988 if opts.get(b'exclude'):
1989 if opts.get(b'exclude'):
1989 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1990 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1990
1991
1991 r = hg.clone(
1992 r = hg.clone(
1992 ui,
1993 ui,
1993 opts,
1994 opts,
1994 source,
1995 source,
1995 dest,
1996 dest,
1996 pull=opts.get(b'pull'),
1997 pull=opts.get(b'pull'),
1997 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1998 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1998 revs=opts.get(b'rev'),
1999 revs=opts.get(b'rev'),
1999 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
2000 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
2000 branch=opts.get(b'branch'),
2001 branch=opts.get(b'branch'),
2001 shareopts=opts.get(b'shareopts'),
2002 shareopts=opts.get(b'shareopts'),
2002 storeincludepats=includepats,
2003 storeincludepats=includepats,
2003 storeexcludepats=excludepats,
2004 storeexcludepats=excludepats,
2004 depth=opts.get(b'depth') or None,
2005 depth=opts.get(b'depth') or None,
2005 )
2006 )
2006
2007
2007 return r is None
2008 return r is None
2008
2009
2009
2010
2010 @command(
2011 @command(
2011 b'commit|ci',
2012 b'commit|ci',
2012 [
2013 [
2013 (
2014 (
2014 b'A',
2015 b'A',
2015 b'addremove',
2016 b'addremove',
2016 None,
2017 None,
2017 _(b'mark new/missing files as added/removed before committing'),
2018 _(b'mark new/missing files as added/removed before committing'),
2018 ),
2019 ),
2019 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
2020 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
2020 (b'', b'amend', None, _(b'amend the parent of the working directory')),
2021 (b'', b'amend', None, _(b'amend the parent of the working directory')),
2021 (b's', b'secret', None, _(b'use the secret phase for committing')),
2022 (b's', b'secret', None, _(b'use the secret phase for committing')),
2022 (b'', b'draft', None, _(b'use the draft phase for committing')),
2023 (b'', b'draft', None, _(b'use the draft phase for committing')),
2023 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
2024 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
2024 (
2025 (
2025 b'',
2026 b'',
2026 b'force-close-branch',
2027 b'force-close-branch',
2027 None,
2028 None,
2028 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
2029 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
2029 ),
2030 ),
2030 (b'i', b'interactive', None, _(b'use interactive mode')),
2031 (b'i', b'interactive', None, _(b'use interactive mode')),
2031 ]
2032 ]
2032 + walkopts
2033 + walkopts
2033 + commitopts
2034 + commitopts
2034 + commitopts2
2035 + commitopts2
2035 + subrepoopts,
2036 + subrepoopts,
2036 _(b'[OPTION]... [FILE]...'),
2037 _(b'[OPTION]... [FILE]...'),
2037 helpcategory=command.CATEGORY_COMMITTING,
2038 helpcategory=command.CATEGORY_COMMITTING,
2038 helpbasic=True,
2039 helpbasic=True,
2039 inferrepo=True,
2040 inferrepo=True,
2040 )
2041 )
2041 def commit(ui, repo, *pats, **opts):
2042 def commit(ui, repo, *pats, **opts):
2042 """commit the specified files or all outstanding changes
2043 """commit the specified files or all outstanding changes
2043
2044
2044 Commit changes to the given files into the repository. Unlike a
2045 Commit changes to the given files into the repository. Unlike a
2045 centralized SCM, this operation is a local operation. See
2046 centralized SCM, this operation is a local operation. See
2046 :hg:`push` for a way to actively distribute your changes.
2047 :hg:`push` for a way to actively distribute your changes.
2047
2048
2048 If a list of files is omitted, all changes reported by :hg:`status`
2049 If a list of files is omitted, all changes reported by :hg:`status`
2049 will be committed.
2050 will be committed.
2050
2051
2051 If you are committing the result of a merge, do not provide any
2052 If you are committing the result of a merge, do not provide any
2052 filenames or -I/-X filters.
2053 filenames or -I/-X filters.
2053
2054
2054 If no commit message is specified, Mercurial starts your
2055 If no commit message is specified, Mercurial starts your
2055 configured editor where you can enter a message. In case your
2056 configured editor where you can enter a message. In case your
2056 commit fails, you will find a backup of your message in
2057 commit fails, you will find a backup of your message in
2057 ``.hg/last-message.txt``.
2058 ``.hg/last-message.txt``.
2058
2059
2059 The --close-branch flag can be used to mark the current branch
2060 The --close-branch flag can be used to mark the current branch
2060 head closed. When all heads of a branch are closed, the branch
2061 head closed. When all heads of a branch are closed, the branch
2061 will be considered closed and no longer listed.
2062 will be considered closed and no longer listed.
2062
2063
2063 The --amend flag can be used to amend the parent of the
2064 The --amend flag can be used to amend the parent of the
2064 working directory with a new commit that contains the changes
2065 working directory with a new commit that contains the changes
2065 in the parent in addition to those currently reported by :hg:`status`,
2066 in the parent in addition to those currently reported by :hg:`status`,
2066 if there are any. The old commit is stored in a backup bundle in
2067 if there are any. The old commit is stored in a backup bundle in
2067 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2068 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2068 on how to restore it).
2069 on how to restore it).
2069
2070
2070 Message, user and date are taken from the amended commit unless
2071 Message, user and date are taken from the amended commit unless
2071 specified. When a message isn't specified on the command line,
2072 specified. When a message isn't specified on the command line,
2072 the editor will open with the message of the amended commit.
2073 the editor will open with the message of the amended commit.
2073
2074
2074 It is not possible to amend public changesets (see :hg:`help phases`)
2075 It is not possible to amend public changesets (see :hg:`help phases`)
2075 or changesets that have children.
2076 or changesets that have children.
2076
2077
2077 See :hg:`help dates` for a list of formats valid for -d/--date.
2078 See :hg:`help dates` for a list of formats valid for -d/--date.
2078
2079
2079 Returns 0 on success, 1 if nothing changed.
2080 Returns 0 on success, 1 if nothing changed.
2080
2081
2081 .. container:: verbose
2082 .. container:: verbose
2082
2083
2083 Examples:
2084 Examples:
2084
2085
2085 - commit all files ending in .py::
2086 - commit all files ending in .py::
2086
2087
2087 hg commit --include "set:**.py"
2088 hg commit --include "set:**.py"
2088
2089
2089 - commit all non-binary files::
2090 - commit all non-binary files::
2090
2091
2091 hg commit --exclude "set:binary()"
2092 hg commit --exclude "set:binary()"
2092
2093
2093 - amend the current commit and set the date to now::
2094 - amend the current commit and set the date to now::
2094
2095
2095 hg commit --amend --date now
2096 hg commit --amend --date now
2096 """
2097 """
2097 cmdutil.check_at_most_one_arg(opts, 'draft', 'secret')
2098 cmdutil.check_at_most_one_arg(opts, 'draft', 'secret')
2098 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2099 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2099 with repo.wlock(), repo.lock():
2100 with repo.wlock(), repo.lock():
2100 return _docommit(ui, repo, *pats, **opts)
2101 return _docommit(ui, repo, *pats, **opts)
2101
2102
2102
2103
2103 def _docommit(ui, repo, *pats, **opts):
2104 def _docommit(ui, repo, *pats, **opts):
2104 if opts.get('interactive'):
2105 if opts.get('interactive'):
2105 opts.pop('interactive')
2106 opts.pop('interactive')
2106 ret = cmdutil.dorecord(
2107 ret = cmdutil.dorecord(
2107 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2108 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2108 )
2109 )
2109 # ret can be 0 (no changes to record) or the value returned by
2110 # ret can be 0 (no changes to record) or the value returned by
2110 # commit(), 1 if nothing changed or None on success.
2111 # commit(), 1 if nothing changed or None on success.
2111 return 1 if ret == 0 else ret
2112 return 1 if ret == 0 else ret
2112
2113
2113 if opts.get('subrepos'):
2114 if opts.get('subrepos'):
2114 # Let --subrepos on the command line override config setting.
2115 # Let --subrepos on the command line override config setting.
2115 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2116 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2116
2117
2117 cmdutil.checkunfinished(repo, commit=True)
2118 cmdutil.checkunfinished(repo, commit=True)
2118
2119
2119 branch = repo[None].branch()
2120 branch = repo[None].branch()
2120 bheads = repo.branchheads(branch)
2121 bheads = repo.branchheads(branch)
2121 tip = repo.changelog.tip()
2122 tip = repo.changelog.tip()
2122
2123
2123 extra = {}
2124 extra = {}
2124 if opts.get('close_branch') or opts.get('force_close_branch'):
2125 if opts.get('close_branch') or opts.get('force_close_branch'):
2125 extra[b'close'] = b'1'
2126 extra[b'close'] = b'1'
2126
2127
2127 if repo[b'.'].closesbranch():
2128 if repo[b'.'].closesbranch():
2128 # Not ideal, but let us do an extra status early to prevent early
2129 # Not ideal, but let us do an extra status early to prevent early
2129 # bail out.
2130 # bail out.
2130 matcher = scmutil.match(
2131 matcher = scmutil.match(
2131 repo[None], pats, pycompat.byteskwargs(opts)
2132 repo[None], pats, pycompat.byteskwargs(opts)
2132 )
2133 )
2133 s = repo.status(match=matcher)
2134 s = repo.status(match=matcher)
2134 if s.modified or s.added or s.removed:
2135 if s.modified or s.added or s.removed:
2135 bheads = repo.branchheads(branch, closed=True)
2136 bheads = repo.branchheads(branch, closed=True)
2136 else:
2137 else:
2137 msg = _(b'current revision is already a branch closing head')
2138 msg = _(b'current revision is already a branch closing head')
2138 raise error.InputError(msg)
2139 raise error.InputError(msg)
2139
2140
2140 if not bheads:
2141 if not bheads:
2141 raise error.InputError(
2142 raise error.InputError(
2142 _(b'branch "%s" has no heads to close') % branch
2143 _(b'branch "%s" has no heads to close') % branch
2143 )
2144 )
2144 elif (
2145 elif (
2145 branch == repo[b'.'].branch()
2146 branch == repo[b'.'].branch()
2146 and repo[b'.'].node() not in bheads
2147 and repo[b'.'].node() not in bheads
2147 and not opts.get('force_close_branch')
2148 and not opts.get('force_close_branch')
2148 ):
2149 ):
2149 hint = _(
2150 hint = _(
2150 b'use --force-close-branch to close branch from a non-head'
2151 b'use --force-close-branch to close branch from a non-head'
2151 b' changeset'
2152 b' changeset'
2152 )
2153 )
2153 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2154 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2154 elif opts.get('amend'):
2155 elif opts.get('amend'):
2155 if (
2156 if (
2156 repo[b'.'].p1().branch() != branch
2157 repo[b'.'].p1().branch() != branch
2157 and repo[b'.'].p2().branch() != branch
2158 and repo[b'.'].p2().branch() != branch
2158 ):
2159 ):
2159 raise error.InputError(_(b'can only close branch heads'))
2160 raise error.InputError(_(b'can only close branch heads'))
2160
2161
2161 if opts.get('amend'):
2162 if opts.get('amend'):
2162 if ui.configbool(b'ui', b'commitsubrepos'):
2163 if ui.configbool(b'ui', b'commitsubrepos'):
2163 raise error.InputError(
2164 raise error.InputError(
2164 _(b'cannot amend with ui.commitsubrepos enabled')
2165 _(b'cannot amend with ui.commitsubrepos enabled')
2165 )
2166 )
2166
2167
2167 old = repo[b'.']
2168 old = repo[b'.']
2168 rewriteutil.precheck(repo, [old.rev()], b'amend')
2169 rewriteutil.precheck(repo, [old.rev()], b'amend')
2169
2170
2170 # Currently histedit gets confused if an amend happens while histedit
2171 # Currently histedit gets confused if an amend happens while histedit
2171 # is in progress. Since we have a checkunfinished command, we are
2172 # is in progress. Since we have a checkunfinished command, we are
2172 # temporarily honoring it.
2173 # temporarily honoring it.
2173 #
2174 #
2174 # Note: eventually this guard will be removed. Please do not expect
2175 # Note: eventually this guard will be removed. Please do not expect
2175 # this behavior to remain.
2176 # this behavior to remain.
2176 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2177 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2177 cmdutil.checkunfinished(repo)
2178 cmdutil.checkunfinished(repo)
2178
2179
2179 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2180 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2180 opts = pycompat.byteskwargs(opts)
2181 opts = pycompat.byteskwargs(opts)
2181 if node == old.node():
2182 if node == old.node():
2182 ui.status(_(b"nothing changed\n"))
2183 ui.status(_(b"nothing changed\n"))
2183 return 1
2184 return 1
2184 else:
2185 else:
2185
2186
2186 def commitfunc(ui, repo, message, match, opts):
2187 def commitfunc(ui, repo, message, match, opts):
2187 overrides = {}
2188 overrides = {}
2188 if opts.get(b'secret'):
2189 if opts.get(b'secret'):
2189 overrides[(b'phases', b'new-commit')] = b'secret'
2190 overrides[(b'phases', b'new-commit')] = b'secret'
2190 elif opts.get(b'draft'):
2191 elif opts.get(b'draft'):
2191 overrides[(b'phases', b'new-commit')] = b'draft'
2192 overrides[(b'phases', b'new-commit')] = b'draft'
2192
2193
2193 baseui = repo.baseui
2194 baseui = repo.baseui
2194 with baseui.configoverride(overrides, b'commit'):
2195 with baseui.configoverride(overrides, b'commit'):
2195 with ui.configoverride(overrides, b'commit'):
2196 with ui.configoverride(overrides, b'commit'):
2196 editform = cmdutil.mergeeditform(
2197 editform = cmdutil.mergeeditform(
2197 repo[None], b'commit.normal'
2198 repo[None], b'commit.normal'
2198 )
2199 )
2199 editor = cmdutil.getcommiteditor(
2200 editor = cmdutil.getcommiteditor(
2200 editform=editform, **pycompat.strkwargs(opts)
2201 editform=editform, **pycompat.strkwargs(opts)
2201 )
2202 )
2202 return repo.commit(
2203 return repo.commit(
2203 message,
2204 message,
2204 opts.get(b'user'),
2205 opts.get(b'user'),
2205 opts.get(b'date'),
2206 opts.get(b'date'),
2206 match,
2207 match,
2207 editor=editor,
2208 editor=editor,
2208 extra=extra,
2209 extra=extra,
2209 )
2210 )
2210
2211
2211 opts = pycompat.byteskwargs(opts)
2212 opts = pycompat.byteskwargs(opts)
2212 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2213 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2213
2214
2214 if not node:
2215 if not node:
2215 stat = cmdutil.postcommitstatus(repo, pats, opts)
2216 stat = cmdutil.postcommitstatus(repo, pats, opts)
2216 if stat.deleted:
2217 if stat.deleted:
2217 ui.status(
2218 ui.status(
2218 _(
2219 _(
2219 b"nothing changed (%d missing files, see "
2220 b"nothing changed (%d missing files, see "
2220 b"'hg status')\n"
2221 b"'hg status')\n"
2221 )
2222 )
2222 % len(stat.deleted)
2223 % len(stat.deleted)
2223 )
2224 )
2224 else:
2225 else:
2225 ui.status(_(b"nothing changed\n"))
2226 ui.status(_(b"nothing changed\n"))
2226 return 1
2227 return 1
2227
2228
2228 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2229 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2229
2230
2230 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2231 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2231 status(
2232 status(
2232 ui,
2233 ui,
2233 repo,
2234 repo,
2234 modified=True,
2235 modified=True,
2235 added=True,
2236 added=True,
2236 removed=True,
2237 removed=True,
2237 deleted=True,
2238 deleted=True,
2238 unknown=True,
2239 unknown=True,
2239 subrepos=opts.get(b'subrepos'),
2240 subrepos=opts.get(b'subrepos'),
2240 )
2241 )
2241
2242
2242
2243
2243 @command(
2244 @command(
2244 b'config|showconfig|debugconfig',
2245 b'config|showconfig|debugconfig',
2245 [
2246 [
2246 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2247 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2247 # This is experimental because we need
2248 # This is experimental because we need
2248 # * reasonable behavior around aliases,
2249 # * reasonable behavior around aliases,
2249 # * decide if we display [debug] [experimental] and [devel] section par
2250 # * decide if we display [debug] [experimental] and [devel] section par
2250 # default
2251 # default
2251 # * some way to display "generic" config entry (the one matching
2252 # * some way to display "generic" config entry (the one matching
2252 # regexp,
2253 # regexp,
2253 # * proper display of the different value type
2254 # * proper display of the different value type
2254 # * a better way to handle <DYNAMIC> values (and variable types),
2255 # * a better way to handle <DYNAMIC> values (and variable types),
2255 # * maybe some type information ?
2256 # * maybe some type information ?
2256 (
2257 (
2257 b'',
2258 b'',
2258 b'exp-all-known',
2259 b'exp-all-known',
2259 None,
2260 None,
2260 _(b'show all known config option (EXPERIMENTAL)'),
2261 _(b'show all known config option (EXPERIMENTAL)'),
2261 ),
2262 ),
2262 (b'e', b'edit', None, _(b'edit user config')),
2263 (b'e', b'edit', None, _(b'edit user config')),
2263 (b'l', b'local', None, _(b'edit repository config')),
2264 (b'l', b'local', None, _(b'edit repository config')),
2264 (b'', b'source', None, _(b'show source of configuration value')),
2265 (b'', b'source', None, _(b'show source of configuration value')),
2265 (
2266 (
2266 b'',
2267 b'',
2267 b'shared',
2268 b'shared',
2268 None,
2269 None,
2269 _(b'edit shared source repository config (EXPERIMENTAL)'),
2270 _(b'edit shared source repository config (EXPERIMENTAL)'),
2270 ),
2271 ),
2271 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2272 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2272 (b'g', b'global', None, _(b'edit global config')),
2273 (b'g', b'global', None, _(b'edit global config')),
2273 ]
2274 ]
2274 + formatteropts,
2275 + formatteropts,
2275 _(b'[-u] [NAME]...'),
2276 _(b'[-u] [NAME]...'),
2276 helpcategory=command.CATEGORY_HELP,
2277 helpcategory=command.CATEGORY_HELP,
2277 optionalrepo=True,
2278 optionalrepo=True,
2278 intents={INTENT_READONLY},
2279 intents={INTENT_READONLY},
2279 )
2280 )
2280 def config(ui, repo, *values, **opts):
2281 def config(ui, repo, *values, **opts):
2281 """show combined config settings from all hgrc files
2282 """show combined config settings from all hgrc files
2282
2283
2283 With no arguments, print names and values of all config items.
2284 With no arguments, print names and values of all config items.
2284
2285
2285 With one argument of the form section.name, print just the value
2286 With one argument of the form section.name, print just the value
2286 of that config item.
2287 of that config item.
2287
2288
2288 With multiple arguments, print names and values of all config
2289 With multiple arguments, print names and values of all config
2289 items with matching section names or section.names.
2290 items with matching section names or section.names.
2290
2291
2291 With --edit, start an editor on the user-level config file. With
2292 With --edit, start an editor on the user-level config file. With
2292 --global, edit the system-wide config file. With --local, edit the
2293 --global, edit the system-wide config file. With --local, edit the
2293 repository-level config file.
2294 repository-level config file.
2294
2295
2295 With --source, the source (filename and line number) is printed
2296 With --source, the source (filename and line number) is printed
2296 for each config item.
2297 for each config item.
2297
2298
2298 See :hg:`help config` for more information about config files.
2299 See :hg:`help config` for more information about config files.
2299
2300
2300 .. container:: verbose
2301 .. container:: verbose
2301
2302
2302 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2303 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2303 This file is not shared across shares when in share-safe mode.
2304 This file is not shared across shares when in share-safe mode.
2304
2305
2305 Template:
2306 Template:
2306
2307
2307 The following keywords are supported. See also :hg:`help templates`.
2308 The following keywords are supported. See also :hg:`help templates`.
2308
2309
2309 :name: String. Config name.
2310 :name: String. Config name.
2310 :source: String. Filename and line number where the item is defined.
2311 :source: String. Filename and line number where the item is defined.
2311 :value: String. Config value.
2312 :value: String. Config value.
2312
2313
2313 The --shared flag can be used to edit the config file of shared source
2314 The --shared flag can be used to edit the config file of shared source
2314 repository. It only works when you have shared using the experimental
2315 repository. It only works when you have shared using the experimental
2315 share safe feature.
2316 share safe feature.
2316
2317
2317 Returns 0 on success, 1 if NAME does not exist.
2318 Returns 0 on success, 1 if NAME does not exist.
2318
2319
2319 """
2320 """
2320
2321
2321 opts = pycompat.byteskwargs(opts)
2322 opts = pycompat.byteskwargs(opts)
2322 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2323 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2323 if any(opts.get(o) for o in editopts):
2324 if any(opts.get(o) for o in editopts):
2324 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2325 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2325 if opts.get(b'local'):
2326 if opts.get(b'local'):
2326 if not repo:
2327 if not repo:
2327 raise error.InputError(
2328 raise error.InputError(
2328 _(b"can't use --local outside a repository")
2329 _(b"can't use --local outside a repository")
2329 )
2330 )
2330 paths = [repo.vfs.join(b'hgrc')]
2331 paths = [repo.vfs.join(b'hgrc')]
2331 elif opts.get(b'global'):
2332 elif opts.get(b'global'):
2332 paths = rcutil.systemrcpath()
2333 paths = rcutil.systemrcpath()
2333 elif opts.get(b'shared'):
2334 elif opts.get(b'shared'):
2334 if not repo.shared():
2335 if not repo.shared():
2335 raise error.InputError(
2336 raise error.InputError(
2336 _(b"repository is not shared; can't use --shared")
2337 _(b"repository is not shared; can't use --shared")
2337 )
2338 )
2338 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2339 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2339 raise error.InputError(
2340 raise error.InputError(
2340 _(
2341 _(
2341 b"share safe feature not enabled; "
2342 b"share safe feature not enabled; "
2342 b"unable to edit shared source repository config"
2343 b"unable to edit shared source repository config"
2343 )
2344 )
2344 )
2345 )
2345 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2346 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2346 elif opts.get(b'non_shared'):
2347 elif opts.get(b'non_shared'):
2347 paths = [repo.vfs.join(b'hgrc-not-shared')]
2348 paths = [repo.vfs.join(b'hgrc-not-shared')]
2348 else:
2349 else:
2349 paths = rcutil.userrcpath()
2350 paths = rcutil.userrcpath()
2350
2351
2351 for f in paths:
2352 for f in paths:
2352 if os.path.exists(f):
2353 if os.path.exists(f):
2353 break
2354 break
2354 else:
2355 else:
2355 if opts.get(b'global'):
2356 if opts.get(b'global'):
2356 samplehgrc = uimod.samplehgrcs[b'global']
2357 samplehgrc = uimod.samplehgrcs[b'global']
2357 elif opts.get(b'local'):
2358 elif opts.get(b'local'):
2358 samplehgrc = uimod.samplehgrcs[b'local']
2359 samplehgrc = uimod.samplehgrcs[b'local']
2359 else:
2360 else:
2360 samplehgrc = uimod.samplehgrcs[b'user']
2361 samplehgrc = uimod.samplehgrcs[b'user']
2361
2362
2362 f = paths[0]
2363 f = paths[0]
2363 fp = open(f, b"wb")
2364 fp = open(f, b"wb")
2364 fp.write(util.tonativeeol(samplehgrc))
2365 fp.write(util.tonativeeol(samplehgrc))
2365 fp.close()
2366 fp.close()
2366
2367
2367 editor = ui.geteditor()
2368 editor = ui.geteditor()
2368 ui.system(
2369 ui.system(
2369 b"%s \"%s\"" % (editor, f),
2370 b"%s \"%s\"" % (editor, f),
2370 onerr=error.InputError,
2371 onerr=error.InputError,
2371 errprefix=_(b"edit failed"),
2372 errprefix=_(b"edit failed"),
2372 blockedtag=b'config_edit',
2373 blockedtag=b'config_edit',
2373 )
2374 )
2374 return
2375 return
2375 ui.pager(b'config')
2376 ui.pager(b'config')
2376 fm = ui.formatter(b'config', opts)
2377 fm = ui.formatter(b'config', opts)
2377 for t, f in rcutil.rccomponents():
2378 for t, f in rcutil.rccomponents():
2378 if t == b'path':
2379 if t == b'path':
2379 ui.debug(b'read config from: %s\n' % f)
2380 ui.debug(b'read config from: %s\n' % f)
2380 elif t == b'resource':
2381 elif t == b'resource':
2381 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2382 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2382 elif t == b'items':
2383 elif t == b'items':
2383 # Don't print anything for 'items'.
2384 # Don't print anything for 'items'.
2384 pass
2385 pass
2385 else:
2386 else:
2386 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2387 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2387 untrusted = bool(opts.get(b'untrusted'))
2388 untrusted = bool(opts.get(b'untrusted'))
2388
2389
2389 selsections = selentries = []
2390 selsections = selentries = []
2390 if values:
2391 if values:
2391 selsections = [v for v in values if b'.' not in v]
2392 selsections = [v for v in values if b'.' not in v]
2392 selentries = [v for v in values if b'.' in v]
2393 selentries = [v for v in values if b'.' in v]
2393 uniquesel = len(selentries) == 1 and not selsections
2394 uniquesel = len(selentries) == 1 and not selsections
2394 selsections = set(selsections)
2395 selsections = set(selsections)
2395 selentries = set(selentries)
2396 selentries = set(selentries)
2396
2397
2397 matched = False
2398 matched = False
2398 all_known = opts[b'exp_all_known']
2399 all_known = opts[b'exp_all_known']
2399 show_source = ui.debugflag or opts.get(b'source')
2400 show_source = ui.debugflag or opts.get(b'source')
2400 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2401 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2401 for section, name, value in entries:
2402 for section, name, value in entries:
2402 source = ui.configsource(section, name, untrusted)
2403 source = ui.configsource(section, name, untrusted)
2403 value = pycompat.bytestr(value)
2404 value = pycompat.bytestr(value)
2404 defaultvalue = ui.configdefault(section, name)
2405 defaultvalue = ui.configdefault(section, name)
2405 if fm.isplain():
2406 if fm.isplain():
2406 source = source or b'none'
2407 source = source or b'none'
2407 value = value.replace(b'\n', b'\\n')
2408 value = value.replace(b'\n', b'\\n')
2408 entryname = section + b'.' + name
2409 entryname = section + b'.' + name
2409 if values and not (section in selsections or entryname in selentries):
2410 if values and not (section in selsections or entryname in selentries):
2410 continue
2411 continue
2411 fm.startitem()
2412 fm.startitem()
2412 fm.condwrite(show_source, b'source', b'%s: ', source)
2413 fm.condwrite(show_source, b'source', b'%s: ', source)
2413 if uniquesel:
2414 if uniquesel:
2414 fm.data(name=entryname)
2415 fm.data(name=entryname)
2415 fm.write(b'value', b'%s\n', value)
2416 fm.write(b'value', b'%s\n', value)
2416 else:
2417 else:
2417 fm.write(b'name value', b'%s=%s\n', entryname, value)
2418 fm.write(b'name value', b'%s=%s\n', entryname, value)
2418 if formatter.isprintable(defaultvalue):
2419 if formatter.isprintable(defaultvalue):
2419 fm.data(defaultvalue=defaultvalue)
2420 fm.data(defaultvalue=defaultvalue)
2420 elif isinstance(defaultvalue, list) and all(
2421 elif isinstance(defaultvalue, list) and all(
2421 formatter.isprintable(e) for e in defaultvalue
2422 formatter.isprintable(e) for e in defaultvalue
2422 ):
2423 ):
2423 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2424 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2424 # TODO: no idea how to process unsupported defaultvalue types
2425 # TODO: no idea how to process unsupported defaultvalue types
2425 matched = True
2426 matched = True
2426 fm.end()
2427 fm.end()
2427 if matched:
2428 if matched:
2428 return 0
2429 return 0
2429 return 1
2430 return 1
2430
2431
2431
2432
2432 @command(
2433 @command(
2433 b'continue',
2434 b'continue',
2434 dryrunopts,
2435 dryrunopts,
2435 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2436 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2436 helpbasic=True,
2437 helpbasic=True,
2437 )
2438 )
2438 def continuecmd(ui, repo, **opts):
2439 def continuecmd(ui, repo, **opts):
2439 """resumes an interrupted operation (EXPERIMENTAL)
2440 """resumes an interrupted operation (EXPERIMENTAL)
2440
2441
2441 Finishes a multistep operation like graft, histedit, rebase, merge,
2442 Finishes a multistep operation like graft, histedit, rebase, merge,
2442 and unshelve if they are in an interrupted state.
2443 and unshelve if they are in an interrupted state.
2443
2444
2444 use --dry-run/-n to dry run the command.
2445 use --dry-run/-n to dry run the command.
2445 """
2446 """
2446 dryrun = opts.get('dry_run')
2447 dryrun = opts.get('dry_run')
2447 contstate = cmdutil.getunfinishedstate(repo)
2448 contstate = cmdutil.getunfinishedstate(repo)
2448 if not contstate:
2449 if not contstate:
2449 raise error.StateError(_(b'no operation in progress'))
2450 raise error.StateError(_(b'no operation in progress'))
2450 if not contstate.continuefunc:
2451 if not contstate.continuefunc:
2451 raise error.StateError(
2452 raise error.StateError(
2452 (
2453 (
2453 _(b"%s in progress but does not support 'hg continue'")
2454 _(b"%s in progress but does not support 'hg continue'")
2454 % (contstate._opname)
2455 % (contstate._opname)
2455 ),
2456 ),
2456 hint=contstate.continuemsg(),
2457 hint=contstate.continuemsg(),
2457 )
2458 )
2458 if dryrun:
2459 if dryrun:
2459 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2460 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2460 return
2461 return
2461 return contstate.continuefunc(ui, repo)
2462 return contstate.continuefunc(ui, repo)
2462
2463
2463
2464
2464 @command(
2465 @command(
2465 b'copy|cp',
2466 b'copy|cp',
2466 [
2467 [
2467 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2468 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2468 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2469 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2469 (
2470 (
2470 b'',
2471 b'',
2471 b'at-rev',
2472 b'at-rev',
2472 b'',
2473 b'',
2473 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2474 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2474 _(b'REV'),
2475 _(b'REV'),
2475 ),
2476 ),
2476 (
2477 (
2477 b'f',
2478 b'f',
2478 b'force',
2479 b'force',
2479 None,
2480 None,
2480 _(b'forcibly copy over an existing managed file'),
2481 _(b'forcibly copy over an existing managed file'),
2481 ),
2482 ),
2482 ]
2483 ]
2483 + walkopts
2484 + walkopts
2484 + dryrunopts,
2485 + dryrunopts,
2485 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2486 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2486 helpcategory=command.CATEGORY_FILE_CONTENTS,
2487 helpcategory=command.CATEGORY_FILE_CONTENTS,
2487 )
2488 )
2488 def copy(ui, repo, *pats, **opts):
2489 def copy(ui, repo, *pats, **opts):
2489 """mark files as copied for the next commit
2490 """mark files as copied for the next commit
2490
2491
2491 Mark dest as having copies of source files. If dest is a
2492 Mark dest as having copies of source files. If dest is a
2492 directory, copies are put in that directory. If dest is a file,
2493 directory, copies are put in that directory. If dest is a file,
2493 the source must be a single file.
2494 the source must be a single file.
2494
2495
2495 By default, this command copies the contents of files as they
2496 By default, this command copies the contents of files as they
2496 exist in the working directory. If invoked with -A/--after, the
2497 exist in the working directory. If invoked with -A/--after, the
2497 operation is recorded, but no copying is performed.
2498 operation is recorded, but no copying is performed.
2498
2499
2499 To undo marking a destination file as copied, use --forget. With that
2500 To undo marking a destination file as copied, use --forget. With that
2500 option, all given (positional) arguments are unmarked as copies. The
2501 option, all given (positional) arguments are unmarked as copies. The
2501 destination file(s) will be left in place (still tracked). Note that
2502 destination file(s) will be left in place (still tracked). Note that
2502 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2503 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2503
2504
2504 This command takes effect with the next commit by default.
2505 This command takes effect with the next commit by default.
2505
2506
2506 Returns 0 on success, 1 if errors are encountered.
2507 Returns 0 on success, 1 if errors are encountered.
2507 """
2508 """
2508 opts = pycompat.byteskwargs(opts)
2509 opts = pycompat.byteskwargs(opts)
2509
2510
2510 context = lambda repo: repo.dirstate.changing_files(repo)
2511 context = lambda repo: repo.dirstate.changing_files(repo)
2511 rev = opts.get(b'at_rev')
2512 rev = opts.get(b'at_rev')
2512 ctx = None
2513 ctx = None
2513 if rev:
2514 if rev:
2514 ctx = logcmdutil.revsingle(repo, rev)
2515 ctx = logcmdutil.revsingle(repo, rev)
2515 if ctx.rev() is not None:
2516 if ctx.rev() is not None:
2516
2517
2517 def context(repo):
2518 def context(repo):
2518 return util.nullcontextmanager()
2519 return util.nullcontextmanager()
2519
2520
2520 opts[b'at_rev'] = ctx.rev()
2521 opts[b'at_rev'] = ctx.rev()
2521 with repo.wlock(), context(repo):
2522 with repo.wlock(), context(repo):
2522 return cmdutil.copy(ui, repo, pats, opts)
2523 return cmdutil.copy(ui, repo, pats, opts)
2523
2524
2524
2525
2525 @command(
2526 @command(
2526 b'debugcommands',
2527 b'debugcommands',
2527 [],
2528 [],
2528 _(b'[COMMAND]'),
2529 _(b'[COMMAND]'),
2529 helpcategory=command.CATEGORY_HELP,
2530 helpcategory=command.CATEGORY_HELP,
2530 norepo=True,
2531 norepo=True,
2531 )
2532 )
2532 def debugcommands(ui, cmd=b'', *args):
2533 def debugcommands(ui, cmd=b'', *args):
2533 """list all available commands and options"""
2534 """list all available commands and options"""
2534 for cmd, vals in sorted(table.items()):
2535 for cmd, vals in sorted(table.items()):
2535 cmd = cmd.split(b'|')[0]
2536 cmd = cmd.split(b'|')[0]
2536 opts = b', '.join([i[1] for i in vals[1]])
2537 opts = b', '.join([i[1] for i in vals[1]])
2537 ui.write(b'%s: %s\n' % (cmd, opts))
2538 ui.write(b'%s: %s\n' % (cmd, opts))
2538
2539
2539
2540
2540 @command(
2541 @command(
2541 b'debugcomplete',
2542 b'debugcomplete',
2542 [(b'o', b'options', None, _(b'show the command options'))],
2543 [(b'o', b'options', None, _(b'show the command options'))],
2543 _(b'[-o] CMD'),
2544 _(b'[-o] CMD'),
2544 helpcategory=command.CATEGORY_HELP,
2545 helpcategory=command.CATEGORY_HELP,
2545 norepo=True,
2546 norepo=True,
2546 )
2547 )
2547 def debugcomplete(ui, cmd=b'', **opts):
2548 def debugcomplete(ui, cmd=b'', **opts):
2548 """returns the completion list associated with the given command"""
2549 """returns the completion list associated with the given command"""
2549
2550
2550 if opts.get('options'):
2551 if opts.get('options'):
2551 options = []
2552 options = []
2552 otables = [globalopts]
2553 otables = [globalopts]
2553 if cmd:
2554 if cmd:
2554 aliases, entry = cmdutil.findcmd(cmd, table, False)
2555 aliases, entry = cmdutil.findcmd(cmd, table, False)
2555 otables.append(entry[1])
2556 otables.append(entry[1])
2556 for t in otables:
2557 for t in otables:
2557 for o in t:
2558 for o in t:
2558 if b"(DEPRECATED)" in o[3]:
2559 if b"(DEPRECATED)" in o[3]:
2559 continue
2560 continue
2560 if o[0]:
2561 if o[0]:
2561 options.append(b'-%s' % o[0])
2562 options.append(b'-%s' % o[0])
2562 options.append(b'--%s' % o[1])
2563 options.append(b'--%s' % o[1])
2563 ui.write(b"%s\n" % b"\n".join(options))
2564 ui.write(b"%s\n" % b"\n".join(options))
2564 return
2565 return
2565
2566
2566 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2567 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2567 if ui.verbose:
2568 if ui.verbose:
2568 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2569 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2569 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2570 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2570
2571
2571
2572
2572 @command(
2573 @command(
2573 b'diff',
2574 b'diff',
2574 [
2575 [
2575 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2576 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2576 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2577 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2577 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2578 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2578 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2579 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2579 ]
2580 ]
2580 + diffopts
2581 + diffopts
2581 + diffopts2
2582 + diffopts2
2582 + walkopts
2583 + walkopts
2583 + subrepoopts,
2584 + subrepoopts,
2584 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2585 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2585 helpcategory=command.CATEGORY_FILE_CONTENTS,
2586 helpcategory=command.CATEGORY_FILE_CONTENTS,
2586 helpbasic=True,
2587 helpbasic=True,
2587 inferrepo=True,
2588 inferrepo=True,
2588 intents={INTENT_READONLY},
2589 intents={INTENT_READONLY},
2589 )
2590 )
2590 def diff(ui, repo, *pats, **opts):
2591 def diff(ui, repo, *pats, **opts):
2591 """diff repository (or selected files)
2592 """diff repository (or selected files)
2592
2593
2593 Show differences between revisions for the specified files.
2594 Show differences between revisions for the specified files.
2594
2595
2595 Differences between files are shown using the unified diff format.
2596 Differences between files are shown using the unified diff format.
2596
2597
2597 .. note::
2598 .. note::
2598
2599
2599 :hg:`diff` may generate unexpected results for merges, as it will
2600 :hg:`diff` may generate unexpected results for merges, as it will
2600 default to comparing against the working directory's first
2601 default to comparing against the working directory's first
2601 parent changeset if no revisions are specified. To diff against the
2602 parent changeset if no revisions are specified. To diff against the
2602 conflict regions, you can use `--config diff.merge=yes`.
2603 conflict regions, you can use `--config diff.merge=yes`.
2603
2604
2604 By default, the working directory files are compared to its first parent. To
2605 By default, the working directory files are compared to its first parent. To
2605 see the differences from another revision, use --from. To see the difference
2606 see the differences from another revision, use --from. To see the difference
2606 to another revision, use --to. For example, :hg:`diff --from .^` will show
2607 to another revision, use --to. For example, :hg:`diff --from .^` will show
2607 the differences from the working copy's grandparent to the working copy,
2608 the differences from the working copy's grandparent to the working copy,
2608 :hg:`diff --to .` will show the diff from the working copy to its parent
2609 :hg:`diff --to .` will show the diff from the working copy to its parent
2609 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2610 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2610 show the diff between those two revisions.
2611 show the diff between those two revisions.
2611
2612
2612 Alternatively you can specify -c/--change with a revision to see the changes
2613 Alternatively you can specify -c/--change with a revision to see the changes
2613 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2614 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2614 equivalent to :hg:`diff --from 42^ --to 42`)
2615 equivalent to :hg:`diff --from 42^ --to 42`)
2615
2616
2616 Without the -a/--text option, diff will avoid generating diffs of
2617 Without the -a/--text option, diff will avoid generating diffs of
2617 files it detects as binary. With -a, diff will generate a diff
2618 files it detects as binary. With -a, diff will generate a diff
2618 anyway, probably with undesirable results.
2619 anyway, probably with undesirable results.
2619
2620
2620 Use the -g/--git option to generate diffs in the git extended diff
2621 Use the -g/--git option to generate diffs in the git extended diff
2621 format. For more information, read :hg:`help diffs`.
2622 format. For more information, read :hg:`help diffs`.
2622
2623
2623 .. container:: verbose
2624 .. container:: verbose
2624
2625
2625 Examples:
2626 Examples:
2626
2627
2627 - compare a file in the current working directory to its parent::
2628 - compare a file in the current working directory to its parent::
2628
2629
2629 hg diff foo.c
2630 hg diff foo.c
2630
2631
2631 - compare two historical versions of a directory, with rename info::
2632 - compare two historical versions of a directory, with rename info::
2632
2633
2633 hg diff --git --from 1.0 --to 1.2 lib/
2634 hg diff --git --from 1.0 --to 1.2 lib/
2634
2635
2635 - get change stats relative to the last change on some date::
2636 - get change stats relative to the last change on some date::
2636
2637
2637 hg diff --stat --from "date('may 2')"
2638 hg diff --stat --from "date('may 2')"
2638
2639
2639 - diff all newly-added files that contain a keyword::
2640 - diff all newly-added files that contain a keyword::
2640
2641
2641 hg diff "set:added() and grep(GNU)"
2642 hg diff "set:added() and grep(GNU)"
2642
2643
2643 - compare a revision and its parents::
2644 - compare a revision and its parents::
2644
2645
2645 hg diff -c 9353 # compare against first parent
2646 hg diff -c 9353 # compare against first parent
2646 hg diff --from 9353^ --to 9353 # same using revset syntax
2647 hg diff --from 9353^ --to 9353 # same using revset syntax
2647 hg diff --from 9353^2 --to 9353 # compare against the second parent
2648 hg diff --from 9353^2 --to 9353 # compare against the second parent
2648
2649
2649 Returns 0 on success.
2650 Returns 0 on success.
2650 """
2651 """
2651
2652
2652 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2653 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2653 opts = pycompat.byteskwargs(opts)
2654 opts = pycompat.byteskwargs(opts)
2654 revs = opts.get(b'rev')
2655 revs = opts.get(b'rev')
2655 change = opts.get(b'change')
2656 change = opts.get(b'change')
2656 from_rev = opts.get(b'from')
2657 from_rev = opts.get(b'from')
2657 to_rev = opts.get(b'to')
2658 to_rev = opts.get(b'to')
2658 stat = opts.get(b'stat')
2659 stat = opts.get(b'stat')
2659 reverse = opts.get(b'reverse')
2660 reverse = opts.get(b'reverse')
2660
2661
2661 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2662 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2662 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2663 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2663 if change:
2664 if change:
2664 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2665 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2665 ctx2 = logcmdutil.revsingle(repo, change, None)
2666 ctx2 = logcmdutil.revsingle(repo, change, None)
2666 ctx1 = logcmdutil.diff_parent(ctx2)
2667 ctx1 = logcmdutil.diff_parent(ctx2)
2667 elif from_rev or to_rev:
2668 elif from_rev or to_rev:
2668 repo = scmutil.unhidehashlikerevs(
2669 repo = scmutil.unhidehashlikerevs(
2669 repo, [from_rev] + [to_rev], b'nowarn'
2670 repo, [from_rev] + [to_rev], b'nowarn'
2670 )
2671 )
2671 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2672 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2672 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2673 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2673 else:
2674 else:
2674 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2675 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2675 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2676 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2676
2677
2677 if reverse:
2678 if reverse:
2678 ctxleft = ctx2
2679 ctxleft = ctx2
2679 ctxright = ctx1
2680 ctxright = ctx1
2680 else:
2681 else:
2681 ctxleft = ctx1
2682 ctxleft = ctx1
2682 ctxright = ctx2
2683 ctxright = ctx2
2683
2684
2684 diffopts = patch.diffallopts(ui, opts)
2685 diffopts = patch.diffallopts(ui, opts)
2685 m = scmutil.match(ctx2, pats, opts)
2686 m = scmutil.match(ctx2, pats, opts)
2686 m = repo.narrowmatch(m)
2687 m = repo.narrowmatch(m)
2687 ui.pager(b'diff')
2688 ui.pager(b'diff')
2688 logcmdutil.diffordiffstat(
2689 logcmdutil.diffordiffstat(
2689 ui,
2690 ui,
2690 repo,
2691 repo,
2691 diffopts,
2692 diffopts,
2692 ctxleft,
2693 ctxleft,
2693 ctxright,
2694 ctxright,
2694 m,
2695 m,
2695 stat=stat,
2696 stat=stat,
2696 listsubrepos=opts.get(b'subrepos'),
2697 listsubrepos=opts.get(b'subrepos'),
2697 root=opts.get(b'root'),
2698 root=opts.get(b'root'),
2698 )
2699 )
2699
2700
2700
2701
2701 @command(
2702 @command(
2702 b'export',
2703 b'export',
2703 [
2704 [
2704 (
2705 (
2705 b'B',
2706 b'B',
2706 b'bookmark',
2707 b'bookmark',
2707 b'',
2708 b'',
2708 _(b'export changes only reachable by given bookmark'),
2709 _(b'export changes only reachable by given bookmark'),
2709 _(b'BOOKMARK'),
2710 _(b'BOOKMARK'),
2710 ),
2711 ),
2711 (
2712 (
2712 b'o',
2713 b'o',
2713 b'output',
2714 b'output',
2714 b'',
2715 b'',
2715 _(b'print output to file with formatted name'),
2716 _(b'print output to file with formatted name'),
2716 _(b'FORMAT'),
2717 _(b'FORMAT'),
2717 ),
2718 ),
2718 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2719 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2719 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2720 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2720 ]
2721 ]
2721 + diffopts
2722 + diffopts
2722 + formatteropts,
2723 + formatteropts,
2723 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2724 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2724 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2725 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2725 helpbasic=True,
2726 helpbasic=True,
2726 intents={INTENT_READONLY},
2727 intents={INTENT_READONLY},
2727 )
2728 )
2728 def export(ui, repo, *changesets, **opts):
2729 def export(ui, repo, *changesets, **opts):
2729 """dump the header and diffs for one or more changesets
2730 """dump the header and diffs for one or more changesets
2730
2731
2731 Print the changeset header and diffs for one or more revisions.
2732 Print the changeset header and diffs for one or more revisions.
2732 If no revision is given, the parent of the working directory is used.
2733 If no revision is given, the parent of the working directory is used.
2733
2734
2734 The information shown in the changeset header is: author, date,
2735 The information shown in the changeset header is: author, date,
2735 branch name (if non-default), changeset hash, parent(s) and commit
2736 branch name (if non-default), changeset hash, parent(s) and commit
2736 comment.
2737 comment.
2737
2738
2738 .. note::
2739 .. note::
2739
2740
2740 :hg:`export` may generate unexpected diff output for merge
2741 :hg:`export` may generate unexpected diff output for merge
2741 changesets, as it will compare the merge changeset against its
2742 changesets, as it will compare the merge changeset against its
2742 first parent only.
2743 first parent only.
2743
2744
2744 Output may be to a file, in which case the name of the file is
2745 Output may be to a file, in which case the name of the file is
2745 given using a template string. See :hg:`help templates`. In addition
2746 given using a template string. See :hg:`help templates`. In addition
2746 to the common template keywords, the following formatting rules are
2747 to the common template keywords, the following formatting rules are
2747 supported:
2748 supported:
2748
2749
2749 :``%%``: literal "%" character
2750 :``%%``: literal "%" character
2750 :``%H``: changeset hash (40 hexadecimal digits)
2751 :``%H``: changeset hash (40 hexadecimal digits)
2751 :``%N``: number of patches being generated
2752 :``%N``: number of patches being generated
2752 :``%R``: changeset revision number
2753 :``%R``: changeset revision number
2753 :``%b``: basename of the exporting repository
2754 :``%b``: basename of the exporting repository
2754 :``%h``: short-form changeset hash (12 hexadecimal digits)
2755 :``%h``: short-form changeset hash (12 hexadecimal digits)
2755 :``%m``: first line of the commit message (only alphanumeric characters)
2756 :``%m``: first line of the commit message (only alphanumeric characters)
2756 :``%n``: zero-padded sequence number, starting at 1
2757 :``%n``: zero-padded sequence number, starting at 1
2757 :``%r``: zero-padded changeset revision number
2758 :``%r``: zero-padded changeset revision number
2758 :``\\``: literal "\\" character
2759 :``\\``: literal "\\" character
2759
2760
2760 Without the -a/--text option, export will avoid generating diffs
2761 Without the -a/--text option, export will avoid generating diffs
2761 of files it detects as binary. With -a, export will generate a
2762 of files it detects as binary. With -a, export will generate a
2762 diff anyway, probably with undesirable results.
2763 diff anyway, probably with undesirable results.
2763
2764
2764 With -B/--bookmark changesets reachable by the given bookmark are
2765 With -B/--bookmark changesets reachable by the given bookmark are
2765 selected.
2766 selected.
2766
2767
2767 Use the -g/--git option to generate diffs in the git extended diff
2768 Use the -g/--git option to generate diffs in the git extended diff
2768 format. See :hg:`help diffs` for more information.
2769 format. See :hg:`help diffs` for more information.
2769
2770
2770 With the --switch-parent option, the diff will be against the
2771 With the --switch-parent option, the diff will be against the
2771 second parent. It can be useful to review a merge.
2772 second parent. It can be useful to review a merge.
2772
2773
2773 .. container:: verbose
2774 .. container:: verbose
2774
2775
2775 Template:
2776 Template:
2776
2777
2777 The following keywords are supported in addition to the common template
2778 The following keywords are supported in addition to the common template
2778 keywords and functions. See also :hg:`help templates`.
2779 keywords and functions. See also :hg:`help templates`.
2779
2780
2780 :diff: String. Diff content.
2781 :diff: String. Diff content.
2781 :parents: List of strings. Parent nodes of the changeset.
2782 :parents: List of strings. Parent nodes of the changeset.
2782
2783
2783 Examples:
2784 Examples:
2784
2785
2785 - use export and import to transplant a bugfix to the current
2786 - use export and import to transplant a bugfix to the current
2786 branch::
2787 branch::
2787
2788
2788 hg export -r 9353 | hg import -
2789 hg export -r 9353 | hg import -
2789
2790
2790 - export all the changesets between two revisions to a file with
2791 - export all the changesets between two revisions to a file with
2791 rename information::
2792 rename information::
2792
2793
2793 hg export --git -r 123:150 > changes.txt
2794 hg export --git -r 123:150 > changes.txt
2794
2795
2795 - split outgoing changes into a series of patches with
2796 - split outgoing changes into a series of patches with
2796 descriptive names::
2797 descriptive names::
2797
2798
2798 hg export -r "outgoing()" -o "%n-%m.patch"
2799 hg export -r "outgoing()" -o "%n-%m.patch"
2799
2800
2800 Returns 0 on success.
2801 Returns 0 on success.
2801 """
2802 """
2802 opts = pycompat.byteskwargs(opts)
2803 opts = pycompat.byteskwargs(opts)
2803 bookmark = opts.get(b'bookmark')
2804 bookmark = opts.get(b'bookmark')
2804 changesets += tuple(opts.get(b'rev', []))
2805 changesets += tuple(opts.get(b'rev', []))
2805
2806
2806 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2807 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2807
2808
2808 if bookmark:
2809 if bookmark:
2809 if bookmark not in repo._bookmarks:
2810 if bookmark not in repo._bookmarks:
2810 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2811 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2811
2812
2812 revs = scmutil.bookmarkrevs(repo, bookmark)
2813 revs = scmutil.bookmarkrevs(repo, bookmark)
2813 else:
2814 else:
2814 if not changesets:
2815 if not changesets:
2815 changesets = [b'.']
2816 changesets = [b'.']
2816
2817
2817 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2818 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2818 revs = logcmdutil.revrange(repo, changesets)
2819 revs = logcmdutil.revrange(repo, changesets)
2819
2820
2820 if not revs:
2821 if not revs:
2821 raise error.InputError(_(b"export requires at least one changeset"))
2822 raise error.InputError(_(b"export requires at least one changeset"))
2822 if len(revs) > 1:
2823 if len(revs) > 1:
2823 ui.note(_(b'exporting patches:\n'))
2824 ui.note(_(b'exporting patches:\n'))
2824 else:
2825 else:
2825 ui.note(_(b'exporting patch:\n'))
2826 ui.note(_(b'exporting patch:\n'))
2826
2827
2827 fntemplate = opts.get(b'output')
2828 fntemplate = opts.get(b'output')
2828 if cmdutil.isstdiofilename(fntemplate):
2829 if cmdutil.isstdiofilename(fntemplate):
2829 fntemplate = b''
2830 fntemplate = b''
2830
2831
2831 if fntemplate:
2832 if fntemplate:
2832 fm = formatter.nullformatter(ui, b'export', opts)
2833 fm = formatter.nullformatter(ui, b'export', opts)
2833 else:
2834 else:
2834 ui.pager(b'export')
2835 ui.pager(b'export')
2835 fm = ui.formatter(b'export', opts)
2836 fm = ui.formatter(b'export', opts)
2836 with fm:
2837 with fm:
2837 cmdutil.export(
2838 cmdutil.export(
2838 repo,
2839 repo,
2839 revs,
2840 revs,
2840 fm,
2841 fm,
2841 fntemplate=fntemplate,
2842 fntemplate=fntemplate,
2842 switch_parent=opts.get(b'switch_parent'),
2843 switch_parent=opts.get(b'switch_parent'),
2843 opts=patch.diffallopts(ui, opts),
2844 opts=patch.diffallopts(ui, opts),
2844 )
2845 )
2845
2846
2846
2847
2847 @command(
2848 @command(
2848 b'files',
2849 b'files',
2849 [
2850 [
2850 (
2851 (
2851 b'r',
2852 b'r',
2852 b'rev',
2853 b'rev',
2853 b'',
2854 b'',
2854 _(b'search the repository as it is in REV'),
2855 _(b'search the repository as it is in REV'),
2855 _(b'REV'),
2856 _(b'REV'),
2856 ),
2857 ),
2857 (
2858 (
2858 b'0',
2859 b'0',
2859 b'print0',
2860 b'print0',
2860 None,
2861 None,
2861 _(b'end filenames with NUL, for use with xargs'),
2862 _(b'end filenames with NUL, for use with xargs'),
2862 ),
2863 ),
2863 ]
2864 ]
2864 + walkopts
2865 + walkopts
2865 + formatteropts
2866 + formatteropts
2866 + subrepoopts,
2867 + subrepoopts,
2867 _(b'[OPTION]... [FILE]...'),
2868 _(b'[OPTION]... [FILE]...'),
2868 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2869 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2869 intents={INTENT_READONLY},
2870 intents={INTENT_READONLY},
2870 )
2871 )
2871 def files(ui, repo, *pats, **opts):
2872 def files(ui, repo, *pats, **opts):
2872 """list tracked files
2873 """list tracked files
2873
2874
2874 Print files under Mercurial control in the working directory or
2875 Print files under Mercurial control in the working directory or
2875 specified revision for given files (excluding removed files).
2876 specified revision for given files (excluding removed files).
2876 Files can be specified as filenames or filesets.
2877 Files can be specified as filenames or filesets.
2877
2878
2878 If no files are given to match, this command prints the names
2879 If no files are given to match, this command prints the names
2879 of all files under Mercurial control.
2880 of all files under Mercurial control.
2880
2881
2881 .. container:: verbose
2882 .. container:: verbose
2882
2883
2883 Template:
2884 Template:
2884
2885
2885 The following keywords are supported in addition to the common template
2886 The following keywords are supported in addition to the common template
2886 keywords and functions. See also :hg:`help templates`.
2887 keywords and functions. See also :hg:`help templates`.
2887
2888
2888 :flags: String. Character denoting file's symlink and executable bits.
2889 :flags: String. Character denoting file's symlink and executable bits.
2889 :path: String. Repository-absolute path of the file.
2890 :path: String. Repository-absolute path of the file.
2890 :size: Integer. Size of the file in bytes.
2891 :size: Integer. Size of the file in bytes.
2891
2892
2892 Examples:
2893 Examples:
2893
2894
2894 - list all files under the current directory::
2895 - list all files under the current directory::
2895
2896
2896 hg files .
2897 hg files .
2897
2898
2898 - shows sizes and flags for current revision::
2899 - shows sizes and flags for current revision::
2899
2900
2900 hg files -vr .
2901 hg files -vr .
2901
2902
2902 - list all files named README::
2903 - list all files named README::
2903
2904
2904 hg files -I "**/README"
2905 hg files -I "**/README"
2905
2906
2906 - list all binary files::
2907 - list all binary files::
2907
2908
2908 hg files "set:binary()"
2909 hg files "set:binary()"
2909
2910
2910 - find files containing a regular expression::
2911 - find files containing a regular expression::
2911
2912
2912 hg files "set:grep('bob')"
2913 hg files "set:grep('bob')"
2913
2914
2914 - search tracked file contents with xargs and grep::
2915 - search tracked file contents with xargs and grep::
2915
2916
2916 hg files -0 | xargs -0 grep foo
2917 hg files -0 | xargs -0 grep foo
2917
2918
2918 See :hg:`help patterns` and :hg:`help filesets` for more information
2919 See :hg:`help patterns` and :hg:`help filesets` for more information
2919 on specifying file patterns.
2920 on specifying file patterns.
2920
2921
2921 Returns 0 if a match is found, 1 otherwise.
2922 Returns 0 if a match is found, 1 otherwise.
2922
2923
2923 """
2924 """
2924
2925
2925 opts = pycompat.byteskwargs(opts)
2926 opts = pycompat.byteskwargs(opts)
2926 rev = opts.get(b'rev')
2927 rev = opts.get(b'rev')
2927 if rev:
2928 if rev:
2928 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2929 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2929 ctx = logcmdutil.revsingle(repo, rev, None)
2930 ctx = logcmdutil.revsingle(repo, rev, None)
2930
2931
2931 end = b'\n'
2932 end = b'\n'
2932 if opts.get(b'print0'):
2933 if opts.get(b'print0'):
2933 end = b'\0'
2934 end = b'\0'
2934 fmt = b'%s' + end
2935 fmt = b'%s' + end
2935
2936
2936 m = scmutil.match(ctx, pats, opts)
2937 m = scmutil.match(ctx, pats, opts)
2937 ui.pager(b'files')
2938 ui.pager(b'files')
2938 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2939 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2939 with ui.formatter(b'files', opts) as fm:
2940 with ui.formatter(b'files', opts) as fm:
2940 return cmdutil.files(
2941 return cmdutil.files(
2941 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2942 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2942 )
2943 )
2943
2944
2944
2945
2945 @command(
2946 @command(
2946 b'forget',
2947 b'forget',
2947 [
2948 [
2948 (b'i', b'interactive', None, _(b'use interactive mode')),
2949 (b'i', b'interactive', None, _(b'use interactive mode')),
2949 ]
2950 ]
2950 + walkopts
2951 + walkopts
2951 + dryrunopts,
2952 + dryrunopts,
2952 _(b'[OPTION]... FILE...'),
2953 _(b'[OPTION]... FILE...'),
2953 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2954 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2954 helpbasic=True,
2955 helpbasic=True,
2955 inferrepo=True,
2956 inferrepo=True,
2956 )
2957 )
2957 def forget(ui, repo, *pats, **opts):
2958 def forget(ui, repo, *pats, **opts):
2958 """forget the specified files on the next commit
2959 """forget the specified files on the next commit
2959
2960
2960 Mark the specified files so they will no longer be tracked
2961 Mark the specified files so they will no longer be tracked
2961 after the next commit.
2962 after the next commit.
2962
2963
2963 This only removes files from the current branch, not from the
2964 This only removes files from the current branch, not from the
2964 entire project history, and it does not delete them from the
2965 entire project history, and it does not delete them from the
2965 working directory.
2966 working directory.
2966
2967
2967 To delete the file from the working directory, see :hg:`remove`.
2968 To delete the file from the working directory, see :hg:`remove`.
2968
2969
2969 To undo a forget before the next commit, see :hg:`add`.
2970 To undo a forget before the next commit, see :hg:`add`.
2970
2971
2971 .. container:: verbose
2972 .. container:: verbose
2972
2973
2973 Examples:
2974 Examples:
2974
2975
2975 - forget newly-added binary files::
2976 - forget newly-added binary files::
2976
2977
2977 hg forget "set:added() and binary()"
2978 hg forget "set:added() and binary()"
2978
2979
2979 - forget files that would be excluded by .hgignore::
2980 - forget files that would be excluded by .hgignore::
2980
2981
2981 hg forget "set:hgignore()"
2982 hg forget "set:hgignore()"
2982
2983
2983 Returns 0 on success.
2984 Returns 0 on success.
2984 """
2985 """
2985
2986
2986 opts = pycompat.byteskwargs(opts)
2987 opts = pycompat.byteskwargs(opts)
2987 if not pats:
2988 if not pats:
2988 raise error.InputError(_(b'no files specified'))
2989 raise error.InputError(_(b'no files specified'))
2989
2990
2990 with repo.wlock(), repo.dirstate.changing_files(repo):
2991 with repo.wlock(), repo.dirstate.changing_files(repo):
2991 m = scmutil.match(repo[None], pats, opts)
2992 m = scmutil.match(repo[None], pats, opts)
2992 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2993 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2993 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2994 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2994 rejected = cmdutil.forget(
2995 rejected = cmdutil.forget(
2995 ui,
2996 ui,
2996 repo,
2997 repo,
2997 m,
2998 m,
2998 prefix=b"",
2999 prefix=b"",
2999 uipathfn=uipathfn,
3000 uipathfn=uipathfn,
3000 explicitonly=False,
3001 explicitonly=False,
3001 dryrun=dryrun,
3002 dryrun=dryrun,
3002 interactive=interactive,
3003 interactive=interactive,
3003 )[0]
3004 )[0]
3004 return rejected and 1 or 0
3005 return rejected and 1 or 0
3005
3006
3006
3007
3007 @command(
3008 @command(
3008 b'graft',
3009 b'graft',
3009 [
3010 [
3010 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
3011 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
3011 (
3012 (
3012 b'',
3013 b'',
3013 b'base',
3014 b'base',
3014 b'',
3015 b'',
3015 _(b'base revision when doing the graft merge (ADVANCED)'),
3016 _(b'base revision when doing the graft merge (ADVANCED)'),
3016 _(b'REV'),
3017 _(b'REV'),
3017 ),
3018 ),
3018 (b'c', b'continue', False, _(b'resume interrupted graft')),
3019 (b'c', b'continue', False, _(b'resume interrupted graft')),
3019 (b'', b'stop', False, _(b'stop interrupted graft')),
3020 (b'', b'stop', False, _(b'stop interrupted graft')),
3020 (b'', b'abort', False, _(b'abort interrupted graft')),
3021 (b'', b'abort', False, _(b'abort interrupted graft')),
3021 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3022 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3022 (b'', b'log', None, _(b'append graft info to log message')),
3023 (b'', b'log', None, _(b'append graft info to log message')),
3023 (
3024 (
3024 b'',
3025 b'',
3025 b'no-commit',
3026 b'no-commit',
3026 None,
3027 None,
3027 _(b"don't commit, just apply the changes in working directory"),
3028 _(b"don't commit, just apply the changes in working directory"),
3028 ),
3029 ),
3029 (b'f', b'force', False, _(b'force graft')),
3030 (b'f', b'force', False, _(b'force graft')),
3030 (
3031 (
3031 b'D',
3032 b'D',
3032 b'currentdate',
3033 b'currentdate',
3033 False,
3034 False,
3034 _(b'record the current date as commit date'),
3035 _(b'record the current date as commit date'),
3035 ),
3036 ),
3036 (
3037 (
3037 b'U',
3038 b'U',
3038 b'currentuser',
3039 b'currentuser',
3039 False,
3040 False,
3040 _(b'record the current user as committer'),
3041 _(b'record the current user as committer'),
3041 ),
3042 ),
3042 ]
3043 ]
3043 + commitopts2
3044 + commitopts2
3044 + mergetoolopts
3045 + mergetoolopts
3045 + dryrunopts,
3046 + dryrunopts,
3046 _(b'[OPTION]... [-r REV]... REV...'),
3047 _(b'[OPTION]... [-r REV]... REV...'),
3047 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3048 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3048 )
3049 )
3049 def graft(ui, repo, *revs, **opts):
3050 def graft(ui, repo, *revs, **opts):
3050 """copy changes from other branches onto the current branch
3051 """copy changes from other branches onto the current branch
3051
3052
3052 This command uses Mercurial's merge logic to copy individual
3053 This command uses Mercurial's merge logic to copy individual
3053 changes from other branches without merging branches in the
3054 changes from other branches without merging branches in the
3054 history graph. This is sometimes known as 'backporting' or
3055 history graph. This is sometimes known as 'backporting' or
3055 'cherry-picking'. By default, graft will copy user, date, and
3056 'cherry-picking'. By default, graft will copy user, date, and
3056 description from the source changesets.
3057 description from the source changesets.
3057
3058
3058 Changesets that are ancestors of the current revision, that have
3059 Changesets that are ancestors of the current revision, that have
3059 already been grafted, or that are merges will be skipped.
3060 already been grafted, or that are merges will be skipped.
3060
3061
3061 If --log is specified, log messages will have a comment appended
3062 If --log is specified, log messages will have a comment appended
3062 of the form::
3063 of the form::
3063
3064
3064 (grafted from CHANGESETHASH)
3065 (grafted from CHANGESETHASH)
3065
3066
3066 If --force is specified, revisions will be grafted even if they
3067 If --force is specified, revisions will be grafted even if they
3067 are already ancestors of, or have been grafted to, the destination.
3068 are already ancestors of, or have been grafted to, the destination.
3068 This is useful when the revisions have since been backed out.
3069 This is useful when the revisions have since been backed out.
3069
3070
3070 If a graft merge results in conflicts, the graft process is
3071 If a graft merge results in conflicts, the graft process is
3071 interrupted so that the current merge can be manually resolved.
3072 interrupted so that the current merge can be manually resolved.
3072 Once all conflicts are addressed, the graft process can be
3073 Once all conflicts are addressed, the graft process can be
3073 continued with the -c/--continue option.
3074 continued with the -c/--continue option.
3074
3075
3075 The -c/--continue option reapplies all the earlier options.
3076 The -c/--continue option reapplies all the earlier options.
3076
3077
3077 .. container:: verbose
3078 .. container:: verbose
3078
3079
3079 The --base option exposes more of how graft internally uses merge with a
3080 The --base option exposes more of how graft internally uses merge with a
3080 custom base revision. --base can be used to specify another ancestor than
3081 custom base revision. --base can be used to specify another ancestor than
3081 the first and only parent.
3082 the first and only parent.
3082
3083
3083 The command::
3084 The command::
3084
3085
3085 hg graft -r 345 --base 234
3086 hg graft -r 345 --base 234
3086
3087
3087 is thus pretty much the same as::
3088 is thus pretty much the same as::
3088
3089
3089 hg diff --from 234 --to 345 | hg import
3090 hg diff --from 234 --to 345 | hg import
3090
3091
3091 but using merge to resolve conflicts and track moved files.
3092 but using merge to resolve conflicts and track moved files.
3092
3093
3093 The result of a merge can thus be backported as a single commit by
3094 The result of a merge can thus be backported as a single commit by
3094 specifying one of the merge parents as base, and thus effectively
3095 specifying one of the merge parents as base, and thus effectively
3095 grafting the changes from the other side.
3096 grafting the changes from the other side.
3096
3097
3097 It is also possible to collapse multiple changesets and clean up history
3098 It is also possible to collapse multiple changesets and clean up history
3098 by specifying another ancestor as base, much like rebase --collapse
3099 by specifying another ancestor as base, much like rebase --collapse
3099 --keep.
3100 --keep.
3100
3101
3101 The commit message can be tweaked after the fact using commit --amend .
3102 The commit message can be tweaked after the fact using commit --amend .
3102
3103
3103 For using non-ancestors as the base to backout changes, see the backout
3104 For using non-ancestors as the base to backout changes, see the backout
3104 command and the hidden --parent option.
3105 command and the hidden --parent option.
3105
3106
3106 .. container:: verbose
3107 .. container:: verbose
3107
3108
3108 Examples:
3109 Examples:
3109
3110
3110 - copy a single change to the stable branch and edit its description::
3111 - copy a single change to the stable branch and edit its description::
3111
3112
3112 hg update stable
3113 hg update stable
3113 hg graft --edit 9393
3114 hg graft --edit 9393
3114
3115
3115 - graft a range of changesets with one exception, updating dates::
3116 - graft a range of changesets with one exception, updating dates::
3116
3117
3117 hg graft -D "2085::2093 and not 2091"
3118 hg graft -D "2085::2093 and not 2091"
3118
3119
3119 - continue a graft after resolving conflicts::
3120 - continue a graft after resolving conflicts::
3120
3121
3121 hg graft -c
3122 hg graft -c
3122
3123
3123 - show the source of a grafted changeset::
3124 - show the source of a grafted changeset::
3124
3125
3125 hg log --debug -r .
3126 hg log --debug -r .
3126
3127
3127 - show revisions sorted by date::
3128 - show revisions sorted by date::
3128
3129
3129 hg log -r "sort(all(), date)"
3130 hg log -r "sort(all(), date)"
3130
3131
3131 - backport the result of a merge as a single commit::
3132 - backport the result of a merge as a single commit::
3132
3133
3133 hg graft -r 123 --base 123^
3134 hg graft -r 123 --base 123^
3134
3135
3135 - land a feature branch as one changeset::
3136 - land a feature branch as one changeset::
3136
3137
3137 hg up -cr default
3138 hg up -cr default
3138 hg graft -r featureX --base "ancestor('featureX', 'default')"
3139 hg graft -r featureX --base "ancestor('featureX', 'default')"
3139
3140
3140 See :hg:`help revisions` for more about specifying revisions.
3141 See :hg:`help revisions` for more about specifying revisions.
3141
3142
3142 Returns 0 on successful completion, 1 if there are unresolved files.
3143 Returns 0 on successful completion, 1 if there are unresolved files.
3143 """
3144 """
3144 with repo.wlock():
3145 with repo.wlock():
3145 return _dograft(ui, repo, *revs, **opts)
3146 return _dograft(ui, repo, *revs, **opts)
3146
3147
3147
3148
3148 def _dograft(ui, repo, *revs, **opts):
3149 def _dograft(ui, repo, *revs, **opts):
3149 if revs and opts.get('rev'):
3150 if revs and opts.get('rev'):
3150 ui.warn(
3151 ui.warn(
3151 _(
3152 _(
3152 b'warning: inconsistent use of --rev might give unexpected '
3153 b'warning: inconsistent use of --rev might give unexpected '
3153 b'revision ordering!\n'
3154 b'revision ordering!\n'
3154 )
3155 )
3155 )
3156 )
3156
3157
3157 revs = list(revs)
3158 revs = list(revs)
3158 revs.extend(opts.get('rev'))
3159 revs.extend(opts.get('rev'))
3159 # a dict of data to be stored in state file
3160 # a dict of data to be stored in state file
3160 statedata = {}
3161 statedata = {}
3161 # list of new nodes created by ongoing graft
3162 # list of new nodes created by ongoing graft
3162 statedata[b'newnodes'] = []
3163 statedata[b'newnodes'] = []
3163
3164
3164 cmdutil.resolve_commit_options(ui, opts)
3165 cmdutil.resolve_commit_options(ui, opts)
3165
3166
3166 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3167 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3167
3168
3168 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3169 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3169
3170
3170 cont = False
3171 cont = False
3171 if opts.get('no_commit'):
3172 if opts.get('no_commit'):
3172 cmdutil.check_incompatible_arguments(
3173 cmdutil.check_incompatible_arguments(
3173 opts,
3174 opts,
3174 'no_commit',
3175 'no_commit',
3175 ['edit', 'currentuser', 'currentdate', 'log'],
3176 ['edit', 'currentuser', 'currentdate', 'log'],
3176 )
3177 )
3177
3178
3178 graftstate = statemod.cmdstate(repo, b'graftstate')
3179 graftstate = statemod.cmdstate(repo, b'graftstate')
3179
3180
3180 if opts.get('stop'):
3181 if opts.get('stop'):
3181 cmdutil.check_incompatible_arguments(
3182 cmdutil.check_incompatible_arguments(
3182 opts,
3183 opts,
3183 'stop',
3184 'stop',
3184 [
3185 [
3185 'edit',
3186 'edit',
3186 'log',
3187 'log',
3187 'user',
3188 'user',
3188 'date',
3189 'date',
3189 'currentdate',
3190 'currentdate',
3190 'currentuser',
3191 'currentuser',
3191 'rev',
3192 'rev',
3192 ],
3193 ],
3193 )
3194 )
3194 return _stopgraft(ui, repo, graftstate)
3195 return _stopgraft(ui, repo, graftstate)
3195 elif opts.get('abort'):
3196 elif opts.get('abort'):
3196 cmdutil.check_incompatible_arguments(
3197 cmdutil.check_incompatible_arguments(
3197 opts,
3198 opts,
3198 'abort',
3199 'abort',
3199 [
3200 [
3200 'edit',
3201 'edit',
3201 'log',
3202 'log',
3202 'user',
3203 'user',
3203 'date',
3204 'date',
3204 'currentdate',
3205 'currentdate',
3205 'currentuser',
3206 'currentuser',
3206 'rev',
3207 'rev',
3207 ],
3208 ],
3208 )
3209 )
3209 return cmdutil.abortgraft(ui, repo, graftstate)
3210 return cmdutil.abortgraft(ui, repo, graftstate)
3210 elif opts.get('continue'):
3211 elif opts.get('continue'):
3211 cont = True
3212 cont = True
3212 if revs:
3213 if revs:
3213 raise error.InputError(_(b"can't specify --continue and revisions"))
3214 raise error.InputError(_(b"can't specify --continue and revisions"))
3214 # read in unfinished revisions
3215 # read in unfinished revisions
3215 if graftstate.exists():
3216 if graftstate.exists():
3216 statedata = cmdutil.readgraftstate(repo, graftstate)
3217 statedata = cmdutil.readgraftstate(repo, graftstate)
3217 if statedata.get(b'date'):
3218 if statedata.get(b'date'):
3218 opts['date'] = statedata[b'date']
3219 opts['date'] = statedata[b'date']
3219 if statedata.get(b'user'):
3220 if statedata.get(b'user'):
3220 opts['user'] = statedata[b'user']
3221 opts['user'] = statedata[b'user']
3221 if statedata.get(b'log'):
3222 if statedata.get(b'log'):
3222 opts['log'] = True
3223 opts['log'] = True
3223 if statedata.get(b'no_commit'):
3224 if statedata.get(b'no_commit'):
3224 opts['no_commit'] = statedata.get(b'no_commit')
3225 opts['no_commit'] = statedata.get(b'no_commit')
3225 if statedata.get(b'base'):
3226 if statedata.get(b'base'):
3226 opts['base'] = statedata.get(b'base')
3227 opts['base'] = statedata.get(b'base')
3227 nodes = statedata[b'nodes']
3228 nodes = statedata[b'nodes']
3228 revs = [repo[node].rev() for node in nodes]
3229 revs = [repo[node].rev() for node in nodes]
3229 else:
3230 else:
3230 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3231 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3231 else:
3232 else:
3232 if not revs:
3233 if not revs:
3233 raise error.InputError(_(b'no revisions specified'))
3234 raise error.InputError(_(b'no revisions specified'))
3234 cmdutil.checkunfinished(repo)
3235 cmdutil.checkunfinished(repo)
3235 cmdutil.bailifchanged(repo)
3236 cmdutil.bailifchanged(repo)
3236 revs = logcmdutil.revrange(repo, revs)
3237 revs = logcmdutil.revrange(repo, revs)
3237
3238
3238 skipped = set()
3239 skipped = set()
3239 basectx = None
3240 basectx = None
3240 if opts.get('base'):
3241 if opts.get('base'):
3241 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3242 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3242 if basectx is None:
3243 if basectx is None:
3243 # check for merges
3244 # check for merges
3244 for rev in repo.revs(b'%ld and merge()', revs):
3245 for rev in repo.revs(b'%ld and merge()', revs):
3245 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3246 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3246 skipped.add(rev)
3247 skipped.add(rev)
3247 revs = [r for r in revs if r not in skipped]
3248 revs = [r for r in revs if r not in skipped]
3248 if not revs:
3249 if not revs:
3249 return -1
3250 return -1
3250 if basectx is not None and len(revs) != 1:
3251 if basectx is not None and len(revs) != 1:
3251 raise error.InputError(_(b'only one revision allowed with --base '))
3252 raise error.InputError(_(b'only one revision allowed with --base '))
3252
3253
3253 # Don't check in the --continue case, in effect retaining --force across
3254 # Don't check in the --continue case, in effect retaining --force across
3254 # --continues. That's because without --force, any revisions we decided to
3255 # --continues. That's because without --force, any revisions we decided to
3255 # skip would have been filtered out here, so they wouldn't have made their
3256 # skip would have been filtered out here, so they wouldn't have made their
3256 # way to the graftstate. With --force, any revisions we would have otherwise
3257 # way to the graftstate. With --force, any revisions we would have otherwise
3257 # skipped would not have been filtered out, and if they hadn't been applied
3258 # skipped would not have been filtered out, and if they hadn't been applied
3258 # already, they'd have been in the graftstate.
3259 # already, they'd have been in the graftstate.
3259 if not (cont or opts.get('force')) and basectx is None:
3260 if not (cont or opts.get('force')) and basectx is None:
3260 # check for ancestors of dest branch
3261 # check for ancestors of dest branch
3261 ancestors = repo.revs(b'%ld & (::.)', revs)
3262 ancestors = repo.revs(b'%ld & (::.)', revs)
3262 for rev in ancestors:
3263 for rev in ancestors:
3263 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3264 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3264
3265
3265 revs = [r for r in revs if r not in ancestors]
3266 revs = [r for r in revs if r not in ancestors]
3266
3267
3267 if not revs:
3268 if not revs:
3268 return -1
3269 return -1
3269
3270
3270 # analyze revs for earlier grafts
3271 # analyze revs for earlier grafts
3271 ids = {}
3272 ids = {}
3272 for ctx in repo.set(b"%ld", revs):
3273 for ctx in repo.set(b"%ld", revs):
3273 ids[ctx.hex()] = ctx.rev()
3274 ids[ctx.hex()] = ctx.rev()
3274 n = ctx.extra().get(b'source')
3275 n = ctx.extra().get(b'source')
3275 if n:
3276 if n:
3276 ids[n] = ctx.rev()
3277 ids[n] = ctx.rev()
3277
3278
3278 # check ancestors for earlier grafts
3279 # check ancestors for earlier grafts
3279 ui.debug(b'scanning for duplicate grafts\n')
3280 ui.debug(b'scanning for duplicate grafts\n')
3280
3281
3281 # The only changesets we can be sure doesn't contain grafts of any
3282 # The only changesets we can be sure doesn't contain grafts of any
3282 # revs, are the ones that are common ancestors of *all* revs:
3283 # revs, are the ones that are common ancestors of *all* revs:
3283 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3284 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3284 ctx = repo[rev]
3285 ctx = repo[rev]
3285 n = ctx.extra().get(b'source')
3286 n = ctx.extra().get(b'source')
3286 if n in ids:
3287 if n in ids:
3287 try:
3288 try:
3288 r = repo[n].rev()
3289 r = repo[n].rev()
3289 except error.RepoLookupError:
3290 except error.RepoLookupError:
3290 r = None
3291 r = None
3291 if r in revs:
3292 if r in revs:
3292 ui.warn(
3293 ui.warn(
3293 _(
3294 _(
3294 b'skipping revision %d:%s '
3295 b'skipping revision %d:%s '
3295 b'(already grafted to %d:%s)\n'
3296 b'(already grafted to %d:%s)\n'
3296 )
3297 )
3297 % (r, repo[r], rev, ctx)
3298 % (r, repo[r], rev, ctx)
3298 )
3299 )
3299 revs.remove(r)
3300 revs.remove(r)
3300 elif ids[n] in revs:
3301 elif ids[n] in revs:
3301 if r is None:
3302 if r is None:
3302 ui.warn(
3303 ui.warn(
3303 _(
3304 _(
3304 b'skipping already grafted revision %d:%s '
3305 b'skipping already grafted revision %d:%s '
3305 b'(%d:%s also has unknown origin %s)\n'
3306 b'(%d:%s also has unknown origin %s)\n'
3306 )
3307 )
3307 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3308 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3308 )
3309 )
3309 else:
3310 else:
3310 ui.warn(
3311 ui.warn(
3311 _(
3312 _(
3312 b'skipping already grafted revision %d:%s '
3313 b'skipping already grafted revision %d:%s '
3313 b'(%d:%s also has origin %d:%s)\n'
3314 b'(%d:%s also has origin %d:%s)\n'
3314 )
3315 )
3315 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3316 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3316 )
3317 )
3317 revs.remove(ids[n])
3318 revs.remove(ids[n])
3318 elif ctx.hex() in ids:
3319 elif ctx.hex() in ids:
3319 r = ids[ctx.hex()]
3320 r = ids[ctx.hex()]
3320 if r in revs:
3321 if r in revs:
3321 ui.warn(
3322 ui.warn(
3322 _(
3323 _(
3323 b'skipping already grafted revision %d:%s '
3324 b'skipping already grafted revision %d:%s '
3324 b'(was grafted from %d:%s)\n'
3325 b'(was grafted from %d:%s)\n'
3325 )
3326 )
3326 % (r, repo[r], rev, ctx)
3327 % (r, repo[r], rev, ctx)
3327 )
3328 )
3328 revs.remove(r)
3329 revs.remove(r)
3329 if not revs:
3330 if not revs:
3330 return -1
3331 return -1
3331
3332
3332 if opts.get('no_commit'):
3333 if opts.get('no_commit'):
3333 statedata[b'no_commit'] = True
3334 statedata[b'no_commit'] = True
3334 if opts.get('base'):
3335 if opts.get('base'):
3335 statedata[b'base'] = opts['base']
3336 statedata[b'base'] = opts['base']
3336 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3337 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3337 desc = b'%d:%s "%s"' % (
3338 desc = b'%d:%s "%s"' % (
3338 ctx.rev(),
3339 ctx.rev(),
3339 ctx,
3340 ctx,
3340 ctx.description().split(b'\n', 1)[0],
3341 ctx.description().split(b'\n', 1)[0],
3341 )
3342 )
3342 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3343 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3343 if names:
3344 if names:
3344 desc += b' (%s)' % b' '.join(names)
3345 desc += b' (%s)' % b' '.join(names)
3345 ui.status(_(b'grafting %s\n') % desc)
3346 ui.status(_(b'grafting %s\n') % desc)
3346 if opts.get('dry_run'):
3347 if opts.get('dry_run'):
3347 continue
3348 continue
3348
3349
3349 source = ctx.extra().get(b'source')
3350 source = ctx.extra().get(b'source')
3350 extra = {}
3351 extra = {}
3351 if source:
3352 if source:
3352 extra[b'source'] = source
3353 extra[b'source'] = source
3353 extra[b'intermediate-source'] = ctx.hex()
3354 extra[b'intermediate-source'] = ctx.hex()
3354 else:
3355 else:
3355 extra[b'source'] = ctx.hex()
3356 extra[b'source'] = ctx.hex()
3356 user = ctx.user()
3357 user = ctx.user()
3357 if opts.get('user'):
3358 if opts.get('user'):
3358 user = opts['user']
3359 user = opts['user']
3359 statedata[b'user'] = user
3360 statedata[b'user'] = user
3360 date = ctx.date()
3361 date = ctx.date()
3361 if opts.get('date'):
3362 if opts.get('date'):
3362 date = opts['date']
3363 date = opts['date']
3363 statedata[b'date'] = date
3364 statedata[b'date'] = date
3364 message = ctx.description()
3365 message = ctx.description()
3365 if opts.get('log'):
3366 if opts.get('log'):
3366 message += b'\n(grafted from %s)' % ctx.hex()
3367 message += b'\n(grafted from %s)' % ctx.hex()
3367 statedata[b'log'] = True
3368 statedata[b'log'] = True
3368
3369
3369 # we don't merge the first commit when continuing
3370 # we don't merge the first commit when continuing
3370 if not cont:
3371 if not cont:
3371 # perform the graft merge with p1(rev) as 'ancestor'
3372 # perform the graft merge with p1(rev) as 'ancestor'
3372 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3373 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3373 base = ctx.p1() if basectx is None else basectx
3374 base = ctx.p1() if basectx is None else basectx
3374 with ui.configoverride(overrides, b'graft'):
3375 with ui.configoverride(overrides, b'graft'):
3375 stats = mergemod.graft(
3376 stats = mergemod.graft(
3376 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3377 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3377 )
3378 )
3378 # report any conflicts
3379 # report any conflicts
3379 if stats.unresolvedcount > 0:
3380 if stats.unresolvedcount > 0:
3380 # write out state for --continue
3381 # write out state for --continue
3381 nodes = [repo[rev].hex() for rev in revs[pos:]]
3382 nodes = [repo[rev].hex() for rev in revs[pos:]]
3382 statedata[b'nodes'] = nodes
3383 statedata[b'nodes'] = nodes
3383 stateversion = 1
3384 stateversion = 1
3384 graftstate.save(stateversion, statedata)
3385 graftstate.save(stateversion, statedata)
3385 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3386 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3386 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3387 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3387 return 1
3388 return 1
3388 else:
3389 else:
3389 cont = False
3390 cont = False
3390
3391
3391 # commit if --no-commit is false
3392 # commit if --no-commit is false
3392 if not opts.get('no_commit'):
3393 if not opts.get('no_commit'):
3393 node = repo.commit(
3394 node = repo.commit(
3394 text=message, user=user, date=date, extra=extra, editor=editor
3395 text=message, user=user, date=date, extra=extra, editor=editor
3395 )
3396 )
3396 if node is None:
3397 if node is None:
3397 ui.warn(
3398 ui.warn(
3398 _(b'note: graft of %d:%s created no changes to commit\n')
3399 _(b'note: graft of %d:%s created no changes to commit\n')
3399 % (ctx.rev(), ctx)
3400 % (ctx.rev(), ctx)
3400 )
3401 )
3401 # checking that newnodes exist because old state files won't have it
3402 # checking that newnodes exist because old state files won't have it
3402 elif statedata.get(b'newnodes') is not None:
3403 elif statedata.get(b'newnodes') is not None:
3403 nn = statedata[b'newnodes']
3404 nn = statedata[b'newnodes']
3404 assert isinstance(nn, list) # list of bytes
3405 assert isinstance(nn, list) # list of bytes
3405 nn.append(node)
3406 nn.append(node)
3406
3407
3407 # remove state when we complete successfully
3408 # remove state when we complete successfully
3408 if not opts.get('dry_run'):
3409 if not opts.get('dry_run'):
3409 graftstate.delete()
3410 graftstate.delete()
3410
3411
3411 return 0
3412 return 0
3412
3413
3413
3414
3414 def _stopgraft(ui, repo, graftstate):
3415 def _stopgraft(ui, repo, graftstate):
3415 """stop the interrupted graft"""
3416 """stop the interrupted graft"""
3416 if not graftstate.exists():
3417 if not graftstate.exists():
3417 raise error.StateError(_(b"no interrupted graft found"))
3418 raise error.StateError(_(b"no interrupted graft found"))
3418 pctx = repo[b'.']
3419 pctx = repo[b'.']
3419 mergemod.clean_update(pctx)
3420 mergemod.clean_update(pctx)
3420 graftstate.delete()
3421 graftstate.delete()
3421 ui.status(_(b"stopped the interrupted graft\n"))
3422 ui.status(_(b"stopped the interrupted graft\n"))
3422 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3423 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3423 return 0
3424 return 0
3424
3425
3425
3426
3426 statemod.addunfinished(
3427 statemod.addunfinished(
3427 b'graft',
3428 b'graft',
3428 fname=b'graftstate',
3429 fname=b'graftstate',
3429 clearable=True,
3430 clearable=True,
3430 stopflag=True,
3431 stopflag=True,
3431 continueflag=True,
3432 continueflag=True,
3432 abortfunc=cmdutil.hgabortgraft,
3433 abortfunc=cmdutil.hgabortgraft,
3433 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3434 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3434 )
3435 )
3435
3436
3436
3437
3437 @command(
3438 @command(
3438 b'grep',
3439 b'grep',
3439 [
3440 [
3440 (b'0', b'print0', None, _(b'end fields with NUL')),
3441 (b'0', b'print0', None, _(b'end fields with NUL')),
3441 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3442 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3442 (
3443 (
3443 b'',
3444 b'',
3444 b'diff',
3445 b'diff',
3445 None,
3446 None,
3446 _(
3447 _(
3447 b'search revision differences for when the pattern was added '
3448 b'search revision differences for when the pattern was added '
3448 b'or removed'
3449 b'or removed'
3449 ),
3450 ),
3450 ),
3451 ),
3451 (b'a', b'text', None, _(b'treat all files as text')),
3452 (b'a', b'text', None, _(b'treat all files as text')),
3452 (
3453 (
3453 b'f',
3454 b'f',
3454 b'follow',
3455 b'follow',
3455 None,
3456 None,
3456 _(
3457 _(
3457 b'follow changeset history,'
3458 b'follow changeset history,'
3458 b' or file history across copies and renames'
3459 b' or file history across copies and renames'
3459 ),
3460 ),
3460 ),
3461 ),
3461 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3462 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3462 (
3463 (
3463 b'l',
3464 b'l',
3464 b'files-with-matches',
3465 b'files-with-matches',
3465 None,
3466 None,
3466 _(b'print only filenames and revisions that match'),
3467 _(b'print only filenames and revisions that match'),
3467 ),
3468 ),
3468 (b'n', b'line-number', None, _(b'print matching line numbers')),
3469 (b'n', b'line-number', None, _(b'print matching line numbers')),
3469 (
3470 (
3470 b'r',
3471 b'r',
3471 b'rev',
3472 b'rev',
3472 [],
3473 [],
3473 _(b'search files changed within revision range'),
3474 _(b'search files changed within revision range'),
3474 _(b'REV'),
3475 _(b'REV'),
3475 ),
3476 ),
3476 (
3477 (
3477 b'',
3478 b'',
3478 b'all-files',
3479 b'all-files',
3479 None,
3480 None,
3480 _(
3481 _(
3481 b'include all files in the changeset while grepping (DEPRECATED)'
3482 b'include all files in the changeset while grepping (DEPRECATED)'
3482 ),
3483 ),
3483 ),
3484 ),
3484 (b'u', b'user', None, _(b'list the author (long with -v)')),
3485 (b'u', b'user', None, _(b'list the author (long with -v)')),
3485 (b'd', b'date', None, _(b'list the date (short with -q)')),
3486 (b'd', b'date', None, _(b'list the date (short with -q)')),
3486 ]
3487 ]
3487 + formatteropts
3488 + formatteropts
3488 + walkopts,
3489 + walkopts,
3489 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3490 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3490 helpcategory=command.CATEGORY_FILE_CONTENTS,
3491 helpcategory=command.CATEGORY_FILE_CONTENTS,
3491 inferrepo=True,
3492 inferrepo=True,
3492 intents={INTENT_READONLY},
3493 intents={INTENT_READONLY},
3493 )
3494 )
3494 def grep(ui, repo, pattern, *pats, **opts):
3495 def grep(ui, repo, pattern, *pats, **opts):
3495 """search for a pattern in specified files
3496 """search for a pattern in specified files
3496
3497
3497 Search the working directory or revision history for a regular
3498 Search the working directory or revision history for a regular
3498 expression in the specified files for the entire repository.
3499 expression in the specified files for the entire repository.
3499
3500
3500 By default, grep searches the repository files in the working
3501 By default, grep searches the repository files in the working
3501 directory and prints the files where it finds a match. To specify
3502 directory and prints the files where it finds a match. To specify
3502 historical revisions instead of the working directory, use the
3503 historical revisions instead of the working directory, use the
3503 --rev flag.
3504 --rev flag.
3504
3505
3505 To search instead historical revision differences that contains a
3506 To search instead historical revision differences that contains a
3506 change in match status ("-" for a match that becomes a non-match,
3507 change in match status ("-" for a match that becomes a non-match,
3507 or "+" for a non-match that becomes a match), use the --diff flag.
3508 or "+" for a non-match that becomes a match), use the --diff flag.
3508
3509
3509 PATTERN can be any Python (roughly Perl-compatible) regular
3510 PATTERN can be any Python (roughly Perl-compatible) regular
3510 expression.
3511 expression.
3511
3512
3512 If no FILEs are specified and the --rev flag isn't supplied, all
3513 If no FILEs are specified and the --rev flag isn't supplied, all
3513 files in the working directory are searched. When using the --rev
3514 files in the working directory are searched. When using the --rev
3514 flag and specifying FILEs, use the --follow argument to also
3515 flag and specifying FILEs, use the --follow argument to also
3515 follow the specified FILEs across renames and copies.
3516 follow the specified FILEs across renames and copies.
3516
3517
3517 .. container:: verbose
3518 .. container:: verbose
3518
3519
3519 Template:
3520 Template:
3520
3521
3521 The following keywords are supported in addition to the common template
3522 The following keywords are supported in addition to the common template
3522 keywords and functions. See also :hg:`help templates`.
3523 keywords and functions. See also :hg:`help templates`.
3523
3524
3524 :change: String. Character denoting insertion ``+`` or removal ``-``.
3525 :change: String. Character denoting insertion ``+`` or removal ``-``.
3525 Available if ``--diff`` is specified.
3526 Available if ``--diff`` is specified.
3526 :lineno: Integer. Line number of the match.
3527 :lineno: Integer. Line number of the match.
3527 :path: String. Repository-absolute path of the file.
3528 :path: String. Repository-absolute path of the file.
3528 :texts: List of text chunks.
3529 :texts: List of text chunks.
3529
3530
3530 And each entry of ``{texts}`` provides the following sub-keywords.
3531 And each entry of ``{texts}`` provides the following sub-keywords.
3531
3532
3532 :matched: Boolean. True if the chunk matches the specified pattern.
3533 :matched: Boolean. True if the chunk matches the specified pattern.
3533 :text: String. Chunk content.
3534 :text: String. Chunk content.
3534
3535
3535 See :hg:`help templates.operators` for the list expansion syntax.
3536 See :hg:`help templates.operators` for the list expansion syntax.
3536
3537
3537 Returns 0 if a match is found, 1 otherwise.
3538 Returns 0 if a match is found, 1 otherwise.
3538
3539
3539 """
3540 """
3540 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3541 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3541
3542
3542 diff = opts.get('all') or opts.get('diff')
3543 diff = opts.get('all') or opts.get('diff')
3543 follow = opts.get('follow')
3544 follow = opts.get('follow')
3544 if opts.get('all_files') is None and not diff:
3545 if opts.get('all_files') is None and not diff:
3545 opts['all_files'] = True
3546 opts['all_files'] = True
3546 plaingrep = (
3547 plaingrep = (
3547 opts.get('all_files') and not opts.get('rev') and not opts.get('follow')
3548 opts.get('all_files') and not opts.get('rev') and not opts.get('follow')
3548 )
3549 )
3549 all_files = opts.get('all_files')
3550 all_files = opts.get('all_files')
3550 if plaingrep:
3551 if plaingrep:
3551 opts['rev'] = [b'wdir()']
3552 opts['rev'] = [b'wdir()']
3552
3553
3553 reflags = re.M
3554 reflags = re.M
3554 if opts.get('ignore_case'):
3555 if opts.get('ignore_case'):
3555 reflags |= re.I
3556 reflags |= re.I
3556 try:
3557 try:
3557 regexp = util.re.compile(pattern, reflags)
3558 regexp = util.re.compile(pattern, reflags)
3558 except re.error as inst:
3559 except re.error as inst:
3559 ui.warn(
3560 ui.warn(
3560 _(b"grep: invalid match pattern: %s\n")
3561 _(b"grep: invalid match pattern: %s\n")
3561 % stringutil.forcebytestr(inst)
3562 % stringutil.forcebytestr(inst)
3562 )
3563 )
3563 return 1
3564 return 1
3564 sep, eol = b':', b'\n'
3565 sep, eol = b':', b'\n'
3565 if opts.get('print0'):
3566 if opts.get('print0'):
3566 sep = eol = b'\0'
3567 sep = eol = b'\0'
3567
3568
3568 searcher = grepmod.grepsearcher(
3569 searcher = grepmod.grepsearcher(
3569 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3570 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3570 )
3571 )
3571
3572
3572 getfile = searcher._getfile
3573 getfile = searcher._getfile
3573
3574
3574 uipathfn = scmutil.getuipathfn(repo)
3575 uipathfn = scmutil.getuipathfn(repo)
3575
3576
3576 def display(fm, fn, ctx, pstates, states):
3577 def display(fm, fn, ctx, pstates, states):
3577 rev = scmutil.intrev(ctx)
3578 rev = scmutil.intrev(ctx)
3578 if fm.isplain():
3579 if fm.isplain():
3579 formatuser = ui.shortuser
3580 formatuser = ui.shortuser
3580 else:
3581 else:
3581 formatuser = pycompat.bytestr
3582 formatuser = pycompat.bytestr
3582 if ui.quiet:
3583 if ui.quiet:
3583 datefmt = b'%Y-%m-%d'
3584 datefmt = b'%Y-%m-%d'
3584 else:
3585 else:
3585 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3586 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3586 found = False
3587 found = False
3587
3588
3588 @util.cachefunc
3589 @util.cachefunc
3589 def binary():
3590 def binary():
3590 flog = getfile(fn)
3591 flog = getfile(fn)
3591 try:
3592 try:
3592 return stringutil.binary(flog.read(ctx.filenode(fn)))
3593 return stringutil.binary(flog.read(ctx.filenode(fn)))
3593 except error.WdirUnsupported:
3594 except error.WdirUnsupported:
3594 return ctx[fn].isbinary()
3595 return ctx[fn].isbinary()
3595
3596
3596 fieldnamemap = {b'linenumber': b'lineno'}
3597 fieldnamemap = {b'linenumber': b'lineno'}
3597 if diff:
3598 if diff:
3598 iter = grepmod.difflinestates(pstates, states)
3599 iter = grepmod.difflinestates(pstates, states)
3599 else:
3600 else:
3600 iter = [(b'', l) for l in states]
3601 iter = [(b'', l) for l in states]
3601 for change, l in iter:
3602 for change, l in iter:
3602 fm.startitem()
3603 fm.startitem()
3603 fm.context(ctx=ctx)
3604 fm.context(ctx=ctx)
3604 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3605 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3605 fm.plain(uipathfn(fn), label=b'grep.filename')
3606 fm.plain(uipathfn(fn), label=b'grep.filename')
3606
3607
3607 cols = [
3608 cols = [
3608 (b'rev', b'%d', rev, not plaingrep, b''),
3609 (b'rev', b'%d', rev, not plaingrep, b''),
3609 (
3610 (
3610 b'linenumber',
3611 b'linenumber',
3611 b'%d',
3612 b'%d',
3612 l.linenum,
3613 l.linenum,
3613 opts.get('line_number'),
3614 opts.get('line_number'),
3614 b'',
3615 b'',
3615 ),
3616 ),
3616 ]
3617 ]
3617 if diff:
3618 if diff:
3618 cols.append(
3619 cols.append(
3619 (
3620 (
3620 b'change',
3621 b'change',
3621 b'%s',
3622 b'%s',
3622 change,
3623 change,
3623 True,
3624 True,
3624 b'grep.inserted '
3625 b'grep.inserted '
3625 if change == b'+'
3626 if change == b'+'
3626 else b'grep.deleted ',
3627 else b'grep.deleted ',
3627 )
3628 )
3628 )
3629 )
3629 cols.extend(
3630 cols.extend(
3630 [
3631 [
3631 (
3632 (
3632 b'user',
3633 b'user',
3633 b'%s',
3634 b'%s',
3634 formatuser(ctx.user()),
3635 formatuser(ctx.user()),
3635 opts.get('user'),
3636 opts.get('user'),
3636 b'',
3637 b'',
3637 ),
3638 ),
3638 (
3639 (
3639 b'date',
3640 b'date',
3640 b'%s',
3641 b'%s',
3641 fm.formatdate(ctx.date(), datefmt),
3642 fm.formatdate(ctx.date(), datefmt),
3642 opts.get('date'),
3643 opts.get('date'),
3643 b'',
3644 b'',
3644 ),
3645 ),
3645 ]
3646 ]
3646 )
3647 )
3647 for name, fmt, data, cond, extra_label in cols:
3648 for name, fmt, data, cond, extra_label in cols:
3648 if cond:
3649 if cond:
3649 fm.plain(sep, label=b'grep.sep')
3650 fm.plain(sep, label=b'grep.sep')
3650 field = fieldnamemap.get(name, name)
3651 field = fieldnamemap.get(name, name)
3651 label = extra_label + (b'grep.%s' % name)
3652 label = extra_label + (b'grep.%s' % name)
3652 fm.condwrite(cond, field, fmt, data, label=label)
3653 fm.condwrite(cond, field, fmt, data, label=label)
3653 if not opts.get('files_with_matches'):
3654 if not opts.get('files_with_matches'):
3654 fm.plain(sep, label=b'grep.sep')
3655 fm.plain(sep, label=b'grep.sep')
3655 if not opts.get('text') and binary():
3656 if not opts.get('text') and binary():
3656 fm.plain(_(b" Binary file matches"))
3657 fm.plain(_(b" Binary file matches"))
3657 else:
3658 else:
3658 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3659 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3659 fm.plain(eol)
3660 fm.plain(eol)
3660 found = True
3661 found = True
3661 if opts.get('files_with_matches'):
3662 if opts.get('files_with_matches'):
3662 break
3663 break
3663 return found
3664 return found
3664
3665
3665 def displaymatches(fm, l):
3666 def displaymatches(fm, l):
3666 p = 0
3667 p = 0
3667 for s, e in l.findpos(regexp):
3668 for s, e in l.findpos(regexp):
3668 if p < s:
3669 if p < s:
3669 fm.startitem()
3670 fm.startitem()
3670 fm.write(b'text', b'%s', l.line[p:s])
3671 fm.write(b'text', b'%s', l.line[p:s])
3671 fm.data(matched=False)
3672 fm.data(matched=False)
3672 fm.startitem()
3673 fm.startitem()
3673 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3674 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3674 fm.data(matched=True)
3675 fm.data(matched=True)
3675 p = e
3676 p = e
3676 if p < len(l.line):
3677 if p < len(l.line):
3677 fm.startitem()
3678 fm.startitem()
3678 fm.write(b'text', b'%s', l.line[p:])
3679 fm.write(b'text', b'%s', l.line[p:])
3679 fm.data(matched=False)
3680 fm.data(matched=False)
3680 fm.end()
3681 fm.end()
3681
3682
3682 found = False
3683 found = False
3683
3684
3684 wopts = logcmdutil.walkopts(
3685 wopts = logcmdutil.walkopts(
3685 pats=pats,
3686 pats=pats,
3686 opts=opts,
3687 opts=opts,
3687 revspec=opts['rev'],
3688 revspec=opts['rev'],
3688 include_pats=opts['include'],
3689 include_pats=opts['include'],
3689 exclude_pats=opts['exclude'],
3690 exclude_pats=opts['exclude'],
3690 follow=follow,
3691 follow=follow,
3691 force_changelog_traversal=all_files,
3692 force_changelog_traversal=all_files,
3692 filter_revisions_by_pats=not all_files,
3693 filter_revisions_by_pats=not all_files,
3693 )
3694 )
3694 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3695 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3695
3696
3696 ui.pager(b'grep')
3697 ui.pager(b'grep')
3697 fm = ui.formatter(b'grep', pycompat.byteskwargs(opts))
3698 fm = ui.formatter(b'grep', pycompat.byteskwargs(opts))
3698 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3699 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3699 r = display(fm, fn, ctx, pstates, states)
3700 r = display(fm, fn, ctx, pstates, states)
3700 found = found or r
3701 found = found or r
3701 if r and not diff and not all_files:
3702 if r and not diff and not all_files:
3702 searcher.skipfile(fn, ctx.rev())
3703 searcher.skipfile(fn, ctx.rev())
3703 fm.end()
3704 fm.end()
3704
3705
3705 return not found
3706 return not found
3706
3707
3707
3708
3708 @command(
3709 @command(
3709 b'heads',
3710 b'heads',
3710 [
3711 [
3711 (
3712 (
3712 b'r',
3713 b'r',
3713 b'rev',
3714 b'rev',
3714 b'',
3715 b'',
3715 _(b'show only heads which are descendants of STARTREV'),
3716 _(b'show only heads which are descendants of STARTREV'),
3716 _(b'STARTREV'),
3717 _(b'STARTREV'),
3717 ),
3718 ),
3718 (b't', b'topo', False, _(b'show topological heads only')),
3719 (b't', b'topo', False, _(b'show topological heads only')),
3719 (
3720 (
3720 b'a',
3721 b'a',
3721 b'active',
3722 b'active',
3722 False,
3723 False,
3723 _(b'show active branchheads only (DEPRECATED)'),
3724 _(b'show active branchheads only (DEPRECATED)'),
3724 ),
3725 ),
3725 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3726 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3726 ]
3727 ]
3727 + templateopts,
3728 + templateopts,
3728 _(b'[-ct] [-r STARTREV] [REV]...'),
3729 _(b'[-ct] [-r STARTREV] [REV]...'),
3729 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3730 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3730 intents={INTENT_READONLY},
3731 intents={INTENT_READONLY},
3731 )
3732 )
3732 def heads(ui, repo, *branchrevs, **opts):
3733 def heads(ui, repo, *branchrevs, **opts):
3733 """show branch heads
3734 """show branch heads
3734
3735
3735 With no arguments, show all open branch heads in the repository.
3736 With no arguments, show all open branch heads in the repository.
3736 Branch heads are changesets that have no descendants on the
3737 Branch heads are changesets that have no descendants on the
3737 same branch. They are where development generally takes place and
3738 same branch. They are where development generally takes place and
3738 are the usual targets for update and merge operations.
3739 are the usual targets for update and merge operations.
3739
3740
3740 If one or more REVs are given, only open branch heads on the
3741 If one or more REVs are given, only open branch heads on the
3741 branches associated with the specified changesets are shown. This
3742 branches associated with the specified changesets are shown. This
3742 means that you can use :hg:`heads .` to see the heads on the
3743 means that you can use :hg:`heads .` to see the heads on the
3743 currently checked-out branch.
3744 currently checked-out branch.
3744
3745
3745 If -c/--closed is specified, also show branch heads marked closed
3746 If -c/--closed is specified, also show branch heads marked closed
3746 (see :hg:`commit --close-branch`).
3747 (see :hg:`commit --close-branch`).
3747
3748
3748 If STARTREV is specified, only those heads that are descendants of
3749 If STARTREV is specified, only those heads that are descendants of
3749 STARTREV will be displayed.
3750 STARTREV will be displayed.
3750
3751
3751 If -t/--topo is specified, named branch mechanics will be ignored and only
3752 If -t/--topo is specified, named branch mechanics will be ignored and only
3752 topological heads (changesets with no children) will be shown.
3753 topological heads (changesets with no children) will be shown.
3753
3754
3754 Returns 0 if matching heads are found, 1 if not.
3755 Returns 0 if matching heads are found, 1 if not.
3755 """
3756 """
3756
3757
3757 opts = pycompat.byteskwargs(opts)
3758 opts = pycompat.byteskwargs(opts)
3758 start = None
3759 start = None
3759 rev = opts.get(b'rev')
3760 rev = opts.get(b'rev')
3760 if rev:
3761 if rev:
3761 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3762 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3762 start = logcmdutil.revsingle(repo, rev, None).node()
3763 start = logcmdutil.revsingle(repo, rev, None).node()
3763
3764
3764 if opts.get(b'topo'):
3765 if opts.get(b'topo'):
3765 heads = [repo[h] for h in repo.heads(start)]
3766 heads = [repo[h] for h in repo.heads(start)]
3766 else:
3767 else:
3767 heads = []
3768 heads = []
3768 for branch in repo.branchmap():
3769 for branch in repo.branchmap():
3769 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3770 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3770 heads = [repo[h] for h in heads]
3771 heads = [repo[h] for h in heads]
3771
3772
3772 if branchrevs:
3773 if branchrevs:
3773 branches = {
3774 branches = {
3774 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3775 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3775 }
3776 }
3776 heads = [h for h in heads if h.branch() in branches]
3777 heads = [h for h in heads if h.branch() in branches]
3777
3778
3778 if opts.get(b'active') and branchrevs:
3779 if opts.get(b'active') and branchrevs:
3779 dagheads = repo.heads(start)
3780 dagheads = repo.heads(start)
3780 heads = [h for h in heads if h.node() in dagheads]
3781 heads = [h for h in heads if h.node() in dagheads]
3781
3782
3782 if branchrevs:
3783 if branchrevs:
3783 haveheads = {h.branch() for h in heads}
3784 haveheads = {h.branch() for h in heads}
3784 if branches - haveheads:
3785 if branches - haveheads:
3785 headless = b', '.join(b for b in branches - haveheads)
3786 headless = b', '.join(b for b in branches - haveheads)
3786 msg = _(b'no open branch heads found on branches %s')
3787 msg = _(b'no open branch heads found on branches %s')
3787 if opts.get(b'rev'):
3788 if opts.get(b'rev'):
3788 msg += _(b' (started at %s)') % opts[b'rev']
3789 msg += _(b' (started at %s)') % opts[b'rev']
3789 ui.warn((msg + b'\n') % headless)
3790 ui.warn((msg + b'\n') % headless)
3790
3791
3791 if not heads:
3792 if not heads:
3792 return 1
3793 return 1
3793
3794
3794 ui.pager(b'heads')
3795 ui.pager(b'heads')
3795 heads = sorted(heads, key=lambda x: -(x.rev()))
3796 heads = sorted(heads, key=lambda x: -(x.rev()))
3796 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3797 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3797 for ctx in heads:
3798 for ctx in heads:
3798 displayer.show(ctx)
3799 displayer.show(ctx)
3799 displayer.close()
3800 displayer.close()
3800
3801
3801
3802
3802 @command(
3803 @command(
3803 b'help',
3804 b'help',
3804 [
3805 [
3805 (b'e', b'extension', None, _(b'show only help for extensions')),
3806 (b'e', b'extension', None, _(b'show only help for extensions')),
3806 (b'c', b'command', None, _(b'show only help for commands')),
3807 (b'c', b'command', None, _(b'show only help for commands')),
3807 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3808 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3808 (
3809 (
3809 b's',
3810 b's',
3810 b'system',
3811 b'system',
3811 [],
3812 [],
3812 _(b'show help for specific platform(s)'),
3813 _(b'show help for specific platform(s)'),
3813 _(b'PLATFORM'),
3814 _(b'PLATFORM'),
3814 ),
3815 ),
3815 ],
3816 ],
3816 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3817 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3817 helpcategory=command.CATEGORY_HELP,
3818 helpcategory=command.CATEGORY_HELP,
3818 norepo=True,
3819 norepo=True,
3819 intents={INTENT_READONLY},
3820 intents={INTENT_READONLY},
3820 )
3821 )
3821 def help_(ui, name=None, **opts):
3822 def help_(ui, name=None, **opts):
3822 """show help for a given topic or a help overview
3823 """show help for a given topic or a help overview
3823
3824
3824 With no arguments, print a list of commands with short help messages.
3825 With no arguments, print a list of commands with short help messages.
3825
3826
3826 Given a topic, extension, or command name, print help for that
3827 Given a topic, extension, or command name, print help for that
3827 topic.
3828 topic.
3828
3829
3829 Returns 0 if successful.
3830 Returns 0 if successful.
3830 """
3831 """
3831
3832
3832 keep = opts.get('system') or []
3833 keep = opts.get('system') or []
3833 if len(keep) == 0:
3834 if len(keep) == 0:
3834 if pycompat.sysplatform.startswith(b'win'):
3835 if pycompat.sysplatform.startswith(b'win'):
3835 keep.append(b'windows')
3836 keep.append(b'windows')
3836 elif pycompat.sysplatform == b'OpenVMS':
3837 elif pycompat.sysplatform == b'OpenVMS':
3837 keep.append(b'vms')
3838 keep.append(b'vms')
3838 elif pycompat.sysplatform == b'plan9':
3839 elif pycompat.sysplatform == b'plan9':
3839 keep.append(b'plan9')
3840 keep.append(b'plan9')
3840 else:
3841 else:
3841 keep.append(b'unix')
3842 keep.append(b'unix')
3842 keep.append(pycompat.sysplatform.lower())
3843 keep.append(pycompat.sysplatform.lower())
3843 if ui.verbose:
3844 if ui.verbose:
3844 keep.append(b'verbose')
3845 keep.append(b'verbose')
3845
3846
3846 commands = sys.modules[__name__]
3847 commands = sys.modules[__name__]
3847 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3848 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3848 ui.pager(b'help')
3849 ui.pager(b'help')
3849 ui.write(formatted)
3850 ui.write(formatted)
3850
3851
3851
3852
3852 @command(
3853 @command(
3853 b'identify|id',
3854 b'identify|id',
3854 [
3855 [
3855 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3856 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3856 (b'n', b'num', None, _(b'show local revision number')),
3857 (b'n', b'num', None, _(b'show local revision number')),
3857 (b'i', b'id', None, _(b'show global revision id')),
3858 (b'i', b'id', None, _(b'show global revision id')),
3858 (b'b', b'branch', None, _(b'show branch')),
3859 (b'b', b'branch', None, _(b'show branch')),
3859 (b't', b'tags', None, _(b'show tags')),
3860 (b't', b'tags', None, _(b'show tags')),
3860 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3861 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3861 ]
3862 ]
3862 + remoteopts
3863 + remoteopts
3863 + formatteropts,
3864 + formatteropts,
3864 _(b'[-nibtB] [-r REV] [SOURCE]'),
3865 _(b'[-nibtB] [-r REV] [SOURCE]'),
3865 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3866 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3866 optionalrepo=True,
3867 optionalrepo=True,
3867 intents={INTENT_READONLY},
3868 intents={INTENT_READONLY},
3868 )
3869 )
3869 def identify(
3870 def identify(
3870 ui,
3871 ui,
3871 repo,
3872 repo,
3872 source=None,
3873 source=None,
3873 rev=None,
3874 rev=None,
3874 num=None,
3875 num=None,
3875 id=None,
3876 id=None,
3876 branch=None,
3877 branch=None,
3877 tags=None,
3878 tags=None,
3878 bookmarks=None,
3879 bookmarks=None,
3879 **opts
3880 **opts
3880 ):
3881 ):
3881 """identify the working directory or specified revision
3882 """identify the working directory or specified revision
3882
3883
3883 Print a summary identifying the repository state at REV using one or
3884 Print a summary identifying the repository state at REV using one or
3884 two parent hash identifiers, followed by a "+" if the working
3885 two parent hash identifiers, followed by a "+" if the working
3885 directory has uncommitted changes, the branch name (if not default),
3886 directory has uncommitted changes, the branch name (if not default),
3886 a list of tags, and a list of bookmarks.
3887 a list of tags, and a list of bookmarks.
3887
3888
3888 When REV is not given, print a summary of the current state of the
3889 When REV is not given, print a summary of the current state of the
3889 repository including the working directory. Specify -r. to get information
3890 repository including the working directory. Specify -r. to get information
3890 of the working directory parent without scanning uncommitted changes.
3891 of the working directory parent without scanning uncommitted changes.
3891
3892
3892 Specifying a path to a repository root or Mercurial bundle will
3893 Specifying a path to a repository root or Mercurial bundle will
3893 cause lookup to operate on that repository/bundle.
3894 cause lookup to operate on that repository/bundle.
3894
3895
3895 .. container:: verbose
3896 .. container:: verbose
3896
3897
3897 Template:
3898 Template:
3898
3899
3899 The following keywords are supported in addition to the common template
3900 The following keywords are supported in addition to the common template
3900 keywords and functions. See also :hg:`help templates`.
3901 keywords and functions. See also :hg:`help templates`.
3901
3902
3902 :dirty: String. Character ``+`` denoting if the working directory has
3903 :dirty: String. Character ``+`` denoting if the working directory has
3903 uncommitted changes.
3904 uncommitted changes.
3904 :id: String. One or two nodes, optionally followed by ``+``.
3905 :id: String. One or two nodes, optionally followed by ``+``.
3905 :parents: List of strings. Parent nodes of the changeset.
3906 :parents: List of strings. Parent nodes of the changeset.
3906
3907
3907 Examples:
3908 Examples:
3908
3909
3909 - generate a build identifier for the working directory::
3910 - generate a build identifier for the working directory::
3910
3911
3911 hg id --id > build-id.dat
3912 hg id --id > build-id.dat
3912
3913
3913 - find the revision corresponding to a tag::
3914 - find the revision corresponding to a tag::
3914
3915
3915 hg id -n -r 1.3
3916 hg id -n -r 1.3
3916
3917
3917 - check the most recent revision of a remote repository::
3918 - check the most recent revision of a remote repository::
3918
3919
3919 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3920 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3920
3921
3921 See :hg:`log` for generating more information about specific revisions,
3922 See :hg:`log` for generating more information about specific revisions,
3922 including full hash identifiers.
3923 including full hash identifiers.
3923
3924
3924 Returns 0 if successful.
3925 Returns 0 if successful.
3925 """
3926 """
3926
3927
3927 opts = pycompat.byteskwargs(opts)
3928 opts = pycompat.byteskwargs(opts)
3928 if not repo and not source:
3929 if not repo and not source:
3929 raise error.InputError(
3930 raise error.InputError(
3930 _(b"there is no Mercurial repository here (.hg not found)")
3931 _(b"there is no Mercurial repository here (.hg not found)")
3931 )
3932 )
3932
3933
3933 default = not (num or id or branch or tags or bookmarks)
3934 default = not (num or id or branch or tags or bookmarks)
3934 output = []
3935 output = []
3935 revs = []
3936 revs = []
3936
3937
3937 peer = None
3938 peer = None
3938 try:
3939 try:
3939 if source:
3940 if source:
3940 path = urlutil.get_unique_pull_path_obj(b'identify', ui, source)
3941 path = urlutil.get_unique_pull_path_obj(b'identify', ui, source)
3941 # only pass ui when no repo
3942 # only pass ui when no repo
3942 peer = hg.peer(repo or ui, opts, path)
3943 peer = hg.peer(repo or ui, opts, path)
3943 repo = peer.local()
3944 repo = peer.local()
3944 branches = (path.branch, [])
3945 branches = (path.branch, [])
3945 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3946 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3946
3947
3947 fm = ui.formatter(b'identify', opts)
3948 fm = ui.formatter(b'identify', opts)
3948 fm.startitem()
3949 fm.startitem()
3949
3950
3950 if not repo:
3951 if not repo:
3951 if num or branch or tags:
3952 if num or branch or tags:
3952 raise error.InputError(
3953 raise error.InputError(
3953 _(b"can't query remote revision number, branch, or tags")
3954 _(b"can't query remote revision number, branch, or tags")
3954 )
3955 )
3955 if not rev and revs:
3956 if not rev and revs:
3956 rev = revs[0]
3957 rev = revs[0]
3957 if not rev:
3958 if not rev:
3958 rev = b"tip"
3959 rev = b"tip"
3959
3960
3960 remoterev = peer.lookup(rev)
3961 remoterev = peer.lookup(rev)
3961 hexrev = fm.hexfunc(remoterev)
3962 hexrev = fm.hexfunc(remoterev)
3962 if default or id:
3963 if default or id:
3963 output = [hexrev]
3964 output = [hexrev]
3964 fm.data(id=hexrev)
3965 fm.data(id=hexrev)
3965
3966
3966 @util.cachefunc
3967 @util.cachefunc
3967 def getbms():
3968 def getbms():
3968 bms = []
3969 bms = []
3969
3970
3970 if b'bookmarks' in peer.listkeys(b'namespaces'):
3971 if b'bookmarks' in peer.listkeys(b'namespaces'):
3971 hexremoterev = hex(remoterev)
3972 hexremoterev = hex(remoterev)
3972 bms = [
3973 bms = [
3973 bm
3974 bm
3974 for bm, bmr in peer.listkeys(b'bookmarks').items()
3975 for bm, bmr in peer.listkeys(b'bookmarks').items()
3975 if bmr == hexremoterev
3976 if bmr == hexremoterev
3976 ]
3977 ]
3977
3978
3978 return sorted(bms)
3979 return sorted(bms)
3979
3980
3980 if fm.isplain():
3981 if fm.isplain():
3981 if bookmarks:
3982 if bookmarks:
3982 output.extend(getbms())
3983 output.extend(getbms())
3983 elif default and not ui.quiet:
3984 elif default and not ui.quiet:
3984 # multiple bookmarks for a single parent separated by '/'
3985 # multiple bookmarks for a single parent separated by '/'
3985 bm = b'/'.join(getbms())
3986 bm = b'/'.join(getbms())
3986 if bm:
3987 if bm:
3987 output.append(bm)
3988 output.append(bm)
3988 else:
3989 else:
3989 fm.data(node=hex(remoterev))
3990 fm.data(node=hex(remoterev))
3990 if bookmarks or b'bookmarks' in fm.datahint():
3991 if bookmarks or b'bookmarks' in fm.datahint():
3991 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3992 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3992 else:
3993 else:
3993 if rev:
3994 if rev:
3994 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3995 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3995 ctx = logcmdutil.revsingle(repo, rev, None)
3996 ctx = logcmdutil.revsingle(repo, rev, None)
3996
3997
3997 if ctx.rev() is None:
3998 if ctx.rev() is None:
3998 ctx = repo[None]
3999 ctx = repo[None]
3999 parents = ctx.parents()
4000 parents = ctx.parents()
4000 taglist = []
4001 taglist = []
4001 for p in parents:
4002 for p in parents:
4002 taglist.extend(p.tags())
4003 taglist.extend(p.tags())
4003
4004
4004 dirty = b""
4005 dirty = b""
4005 if ctx.dirty(missing=True, merge=False, branch=False):
4006 if ctx.dirty(missing=True, merge=False, branch=False):
4006 dirty = b'+'
4007 dirty = b'+'
4007 fm.data(dirty=dirty)
4008 fm.data(dirty=dirty)
4008
4009
4009 hexoutput = [fm.hexfunc(p.node()) for p in parents]
4010 hexoutput = [fm.hexfunc(p.node()) for p in parents]
4010 if default or id:
4011 if default or id:
4011 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
4012 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
4012 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
4013 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
4013
4014
4014 if num:
4015 if num:
4015 numoutput = [b"%d" % p.rev() for p in parents]
4016 numoutput = [b"%d" % p.rev() for p in parents]
4016 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
4017 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
4017
4018
4018 fm.data(
4019 fm.data(
4019 parents=fm.formatlist(
4020 parents=fm.formatlist(
4020 [fm.hexfunc(p.node()) for p in parents], name=b'node'
4021 [fm.hexfunc(p.node()) for p in parents], name=b'node'
4021 )
4022 )
4022 )
4023 )
4023 else:
4024 else:
4024 hexoutput = fm.hexfunc(ctx.node())
4025 hexoutput = fm.hexfunc(ctx.node())
4025 if default or id:
4026 if default or id:
4026 output = [hexoutput]
4027 output = [hexoutput]
4027 fm.data(id=hexoutput)
4028 fm.data(id=hexoutput)
4028
4029
4029 if num:
4030 if num:
4030 output.append(pycompat.bytestr(ctx.rev()))
4031 output.append(pycompat.bytestr(ctx.rev()))
4031 taglist = ctx.tags()
4032 taglist = ctx.tags()
4032
4033
4033 if default and not ui.quiet:
4034 if default and not ui.quiet:
4034 b = ctx.branch()
4035 b = ctx.branch()
4035 if b != b'default':
4036 if b != b'default':
4036 output.append(b"(%s)" % b)
4037 output.append(b"(%s)" % b)
4037
4038
4038 # multiple tags for a single parent separated by '/'
4039 # multiple tags for a single parent separated by '/'
4039 t = b'/'.join(taglist)
4040 t = b'/'.join(taglist)
4040 if t:
4041 if t:
4041 output.append(t)
4042 output.append(t)
4042
4043
4043 # multiple bookmarks for a single parent separated by '/'
4044 # multiple bookmarks for a single parent separated by '/'
4044 bm = b'/'.join(ctx.bookmarks())
4045 bm = b'/'.join(ctx.bookmarks())
4045 if bm:
4046 if bm:
4046 output.append(bm)
4047 output.append(bm)
4047 else:
4048 else:
4048 if branch:
4049 if branch:
4049 output.append(ctx.branch())
4050 output.append(ctx.branch())
4050
4051
4051 if tags:
4052 if tags:
4052 output.extend(taglist)
4053 output.extend(taglist)
4053
4054
4054 if bookmarks:
4055 if bookmarks:
4055 output.extend(ctx.bookmarks())
4056 output.extend(ctx.bookmarks())
4056
4057
4057 fm.data(node=ctx.hex())
4058 fm.data(node=ctx.hex())
4058 fm.data(branch=ctx.branch())
4059 fm.data(branch=ctx.branch())
4059 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4060 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4060 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4061 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4061 fm.context(ctx=ctx)
4062 fm.context(ctx=ctx)
4062
4063
4063 fm.plain(b"%s\n" % b' '.join(output))
4064 fm.plain(b"%s\n" % b' '.join(output))
4064 fm.end()
4065 fm.end()
4065 finally:
4066 finally:
4066 if peer:
4067 if peer:
4067 peer.close()
4068 peer.close()
4068
4069
4069
4070
4070 @command(
4071 @command(
4071 b'import|patch',
4072 b'import|patch',
4072 [
4073 [
4073 (
4074 (
4074 b'p',
4075 b'p',
4075 b'strip',
4076 b'strip',
4076 1,
4077 1,
4077 _(
4078 _(
4078 b'directory strip option for patch. This has the same '
4079 b'directory strip option for patch. This has the same '
4079 b'meaning as the corresponding patch option'
4080 b'meaning as the corresponding patch option'
4080 ),
4081 ),
4081 _(b'NUM'),
4082 _(b'NUM'),
4082 ),
4083 ),
4083 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4084 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4084 (b'', b'secret', None, _(b'use the secret phase for committing')),
4085 (b'', b'secret', None, _(b'use the secret phase for committing')),
4085 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4086 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4086 (
4087 (
4087 b'f',
4088 b'f',
4088 b'force',
4089 b'force',
4089 None,
4090 None,
4090 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4091 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4091 ),
4092 ),
4092 (
4093 (
4093 b'',
4094 b'',
4094 b'no-commit',
4095 b'no-commit',
4095 None,
4096 None,
4096 _(b"don't commit, just update the working directory"),
4097 _(b"don't commit, just update the working directory"),
4097 ),
4098 ),
4098 (
4099 (
4099 b'',
4100 b'',
4100 b'bypass',
4101 b'bypass',
4101 None,
4102 None,
4102 _(b"apply patch without touching the working directory"),
4103 _(b"apply patch without touching the working directory"),
4103 ),
4104 ),
4104 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4105 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4105 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4106 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4106 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4107 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4107 (
4108 (
4108 b'',
4109 b'',
4109 b'import-branch',
4110 b'import-branch',
4110 None,
4111 None,
4111 _(b'use any branch information in patch (implied by --exact)'),
4112 _(b'use any branch information in patch (implied by --exact)'),
4112 ),
4113 ),
4113 ]
4114 ]
4114 + commitopts
4115 + commitopts
4115 + commitopts2
4116 + commitopts2
4116 + similarityopts,
4117 + similarityopts,
4117 _(b'[OPTION]... PATCH...'),
4118 _(b'[OPTION]... PATCH...'),
4118 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4119 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4119 )
4120 )
4120 def import_(ui, repo, patch1=None, *patches, **opts):
4121 def import_(ui, repo, patch1=None, *patches, **opts):
4121 """import an ordered set of patches
4122 """import an ordered set of patches
4122
4123
4123 Import a list of patches and commit them individually (unless
4124 Import a list of patches and commit them individually (unless
4124 --no-commit is specified).
4125 --no-commit is specified).
4125
4126
4126 To read a patch from standard input (stdin), use "-" as the patch
4127 To read a patch from standard input (stdin), use "-" as the patch
4127 name. If a URL is specified, the patch will be downloaded from
4128 name. If a URL is specified, the patch will be downloaded from
4128 there.
4129 there.
4129
4130
4130 Import first applies changes to the working directory (unless
4131 Import first applies changes to the working directory (unless
4131 --bypass is specified), import will abort if there are outstanding
4132 --bypass is specified), import will abort if there are outstanding
4132 changes.
4133 changes.
4133
4134
4134 Use --bypass to apply and commit patches directly to the
4135 Use --bypass to apply and commit patches directly to the
4135 repository, without affecting the working directory. Without
4136 repository, without affecting the working directory. Without
4136 --exact, patches will be applied on top of the working directory
4137 --exact, patches will be applied on top of the working directory
4137 parent revision.
4138 parent revision.
4138
4139
4139 You can import a patch straight from a mail message. Even patches
4140 You can import a patch straight from a mail message. Even patches
4140 as attachments work (to use the body part, it must have type
4141 as attachments work (to use the body part, it must have type
4141 text/plain or text/x-patch). From and Subject headers of email
4142 text/plain or text/x-patch). From and Subject headers of email
4142 message are used as default committer and commit message. All
4143 message are used as default committer and commit message. All
4143 text/plain body parts before first diff are added to the commit
4144 text/plain body parts before first diff are added to the commit
4144 message.
4145 message.
4145
4146
4146 If the imported patch was generated by :hg:`export`, user and
4147 If the imported patch was generated by :hg:`export`, user and
4147 description from patch override values from message headers and
4148 description from patch override values from message headers and
4148 body. Values given on command line with -m/--message and -u/--user
4149 body. Values given on command line with -m/--message and -u/--user
4149 override these.
4150 override these.
4150
4151
4151 If --exact is specified, import will set the working directory to
4152 If --exact is specified, import will set the working directory to
4152 the parent of each patch before applying it, and will abort if the
4153 the parent of each patch before applying it, and will abort if the
4153 resulting changeset has a different ID than the one recorded in
4154 resulting changeset has a different ID than the one recorded in
4154 the patch. This will guard against various ways that portable
4155 the patch. This will guard against various ways that portable
4155 patch formats and mail systems might fail to transfer Mercurial
4156 patch formats and mail systems might fail to transfer Mercurial
4156 data or metadata. See :hg:`bundle` for lossless transmission.
4157 data or metadata. See :hg:`bundle` for lossless transmission.
4157
4158
4158 Use --partial to ensure a changeset will be created from the patch
4159 Use --partial to ensure a changeset will be created from the patch
4159 even if some hunks fail to apply. Hunks that fail to apply will be
4160 even if some hunks fail to apply. Hunks that fail to apply will be
4160 written to a <target-file>.rej file. Conflicts can then be resolved
4161 written to a <target-file>.rej file. Conflicts can then be resolved
4161 by hand before :hg:`commit --amend` is run to update the created
4162 by hand before :hg:`commit --amend` is run to update the created
4162 changeset. This flag exists to let people import patches that
4163 changeset. This flag exists to let people import patches that
4163 partially apply without losing the associated metadata (author,
4164 partially apply without losing the associated metadata (author,
4164 date, description, ...).
4165 date, description, ...).
4165
4166
4166 .. note::
4167 .. note::
4167
4168
4168 When no hunks apply cleanly, :hg:`import --partial` will create
4169 When no hunks apply cleanly, :hg:`import --partial` will create
4169 an empty changeset, importing only the patch metadata.
4170 an empty changeset, importing only the patch metadata.
4170
4171
4171 With -s/--similarity, hg will attempt to discover renames and
4172 With -s/--similarity, hg will attempt to discover renames and
4172 copies in the patch in the same way as :hg:`addremove`.
4173 copies in the patch in the same way as :hg:`addremove`.
4173
4174
4174 It is possible to use external patch programs to perform the patch
4175 It is possible to use external patch programs to perform the patch
4175 by setting the ``ui.patch`` configuration option. For the default
4176 by setting the ``ui.patch`` configuration option. For the default
4176 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4177 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4177 See :hg:`help config` for more information about configuration
4178 See :hg:`help config` for more information about configuration
4178 files and how to use these options.
4179 files and how to use these options.
4179
4180
4180 See :hg:`help dates` for a list of formats valid for -d/--date.
4181 See :hg:`help dates` for a list of formats valid for -d/--date.
4181
4182
4182 .. container:: verbose
4183 .. container:: verbose
4183
4184
4184 Examples:
4185 Examples:
4185
4186
4186 - import a traditional patch from a website and detect renames::
4187 - import a traditional patch from a website and detect renames::
4187
4188
4188 hg import -s 80 http://example.com/bugfix.patch
4189 hg import -s 80 http://example.com/bugfix.patch
4189
4190
4190 - import a changeset from an hgweb server::
4191 - import a changeset from an hgweb server::
4191
4192
4192 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4193 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4193
4194
4194 - import all the patches in an Unix-style mbox::
4195 - import all the patches in an Unix-style mbox::
4195
4196
4196 hg import incoming-patches.mbox
4197 hg import incoming-patches.mbox
4197
4198
4198 - import patches from stdin::
4199 - import patches from stdin::
4199
4200
4200 hg import -
4201 hg import -
4201
4202
4202 - attempt to exactly restore an exported changeset (not always
4203 - attempt to exactly restore an exported changeset (not always
4203 possible)::
4204 possible)::
4204
4205
4205 hg import --exact proposed-fix.patch
4206 hg import --exact proposed-fix.patch
4206
4207
4207 - use an external tool to apply a patch which is too fuzzy for
4208 - use an external tool to apply a patch which is too fuzzy for
4208 the default internal tool.
4209 the default internal tool.
4209
4210
4210 hg import --config ui.patch="patch --merge" fuzzy.patch
4211 hg import --config ui.patch="patch --merge" fuzzy.patch
4211
4212
4212 - change the default fuzzing from 2 to a less strict 7
4213 - change the default fuzzing from 2 to a less strict 7
4213
4214
4214 hg import --config ui.fuzz=7 fuzz.patch
4215 hg import --config ui.fuzz=7 fuzz.patch
4215
4216
4216 Returns 0 on success, 1 on partial success (see --partial).
4217 Returns 0 on success, 1 on partial success (see --partial).
4217 """
4218 """
4218
4219
4219 cmdutil.check_incompatible_arguments(
4220 cmdutil.check_incompatible_arguments(
4220 opts, 'no_commit', ['bypass', 'secret']
4221 opts, 'no_commit', ['bypass', 'secret']
4221 )
4222 )
4222 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4223 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4223 opts = pycompat.byteskwargs(opts)
4224 opts = pycompat.byteskwargs(opts)
4224 if not patch1:
4225 if not patch1:
4225 raise error.InputError(_(b'need at least one patch to import'))
4226 raise error.InputError(_(b'need at least one patch to import'))
4226
4227
4227 patches = (patch1,) + patches
4228 patches = (patch1,) + patches
4228
4229
4229 date = opts.get(b'date')
4230 date = opts.get(b'date')
4230 if date:
4231 if date:
4231 opts[b'date'] = dateutil.parsedate(date)
4232 opts[b'date'] = dateutil.parsedate(date)
4232
4233
4233 exact = opts.get(b'exact')
4234 exact = opts.get(b'exact')
4234 update = not opts.get(b'bypass')
4235 update = not opts.get(b'bypass')
4235 try:
4236 try:
4236 sim = float(opts.get(b'similarity') or 0)
4237 sim = float(opts.get(b'similarity') or 0)
4237 except ValueError:
4238 except ValueError:
4238 raise error.InputError(_(b'similarity must be a number'))
4239 raise error.InputError(_(b'similarity must be a number'))
4239 if sim < 0 or sim > 100:
4240 if sim < 0 or sim > 100:
4240 raise error.InputError(_(b'similarity must be between 0 and 100'))
4241 raise error.InputError(_(b'similarity must be between 0 and 100'))
4241 if sim and not update:
4242 if sim and not update:
4242 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4243 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4243
4244
4244 base = opts[b"base"]
4245 base = opts[b"base"]
4245 msgs = []
4246 msgs = []
4246 ret = 0
4247 ret = 0
4247
4248
4248 with repo.wlock():
4249 with repo.wlock():
4249 if update:
4250 if update:
4250 cmdutil.checkunfinished(repo)
4251 cmdutil.checkunfinished(repo)
4251 if exact or not opts.get(b'force'):
4252 if exact or not opts.get(b'force'):
4252 cmdutil.bailifchanged(repo)
4253 cmdutil.bailifchanged(repo)
4253
4254
4254 if not opts.get(b'no_commit'):
4255 if not opts.get(b'no_commit'):
4255 lock = repo.lock
4256 lock = repo.lock
4256 tr = lambda: repo.transaction(b'import')
4257 tr = lambda: repo.transaction(b'import')
4257 else:
4258 else:
4258 lock = util.nullcontextmanager
4259 lock = util.nullcontextmanager
4259 tr = util.nullcontextmanager
4260 tr = util.nullcontextmanager
4260 with lock(), tr():
4261 with lock(), tr():
4261 parents = repo[None].parents()
4262 parents = repo[None].parents()
4262 for patchurl in patches:
4263 for patchurl in patches:
4263 if patchurl == b'-':
4264 if patchurl == b'-':
4264 ui.status(_(b'applying patch from stdin\n'))
4265 ui.status(_(b'applying patch from stdin\n'))
4265 patchfile = ui.fin
4266 patchfile = ui.fin
4266 patchurl = b'stdin' # for error message
4267 patchurl = b'stdin' # for error message
4267 else:
4268 else:
4268 patchurl = os.path.join(base, patchurl)
4269 patchurl = os.path.join(base, patchurl)
4269 ui.status(_(b'applying %s\n') % patchurl)
4270 ui.status(_(b'applying %s\n') % patchurl)
4270 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4271 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4271
4272
4272 haspatch = False
4273 haspatch = False
4273 for hunk in patch.split(patchfile):
4274 for hunk in patch.split(patchfile):
4274 with patch.extract(ui, hunk) as patchdata:
4275 with patch.extract(ui, hunk) as patchdata:
4275 msg, node, rej = cmdutil.tryimportone(
4276 msg, node, rej = cmdutil.tryimportone(
4276 ui, repo, patchdata, parents, opts, msgs, hg.clean
4277 ui, repo, patchdata, parents, opts, msgs, hg.clean
4277 )
4278 )
4278 if msg:
4279 if msg:
4279 haspatch = True
4280 haspatch = True
4280 ui.note(msg + b'\n')
4281 ui.note(msg + b'\n')
4281 if update or exact:
4282 if update or exact:
4282 parents = repo[None].parents()
4283 parents = repo[None].parents()
4283 else:
4284 else:
4284 parents = [repo[node]]
4285 parents = [repo[node]]
4285 if rej:
4286 if rej:
4286 ui.write_err(_(b"patch applied partially\n"))
4287 ui.write_err(_(b"patch applied partially\n"))
4287 ui.write_err(
4288 ui.write_err(
4288 _(
4289 _(
4289 b"(fix the .rej files and run "
4290 b"(fix the .rej files and run "
4290 b"`hg commit --amend`)\n"
4291 b"`hg commit --amend`)\n"
4291 )
4292 )
4292 )
4293 )
4293 ret = 1
4294 ret = 1
4294 break
4295 break
4295
4296
4296 if not haspatch:
4297 if not haspatch:
4297 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4298 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4298
4299
4299 if msgs:
4300 if msgs:
4300 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4301 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4301 return ret
4302 return ret
4302
4303
4303
4304
4304 @command(
4305 @command(
4305 b'incoming|in',
4306 b'incoming|in',
4306 [
4307 [
4307 (
4308 (
4308 b'f',
4309 b'f',
4309 b'force',
4310 b'force',
4310 None,
4311 None,
4311 _(b'run even if remote repository is unrelated'),
4312 _(b'run even if remote repository is unrelated'),
4312 ),
4313 ),
4313 (b'n', b'newest-first', None, _(b'show newest record first')),
4314 (b'n', b'newest-first', None, _(b'show newest record first')),
4314 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4315 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4315 (
4316 (
4316 b'r',
4317 b'r',
4317 b'rev',
4318 b'rev',
4318 [],
4319 [],
4319 _(b'a remote changeset intended to be added'),
4320 _(b'a remote changeset intended to be added'),
4320 _(b'REV'),
4321 _(b'REV'),
4321 ),
4322 ),
4322 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4323 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4323 (
4324 (
4324 b'b',
4325 b'b',
4325 b'branch',
4326 b'branch',
4326 [],
4327 [],
4327 _(b'a specific branch you would like to pull'),
4328 _(b'a specific branch you would like to pull'),
4328 _(b'BRANCH'),
4329 _(b'BRANCH'),
4329 ),
4330 ),
4330 ]
4331 ]
4331 + logopts
4332 + logopts
4332 + remoteopts
4333 + remoteopts
4333 + subrepoopts,
4334 + subrepoopts,
4334 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4335 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4335 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4336 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4336 )
4337 )
4337 def incoming(ui, repo, source=b"default", **opts):
4338 def incoming(ui, repo, source=b"default", **opts):
4338 """show new changesets found in source
4339 """show new changesets found in source
4339
4340
4340 Show new changesets found in the specified path/URL or the default
4341 Show new changesets found in the specified path/URL or the default
4341 pull location. These are the changesets that would have been pulled
4342 pull location. These are the changesets that would have been pulled
4342 by :hg:`pull` at the time you issued this command.
4343 by :hg:`pull` at the time you issued this command.
4343
4344
4344 See pull for valid source format details.
4345 See pull for valid source format details.
4345
4346
4346 .. container:: verbose
4347 .. container:: verbose
4347
4348
4348 With -B/--bookmarks, the result of bookmark comparison between
4349 With -B/--bookmarks, the result of bookmark comparison between
4349 local and remote repositories is displayed. With -v/--verbose,
4350 local and remote repositories is displayed. With -v/--verbose,
4350 status is also displayed for each bookmark like below::
4351 status is also displayed for each bookmark like below::
4351
4352
4352 BM1 01234567890a added
4353 BM1 01234567890a added
4353 BM2 1234567890ab advanced
4354 BM2 1234567890ab advanced
4354 BM3 234567890abc diverged
4355 BM3 234567890abc diverged
4355 BM4 34567890abcd changed
4356 BM4 34567890abcd changed
4356
4357
4357 The action taken locally when pulling depends on the
4358 The action taken locally when pulling depends on the
4358 status of each bookmark:
4359 status of each bookmark:
4359
4360
4360 :``added``: pull will create it
4361 :``added``: pull will create it
4361 :``advanced``: pull will update it
4362 :``advanced``: pull will update it
4362 :``diverged``: pull will create a divergent bookmark
4363 :``diverged``: pull will create a divergent bookmark
4363 :``changed``: result depends on remote changesets
4364 :``changed``: result depends on remote changesets
4364
4365
4365 From the point of view of pulling behavior, bookmark
4366 From the point of view of pulling behavior, bookmark
4366 existing only in the remote repository are treated as ``added``,
4367 existing only in the remote repository are treated as ``added``,
4367 even if it is in fact locally deleted.
4368 even if it is in fact locally deleted.
4368
4369
4369 .. container:: verbose
4370 .. container:: verbose
4370
4371
4371 For remote repository, using --bundle avoids downloading the
4372 For remote repository, using --bundle avoids downloading the
4372 changesets twice if the incoming is followed by a pull.
4373 changesets twice if the incoming is followed by a pull.
4373
4374
4374 Examples:
4375 Examples:
4375
4376
4376 - show incoming changes with patches and full description::
4377 - show incoming changes with patches and full description::
4377
4378
4378 hg incoming -vp
4379 hg incoming -vp
4379
4380
4380 - show incoming changes excluding merges, store a bundle::
4381 - show incoming changes excluding merges, store a bundle::
4381
4382
4382 hg in -vpM --bundle incoming.hg
4383 hg in -vpM --bundle incoming.hg
4383 hg pull incoming.hg
4384 hg pull incoming.hg
4384
4385
4385 - briefly list changes inside a bundle::
4386 - briefly list changes inside a bundle::
4386
4387
4387 hg in changes.hg -T "{desc|firstline}\\n"
4388 hg in changes.hg -T "{desc|firstline}\\n"
4388
4389
4389 Returns 0 if there are incoming changes, 1 otherwise.
4390 Returns 0 if there are incoming changes, 1 otherwise.
4390 """
4391 """
4391 opts = pycompat.byteskwargs(opts)
4392 opts = pycompat.byteskwargs(opts)
4392 if opts.get(b'graph'):
4393 if opts.get(b'graph'):
4393 logcmdutil.checkunsupportedgraphflags([], opts)
4394 logcmdutil.checkunsupportedgraphflags([], opts)
4394
4395
4395 def display(other, chlist, displayer):
4396 def display(other, chlist, displayer):
4396 revdag = logcmdutil.graphrevs(other, chlist, opts)
4397 revdag = logcmdutil.graphrevs(other, chlist, opts)
4397 logcmdutil.displaygraph(
4398 logcmdutil.displaygraph(
4398 ui, repo, revdag, displayer, graphmod.asciiedges
4399 ui, repo, revdag, displayer, graphmod.asciiedges
4399 )
4400 )
4400
4401
4401 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4402 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4402 return 0
4403 return 0
4403
4404
4404 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4405 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4405
4406
4406 if opts.get(b'bookmarks'):
4407 if opts.get(b'bookmarks'):
4407 srcs = urlutil.get_pull_paths(repo, ui, [source])
4408 srcs = urlutil.get_pull_paths(repo, ui, [source])
4408 for path in srcs:
4409 for path in srcs:
4409 # XXX the "branches" options are not used. Should it be used?
4410 # XXX the "branches" options are not used. Should it be used?
4410 other = hg.peer(repo, opts, path)
4411 other = hg.peer(repo, opts, path)
4411 try:
4412 try:
4412 if b'bookmarks' not in other.listkeys(b'namespaces'):
4413 if b'bookmarks' not in other.listkeys(b'namespaces'):
4413 ui.warn(_(b"remote doesn't support bookmarks\n"))
4414 ui.warn(_(b"remote doesn't support bookmarks\n"))
4414 return 0
4415 return 0
4415 ui.pager(b'incoming')
4416 ui.pager(b'incoming')
4416 ui.status(
4417 ui.status(
4417 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
4418 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
4418 )
4419 )
4419 return bookmarks.incoming(
4420 return bookmarks.incoming(
4420 ui, repo, other, mode=path.bookmarks_mode
4421 ui, repo, other, mode=path.bookmarks_mode
4421 )
4422 )
4422 finally:
4423 finally:
4423 other.close()
4424 other.close()
4424
4425
4425 return hg.incoming(ui, repo, source, opts)
4426 return hg.incoming(ui, repo, source, opts)
4426
4427
4427
4428
4428 @command(
4429 @command(
4429 b'init',
4430 b'init',
4430 remoteopts,
4431 remoteopts,
4431 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4432 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4432 helpcategory=command.CATEGORY_REPO_CREATION,
4433 helpcategory=command.CATEGORY_REPO_CREATION,
4433 helpbasic=True,
4434 helpbasic=True,
4434 norepo=True,
4435 norepo=True,
4435 )
4436 )
4436 def init(ui, dest=b".", **opts):
4437 def init(ui, dest=b".", **opts):
4437 """create a new repository in the given directory
4438 """create a new repository in the given directory
4438
4439
4439 Initialize a new repository in the given directory. If the given
4440 Initialize a new repository in the given directory. If the given
4440 directory does not exist, it will be created.
4441 directory does not exist, it will be created.
4441
4442
4442 If no directory is given, the current directory is used.
4443 If no directory is given, the current directory is used.
4443
4444
4444 It is possible to specify an ``ssh://`` URL as the destination.
4445 It is possible to specify an ``ssh://`` URL as the destination.
4445 See :hg:`help urls` for more information.
4446 See :hg:`help urls` for more information.
4446
4447
4447 Returns 0 on success.
4448 Returns 0 on success.
4448 """
4449 """
4449 opts = pycompat.byteskwargs(opts)
4450 opts = pycompat.byteskwargs(opts)
4450 path = urlutil.get_clone_path_obj(ui, dest)
4451 path = urlutil.get_clone_path_obj(ui, dest)
4451 peer = hg.peer(ui, opts, path, create=True)
4452 peer = hg.peer(ui, opts, path, create=True)
4452 peer.close()
4453 peer.close()
4453
4454
4454
4455
4455 @command(
4456 @command(
4456 b'locate',
4457 b'locate',
4457 [
4458 [
4458 (
4459 (
4459 b'r',
4460 b'r',
4460 b'rev',
4461 b'rev',
4461 b'',
4462 b'',
4462 _(b'search the repository as it is in REV'),
4463 _(b'search the repository as it is in REV'),
4463 _(b'REV'),
4464 _(b'REV'),
4464 ),
4465 ),
4465 (
4466 (
4466 b'0',
4467 b'0',
4467 b'print0',
4468 b'print0',
4468 None,
4469 None,
4469 _(b'end filenames with NUL, for use with xargs'),
4470 _(b'end filenames with NUL, for use with xargs'),
4470 ),
4471 ),
4471 (
4472 (
4472 b'f',
4473 b'f',
4473 b'fullpath',
4474 b'fullpath',
4474 None,
4475 None,
4475 _(b'print complete paths from the filesystem root'),
4476 _(b'print complete paths from the filesystem root'),
4476 ),
4477 ),
4477 ]
4478 ]
4478 + walkopts,
4479 + walkopts,
4479 _(b'[OPTION]... [PATTERN]...'),
4480 _(b'[OPTION]... [PATTERN]...'),
4480 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4481 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4481 )
4482 )
4482 def locate(ui, repo, *pats, **opts):
4483 def locate(ui, repo, *pats, **opts):
4483 """locate files matching specific patterns (DEPRECATED)
4484 """locate files matching specific patterns (DEPRECATED)
4484
4485
4485 Print files under Mercurial control in the working directory whose
4486 Print files under Mercurial control in the working directory whose
4486 names match the given patterns.
4487 names match the given patterns.
4487
4488
4488 By default, this command searches all directories in the working
4489 By default, this command searches all directories in the working
4489 directory. To search just the current directory and its
4490 directory. To search just the current directory and its
4490 subdirectories, use "--include .".
4491 subdirectories, use "--include .".
4491
4492
4492 If no patterns are given to match, this command prints the names
4493 If no patterns are given to match, this command prints the names
4493 of all files under Mercurial control in the working directory.
4494 of all files under Mercurial control in the working directory.
4494
4495
4495 If you want to feed the output of this command into the "xargs"
4496 If you want to feed the output of this command into the "xargs"
4496 command, use the -0 option to both this command and "xargs". This
4497 command, use the -0 option to both this command and "xargs". This
4497 will avoid the problem of "xargs" treating single filenames that
4498 will avoid the problem of "xargs" treating single filenames that
4498 contain whitespace as multiple filenames.
4499 contain whitespace as multiple filenames.
4499
4500
4500 See :hg:`help files` for a more versatile command.
4501 See :hg:`help files` for a more versatile command.
4501
4502
4502 Returns 0 if a match is found, 1 otherwise.
4503 Returns 0 if a match is found, 1 otherwise.
4503 """
4504 """
4504 opts = pycompat.byteskwargs(opts)
4505 opts = pycompat.byteskwargs(opts)
4505 if opts.get(b'print0'):
4506 if opts.get(b'print0'):
4506 end = b'\0'
4507 end = b'\0'
4507 else:
4508 else:
4508 end = b'\n'
4509 end = b'\n'
4509 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4510 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4510
4511
4511 ret = 1
4512 ret = 1
4512 m = scmutil.match(
4513 m = scmutil.match(
4513 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4514 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4514 )
4515 )
4515
4516
4516 ui.pager(b'locate')
4517 ui.pager(b'locate')
4517 if ctx.rev() is None:
4518 if ctx.rev() is None:
4518 # When run on the working copy, "locate" includes removed files, so
4519 # When run on the working copy, "locate" includes removed files, so
4519 # we get the list of files from the dirstate.
4520 # we get the list of files from the dirstate.
4520 filesgen = sorted(repo.dirstate.matches(m))
4521 filesgen = sorted(repo.dirstate.matches(m))
4521 else:
4522 else:
4522 filesgen = ctx.matches(m)
4523 filesgen = ctx.matches(m)
4523 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4524 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4524 for abs in filesgen:
4525 for abs in filesgen:
4525 if opts.get(b'fullpath'):
4526 if opts.get(b'fullpath'):
4526 ui.write(repo.wjoin(abs), end)
4527 ui.write(repo.wjoin(abs), end)
4527 else:
4528 else:
4528 ui.write(uipathfn(abs), end)
4529 ui.write(uipathfn(abs), end)
4529 ret = 0
4530 ret = 0
4530
4531
4531 return ret
4532 return ret
4532
4533
4533
4534
4534 @command(
4535 @command(
4535 b'log|history',
4536 b'log|history',
4536 [
4537 [
4537 (
4538 (
4538 b'f',
4539 b'f',
4539 b'follow',
4540 b'follow',
4540 None,
4541 None,
4541 _(
4542 _(
4542 b'follow changeset history, or file history across copies and renames'
4543 b'follow changeset history, or file history across copies and renames'
4543 ),
4544 ),
4544 ),
4545 ),
4545 (
4546 (
4546 b'',
4547 b'',
4547 b'follow-first',
4548 b'follow-first',
4548 None,
4549 None,
4549 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4550 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4550 ),
4551 ),
4551 (
4552 (
4552 b'd',
4553 b'd',
4553 b'date',
4554 b'date',
4554 b'',
4555 b'',
4555 _(b'show revisions matching date spec'),
4556 _(b'show revisions matching date spec'),
4556 _(b'DATE'),
4557 _(b'DATE'),
4557 ),
4558 ),
4558 (b'C', b'copies', None, _(b'show copied files')),
4559 (b'C', b'copies', None, _(b'show copied files')),
4559 (
4560 (
4560 b'k',
4561 b'k',
4561 b'keyword',
4562 b'keyword',
4562 [],
4563 [],
4563 _(b'do case-insensitive search for a given text'),
4564 _(b'do case-insensitive search for a given text'),
4564 _(b'TEXT'),
4565 _(b'TEXT'),
4565 ),
4566 ),
4566 (
4567 (
4567 b'r',
4568 b'r',
4568 b'rev',
4569 b'rev',
4569 [],
4570 [],
4570 _(b'revisions to select or follow from'),
4571 _(b'revisions to select or follow from'),
4571 _(b'REV'),
4572 _(b'REV'),
4572 ),
4573 ),
4573 (
4574 (
4574 b'L',
4575 b'L',
4575 b'line-range',
4576 b'line-range',
4576 [],
4577 [],
4577 _(b'follow line range of specified file (EXPERIMENTAL)'),
4578 _(b'follow line range of specified file (EXPERIMENTAL)'),
4578 _(b'FILE,RANGE'),
4579 _(b'FILE,RANGE'),
4579 ),
4580 ),
4580 (
4581 (
4581 b'',
4582 b'',
4582 b'removed',
4583 b'removed',
4583 None,
4584 None,
4584 _(b'include revisions where files were removed'),
4585 _(b'include revisions where files were removed'),
4585 ),
4586 ),
4586 (
4587 (
4587 b'm',
4588 b'm',
4588 b'only-merges',
4589 b'only-merges',
4589 None,
4590 None,
4590 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4591 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4591 ),
4592 ),
4592 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4593 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4593 (
4594 (
4594 b'',
4595 b'',
4595 b'only-branch',
4596 b'only-branch',
4596 [],
4597 [],
4597 _(
4598 _(
4598 b'show only changesets within the given named branch (DEPRECATED)'
4599 b'show only changesets within the given named branch (DEPRECATED)'
4599 ),
4600 ),
4600 _(b'BRANCH'),
4601 _(b'BRANCH'),
4601 ),
4602 ),
4602 (
4603 (
4603 b'b',
4604 b'b',
4604 b'branch',
4605 b'branch',
4605 [],
4606 [],
4606 _(b'show changesets within the given named branch'),
4607 _(b'show changesets within the given named branch'),
4607 _(b'BRANCH'),
4608 _(b'BRANCH'),
4608 ),
4609 ),
4609 (
4610 (
4610 b'B',
4611 b'B',
4611 b'bookmark',
4612 b'bookmark',
4612 [],
4613 [],
4613 _(b"show changesets within the given bookmark"),
4614 _(b"show changesets within the given bookmark"),
4614 _(b'BOOKMARK'),
4615 _(b'BOOKMARK'),
4615 ),
4616 ),
4616 (
4617 (
4617 b'P',
4618 b'P',
4618 b'prune',
4619 b'prune',
4619 [],
4620 [],
4620 _(b'do not display revision or any of its ancestors'),
4621 _(b'do not display revision or any of its ancestors'),
4621 _(b'REV'),
4622 _(b'REV'),
4622 ),
4623 ),
4623 ]
4624 ]
4624 + logopts
4625 + logopts
4625 + walkopts,
4626 + walkopts,
4626 _(b'[OPTION]... [FILE]'),
4627 _(b'[OPTION]... [FILE]'),
4627 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4628 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4628 helpbasic=True,
4629 helpbasic=True,
4629 inferrepo=True,
4630 inferrepo=True,
4630 intents={INTENT_READONLY},
4631 intents={INTENT_READONLY},
4631 )
4632 )
4632 def log(ui, repo, *pats, **opts):
4633 def log(ui, repo, *pats, **opts):
4633 """show revision history of entire repository or files
4634 """show revision history of entire repository or files
4634
4635
4635 Print the revision history of the specified files or the entire
4636 Print the revision history of the specified files or the entire
4636 project.
4637 project.
4637
4638
4638 If no revision range is specified, the default is ``tip:0`` unless
4639 If no revision range is specified, the default is ``tip:0`` unless
4639 --follow is set.
4640 --follow is set.
4640
4641
4641 File history is shown without following rename or copy history of
4642 File history is shown without following rename or copy history of
4642 files. Use -f/--follow with a filename to follow history across
4643 files. Use -f/--follow with a filename to follow history across
4643 renames and copies. --follow without a filename will only show
4644 renames and copies. --follow without a filename will only show
4644 ancestors of the starting revisions. The starting revisions can be
4645 ancestors of the starting revisions. The starting revisions can be
4645 specified by -r/--rev, which default to the working directory parent.
4646 specified by -r/--rev, which default to the working directory parent.
4646
4647
4647 By default this command prints revision number and changeset id,
4648 By default this command prints revision number and changeset id,
4648 tags, non-trivial parents, user, date and time, and a summary for
4649 tags, non-trivial parents, user, date and time, and a summary for
4649 each commit. When the -v/--verbose switch is used, the list of
4650 each commit. When the -v/--verbose switch is used, the list of
4650 changed files and full commit message are shown.
4651 changed files and full commit message are shown.
4651
4652
4652 With --graph the revisions are shown as an ASCII art DAG with the most
4653 With --graph the revisions are shown as an ASCII art DAG with the most
4653 recent changeset at the top.
4654 recent changeset at the top.
4654 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4655 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4655 involved in an unresolved merge conflict, '_' closes a branch,
4656 involved in an unresolved merge conflict, '_' closes a branch,
4656 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4657 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4657 changeset from the lines below is a parent of the 'o' merge on the same
4658 changeset from the lines below is a parent of the 'o' merge on the same
4658 line.
4659 line.
4659 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4660 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4660 of a '|' indicates one or more revisions in a path are omitted.
4661 of a '|' indicates one or more revisions in a path are omitted.
4661
4662
4662 .. container:: verbose
4663 .. container:: verbose
4663
4664
4664 Use -L/--line-range FILE,M:N options to follow the history of lines
4665 Use -L/--line-range FILE,M:N options to follow the history of lines
4665 from M to N in FILE. With -p/--patch only diff hunks affecting
4666 from M to N in FILE. With -p/--patch only diff hunks affecting
4666 specified line range will be shown. This option requires --follow;
4667 specified line range will be shown. This option requires --follow;
4667 it can be specified multiple times. Currently, this option is not
4668 it can be specified multiple times. Currently, this option is not
4668 compatible with --graph. This option is experimental.
4669 compatible with --graph. This option is experimental.
4669
4670
4670 .. note::
4671 .. note::
4671
4672
4672 :hg:`log --patch` may generate unexpected diff output for merge
4673 :hg:`log --patch` may generate unexpected diff output for merge
4673 changesets, as it will only compare the merge changeset against
4674 changesets, as it will only compare the merge changeset against
4674 its first parent. Also, only files different from BOTH parents
4675 its first parent. Also, only files different from BOTH parents
4675 will appear in files:.
4676 will appear in files:.
4676
4677
4677 .. note::
4678 .. note::
4678
4679
4679 For performance reasons, :hg:`log FILE` may omit duplicate changes
4680 For performance reasons, :hg:`log FILE` may omit duplicate changes
4680 made on branches and will not show removals or mode changes. To
4681 made on branches and will not show removals or mode changes. To
4681 see all such changes, use the --removed switch.
4682 see all such changes, use the --removed switch.
4682
4683
4683 .. container:: verbose
4684 .. container:: verbose
4684
4685
4685 .. note::
4686 .. note::
4686
4687
4687 The history resulting from -L/--line-range options depends on diff
4688 The history resulting from -L/--line-range options depends on diff
4688 options; for instance if white-spaces are ignored, respective changes
4689 options; for instance if white-spaces are ignored, respective changes
4689 with only white-spaces in specified line range will not be listed.
4690 with only white-spaces in specified line range will not be listed.
4690
4691
4691 .. container:: verbose
4692 .. container:: verbose
4692
4693
4693 Some examples:
4694 Some examples:
4694
4695
4695 - changesets with full descriptions and file lists::
4696 - changesets with full descriptions and file lists::
4696
4697
4697 hg log -v
4698 hg log -v
4698
4699
4699 - changesets ancestral to the working directory::
4700 - changesets ancestral to the working directory::
4700
4701
4701 hg log -f
4702 hg log -f
4702
4703
4703 - last 10 commits on the current branch::
4704 - last 10 commits on the current branch::
4704
4705
4705 hg log -l 10 -b .
4706 hg log -l 10 -b .
4706
4707
4707 - changesets showing all modifications of a file, including removals::
4708 - changesets showing all modifications of a file, including removals::
4708
4709
4709 hg log --removed file.c
4710 hg log --removed file.c
4710
4711
4711 - all changesets that touch a directory, with diffs, excluding merges::
4712 - all changesets that touch a directory, with diffs, excluding merges::
4712
4713
4713 hg log -Mp lib/
4714 hg log -Mp lib/
4714
4715
4715 - all revision numbers that match a keyword::
4716 - all revision numbers that match a keyword::
4716
4717
4717 hg log -k bug --template "{rev}\\n"
4718 hg log -k bug --template "{rev}\\n"
4718
4719
4719 - the full hash identifier of the working directory parent::
4720 - the full hash identifier of the working directory parent::
4720
4721
4721 hg log -r . --template "{node}\\n"
4722 hg log -r . --template "{node}\\n"
4722
4723
4723 - list available log templates::
4724 - list available log templates::
4724
4725
4725 hg log -T list
4726 hg log -T list
4726
4727
4727 - check if a given changeset is included in a tagged release::
4728 - check if a given changeset is included in a tagged release::
4728
4729
4729 hg log -r "a21ccf and ancestor(1.9)"
4730 hg log -r "a21ccf and ancestor(1.9)"
4730
4731
4731 - find all changesets by some user in a date range::
4732 - find all changesets by some user in a date range::
4732
4733
4733 hg log -k alice -d "may 2008 to jul 2008"
4734 hg log -k alice -d "may 2008 to jul 2008"
4734
4735
4735 - summary of all changesets after the last tag::
4736 - summary of all changesets after the last tag::
4736
4737
4737 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4738 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4738
4739
4739 - changesets touching lines 13 to 23 for file.c::
4740 - changesets touching lines 13 to 23 for file.c::
4740
4741
4741 hg log -L file.c,13:23
4742 hg log -L file.c,13:23
4742
4743
4743 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4744 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4744 main.c with patch::
4745 main.c with patch::
4745
4746
4746 hg log -L file.c,13:23 -L main.c,2:6 -p
4747 hg log -L file.c,13:23 -L main.c,2:6 -p
4747
4748
4748 See :hg:`help dates` for a list of formats valid for -d/--date.
4749 See :hg:`help dates` for a list of formats valid for -d/--date.
4749
4750
4750 See :hg:`help revisions` for more about specifying and ordering
4751 See :hg:`help revisions` for more about specifying and ordering
4751 revisions.
4752 revisions.
4752
4753
4753 See :hg:`help templates` for more about pre-packaged styles and
4754 See :hg:`help templates` for more about pre-packaged styles and
4754 specifying custom templates. The default template used by the log
4755 specifying custom templates. The default template used by the log
4755 command can be customized via the ``command-templates.log`` configuration
4756 command can be customized via the ``command-templates.log`` configuration
4756 setting.
4757 setting.
4757
4758
4758 Returns 0 on success.
4759 Returns 0 on success.
4759
4760
4760 """
4761 """
4761 opts = pycompat.byteskwargs(opts)
4762 opts = pycompat.byteskwargs(opts)
4762 linerange = opts.get(b'line_range')
4763 linerange = opts.get(b'line_range')
4763
4764
4764 if linerange and not opts.get(b'follow'):
4765 if linerange and not opts.get(b'follow'):
4765 raise error.InputError(_(b'--line-range requires --follow'))
4766 raise error.InputError(_(b'--line-range requires --follow'))
4766
4767
4767 if linerange and pats:
4768 if linerange and pats:
4768 # TODO: take pats as patterns with no line-range filter
4769 # TODO: take pats as patterns with no line-range filter
4769 raise error.InputError(
4770 raise error.InputError(
4770 _(b'FILE arguments are not compatible with --line-range option')
4771 _(b'FILE arguments are not compatible with --line-range option')
4771 )
4772 )
4772
4773
4773 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4774 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4774 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4775 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4775 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4776 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4776 if linerange:
4777 if linerange:
4777 # TODO: should follow file history from logcmdutil._initialrevs(),
4778 # TODO: should follow file history from logcmdutil._initialrevs(),
4778 # then filter the result by logcmdutil._makerevset() and --limit
4779 # then filter the result by logcmdutil._makerevset() and --limit
4779 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4780 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4780
4781
4781 getcopies = None
4782 getcopies = None
4782 if opts.get(b'copies'):
4783 if opts.get(b'copies'):
4783 endrev = None
4784 endrev = None
4784 if revs:
4785 if revs:
4785 endrev = revs.max() + 1
4786 endrev = revs.max() + 1
4786 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4787 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4787
4788
4788 ui.pager(b'log')
4789 ui.pager(b'log')
4789 displayer = logcmdutil.changesetdisplayer(
4790 displayer = logcmdutil.changesetdisplayer(
4790 ui, repo, opts, differ, buffered=True
4791 ui, repo, opts, differ, buffered=True
4791 )
4792 )
4792 if opts.get(b'graph'):
4793 if opts.get(b'graph'):
4793 displayfn = logcmdutil.displaygraphrevs
4794 displayfn = logcmdutil.displaygraphrevs
4794 else:
4795 else:
4795 displayfn = logcmdutil.displayrevs
4796 displayfn = logcmdutil.displayrevs
4796 displayfn(ui, repo, revs, displayer, getcopies)
4797 displayfn(ui, repo, revs, displayer, getcopies)
4797
4798
4798
4799
4799 @command(
4800 @command(
4800 b'manifest',
4801 b'manifest',
4801 [
4802 [
4802 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4803 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4803 (b'', b'all', False, _(b"list files from all revisions")),
4804 (b'', b'all', False, _(b"list files from all revisions")),
4804 ]
4805 ]
4805 + formatteropts,
4806 + formatteropts,
4806 _(b'[-r REV]'),
4807 _(b'[-r REV]'),
4807 helpcategory=command.CATEGORY_MAINTENANCE,
4808 helpcategory=command.CATEGORY_MAINTENANCE,
4808 intents={INTENT_READONLY},
4809 intents={INTENT_READONLY},
4809 )
4810 )
4810 def manifest(ui, repo, node=None, rev=None, **opts):
4811 def manifest(ui, repo, node=None, rev=None, **opts):
4811 """output the current or given revision of the project manifest
4812 """output the current or given revision of the project manifest
4812
4813
4813 Print a list of version controlled files for the given revision.
4814 Print a list of version controlled files for the given revision.
4814 If no revision is given, the first parent of the working directory
4815 If no revision is given, the first parent of the working directory
4815 is used, or the null revision if no revision is checked out.
4816 is used, or the null revision if no revision is checked out.
4816
4817
4817 With -v, print file permissions, symlink and executable bits.
4818 With -v, print file permissions, symlink and executable bits.
4818 With --debug, print file revision hashes.
4819 With --debug, print file revision hashes.
4819
4820
4820 If option --all is specified, the list of all files from all revisions
4821 If option --all is specified, the list of all files from all revisions
4821 is printed. This includes deleted and renamed files.
4822 is printed. This includes deleted and renamed files.
4822
4823
4823 Returns 0 on success.
4824 Returns 0 on success.
4824 """
4825 """
4825 opts = pycompat.byteskwargs(opts)
4826 opts = pycompat.byteskwargs(opts)
4826 fm = ui.formatter(b'manifest', opts)
4827 fm = ui.formatter(b'manifest', opts)
4827
4828
4828 if opts.get(b'all'):
4829 if opts.get(b'all'):
4829 if rev or node:
4830 if rev or node:
4830 raise error.InputError(_(b"can't specify a revision with --all"))
4831 raise error.InputError(_(b"can't specify a revision with --all"))
4831
4832
4832 res = set()
4833 res = set()
4833 for rev in repo:
4834 for rev in repo:
4834 ctx = repo[rev]
4835 ctx = repo[rev]
4835 res |= set(ctx.files())
4836 res |= set(ctx.files())
4836
4837
4837 ui.pager(b'manifest')
4838 ui.pager(b'manifest')
4838 for f in sorted(res):
4839 for f in sorted(res):
4839 fm.startitem()
4840 fm.startitem()
4840 fm.write(b"path", b'%s\n', f)
4841 fm.write(b"path", b'%s\n', f)
4841 fm.end()
4842 fm.end()
4842 return
4843 return
4843
4844
4844 if rev and node:
4845 if rev and node:
4845 raise error.InputError(_(b"please specify just one revision"))
4846 raise error.InputError(_(b"please specify just one revision"))
4846
4847
4847 if not node:
4848 if not node:
4848 node = rev
4849 node = rev
4849
4850
4850 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4851 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4851 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4852 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4852 if node:
4853 if node:
4853 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4854 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4854 ctx = logcmdutil.revsingle(repo, node)
4855 ctx = logcmdutil.revsingle(repo, node)
4855 mf = ctx.manifest()
4856 mf = ctx.manifest()
4856 ui.pager(b'manifest')
4857 ui.pager(b'manifest')
4857 for f in ctx:
4858 for f in ctx:
4858 fm.startitem()
4859 fm.startitem()
4859 fm.context(ctx=ctx)
4860 fm.context(ctx=ctx)
4860 fl = ctx[f].flags()
4861 fl = ctx[f].flags()
4861 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4862 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4862 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4863 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4863 fm.write(b'path', b'%s\n', f)
4864 fm.write(b'path', b'%s\n', f)
4864 fm.end()
4865 fm.end()
4865
4866
4866
4867
4867 @command(
4868 @command(
4868 b'merge',
4869 b'merge',
4869 [
4870 [
4870 (
4871 (
4871 b'f',
4872 b'f',
4872 b'force',
4873 b'force',
4873 None,
4874 None,
4874 _(b'force a merge including outstanding changes (DEPRECATED)'),
4875 _(b'force a merge including outstanding changes (DEPRECATED)'),
4875 ),
4876 ),
4876 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4877 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4877 (
4878 (
4878 b'P',
4879 b'P',
4879 b'preview',
4880 b'preview',
4880 None,
4881 None,
4881 _(b'review revisions to merge (no merge is performed)'),
4882 _(b'review revisions to merge (no merge is performed)'),
4882 ),
4883 ),
4883 (b'', b'abort', None, _(b'abort the ongoing merge')),
4884 (b'', b'abort', None, _(b'abort the ongoing merge')),
4884 ]
4885 ]
4885 + mergetoolopts,
4886 + mergetoolopts,
4886 _(b'[-P] [[-r] REV]'),
4887 _(b'[-P] [[-r] REV]'),
4887 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4888 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4888 helpbasic=True,
4889 helpbasic=True,
4889 )
4890 )
4890 def merge(ui, repo, node=None, **opts):
4891 def merge(ui, repo, node=None, **opts):
4891 """merge another revision into working directory
4892 """merge another revision into working directory
4892
4893
4893 The current working directory is updated with all changes made in
4894 The current working directory is updated with all changes made in
4894 the requested revision since the last common predecessor revision.
4895 the requested revision since the last common predecessor revision.
4895
4896
4896 Files that changed between either parent are marked as changed for
4897 Files that changed between either parent are marked as changed for
4897 the next commit and a commit must be performed before any further
4898 the next commit and a commit must be performed before any further
4898 updates to the repository are allowed. The next commit will have
4899 updates to the repository are allowed. The next commit will have
4899 two parents.
4900 two parents.
4900
4901
4901 ``--tool`` can be used to specify the merge tool used for file
4902 ``--tool`` can be used to specify the merge tool used for file
4902 merges. It overrides the HGMERGE environment variable and your
4903 merges. It overrides the HGMERGE environment variable and your
4903 configuration files. See :hg:`help merge-tools` for options.
4904 configuration files. See :hg:`help merge-tools` for options.
4904
4905
4905 If no revision is specified, the working directory's parent is a
4906 If no revision is specified, the working directory's parent is a
4906 head revision, and the current branch contains exactly one other
4907 head revision, and the current branch contains exactly one other
4907 head, the other head is merged with by default. Otherwise, an
4908 head, the other head is merged with by default. Otherwise, an
4908 explicit revision with which to merge must be provided.
4909 explicit revision with which to merge must be provided.
4909
4910
4910 See :hg:`help resolve` for information on handling file conflicts.
4911 See :hg:`help resolve` for information on handling file conflicts.
4911
4912
4912 To undo an uncommitted merge, use :hg:`merge --abort` which
4913 To undo an uncommitted merge, use :hg:`merge --abort` which
4913 will check out a clean copy of the original merge parent, losing
4914 will check out a clean copy of the original merge parent, losing
4914 all changes.
4915 all changes.
4915
4916
4916 Returns 0 on success, 1 if there are unresolved files.
4917 Returns 0 on success, 1 if there are unresolved files.
4917 """
4918 """
4918
4919
4919 opts = pycompat.byteskwargs(opts)
4920 opts = pycompat.byteskwargs(opts)
4920 abort = opts.get(b'abort')
4921 abort = opts.get(b'abort')
4921 if abort and repo.dirstate.p2() == repo.nullid:
4922 if abort and repo.dirstate.p2() == repo.nullid:
4922 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4923 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4923 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4924 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4924 if abort:
4925 if abort:
4925 state = cmdutil.getunfinishedstate(repo)
4926 state = cmdutil.getunfinishedstate(repo)
4926 if state and state._opname != b'merge':
4927 if state and state._opname != b'merge':
4927 raise error.StateError(
4928 raise error.StateError(
4928 _(b'cannot abort merge with %s in progress') % (state._opname),
4929 _(b'cannot abort merge with %s in progress') % (state._opname),
4929 hint=state.hint(),
4930 hint=state.hint(),
4930 )
4931 )
4931 if node:
4932 if node:
4932 raise error.InputError(_(b"cannot specify a node with --abort"))
4933 raise error.InputError(_(b"cannot specify a node with --abort"))
4933 return hg.abortmerge(repo.ui, repo)
4934 return hg.abortmerge(repo.ui, repo)
4934
4935
4935 if opts.get(b'rev') and node:
4936 if opts.get(b'rev') and node:
4936 raise error.InputError(_(b"please specify just one revision"))
4937 raise error.InputError(_(b"please specify just one revision"))
4937 if not node:
4938 if not node:
4938 node = opts.get(b'rev')
4939 node = opts.get(b'rev')
4939
4940
4940 if node:
4941 if node:
4941 ctx = logcmdutil.revsingle(repo, node)
4942 ctx = logcmdutil.revsingle(repo, node)
4942 else:
4943 else:
4943 if ui.configbool(b'commands', b'merge.require-rev'):
4944 if ui.configbool(b'commands', b'merge.require-rev'):
4944 raise error.InputError(
4945 raise error.InputError(
4945 _(
4946 _(
4946 b'configuration requires specifying revision to merge '
4947 b'configuration requires specifying revision to merge '
4947 b'with'
4948 b'with'
4948 )
4949 )
4949 )
4950 )
4950 ctx = repo[destutil.destmerge(repo)]
4951 ctx = repo[destutil.destmerge(repo)]
4951
4952
4952 if ctx.node() is None:
4953 if ctx.node() is None:
4953 raise error.InputError(
4954 raise error.InputError(
4954 _(b'merging with the working copy has no effect')
4955 _(b'merging with the working copy has no effect')
4955 )
4956 )
4956
4957
4957 if opts.get(b'preview'):
4958 if opts.get(b'preview'):
4958 # find nodes that are ancestors of p2 but not of p1
4959 # find nodes that are ancestors of p2 but not of p1
4959 p1 = repo[b'.'].node()
4960 p1 = repo[b'.'].node()
4960 p2 = ctx.node()
4961 p2 = ctx.node()
4961 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4962 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4962
4963
4963 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4964 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4964 for node in nodes:
4965 for node in nodes:
4965 displayer.show(repo[node])
4966 displayer.show(repo[node])
4966 displayer.close()
4967 displayer.close()
4967 return 0
4968 return 0
4968
4969
4969 # ui.forcemerge is an internal variable, do not document
4970 # ui.forcemerge is an internal variable, do not document
4970 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4971 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4971 with ui.configoverride(overrides, b'merge'):
4972 with ui.configoverride(overrides, b'merge'):
4972 force = opts.get(b'force')
4973 force = opts.get(b'force')
4973 labels = [b'working copy', b'merge rev', b'common ancestor']
4974 labels = [b'working copy', b'merge rev', b'common ancestor']
4974 return hg.merge(ctx, force=force, labels=labels)
4975 return hg.merge(ctx, force=force, labels=labels)
4975
4976
4976
4977
4977 statemod.addunfinished(
4978 statemod.addunfinished(
4978 b'merge',
4979 b'merge',
4979 fname=None,
4980 fname=None,
4980 clearable=True,
4981 clearable=True,
4981 allowcommit=True,
4982 allowcommit=True,
4982 cmdmsg=_(b'outstanding uncommitted merge'),
4983 cmdmsg=_(b'outstanding uncommitted merge'),
4983 abortfunc=hg.abortmerge,
4984 abortfunc=hg.abortmerge,
4984 statushint=_(
4985 statushint=_(
4985 b'To continue: hg commit\nTo abort: hg merge --abort'
4986 b'To continue: hg commit\nTo abort: hg merge --abort'
4986 ),
4987 ),
4987 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4988 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4988 )
4989 )
4989
4990
4990
4991
4991 @command(
4992 @command(
4992 b'outgoing|out',
4993 b'outgoing|out',
4993 [
4994 [
4994 (
4995 (
4995 b'f',
4996 b'f',
4996 b'force',
4997 b'force',
4997 None,
4998 None,
4998 _(b'run even when the destination is unrelated'),
4999 _(b'run even when the destination is unrelated'),
4999 ),
5000 ),
5000 (
5001 (
5001 b'r',
5002 b'r',
5002 b'rev',
5003 b'rev',
5003 [],
5004 [],
5004 _(b'a changeset intended to be included in the destination'),
5005 _(b'a changeset intended to be included in the destination'),
5005 _(b'REV'),
5006 _(b'REV'),
5006 ),
5007 ),
5007 (b'n', b'newest-first', None, _(b'show newest record first')),
5008 (b'n', b'newest-first', None, _(b'show newest record first')),
5008 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
5009 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
5009 (
5010 (
5010 b'b',
5011 b'b',
5011 b'branch',
5012 b'branch',
5012 [],
5013 [],
5013 _(b'a specific branch you would like to push'),
5014 _(b'a specific branch you would like to push'),
5014 _(b'BRANCH'),
5015 _(b'BRANCH'),
5015 ),
5016 ),
5016 ]
5017 ]
5017 + logopts
5018 + logopts
5018 + remoteopts
5019 + remoteopts
5019 + subrepoopts,
5020 + subrepoopts,
5020 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
5021 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
5021 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5022 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5022 )
5023 )
5023 def outgoing(ui, repo, *dests, **opts):
5024 def outgoing(ui, repo, *dests, **opts):
5024 """show changesets not found in the destination
5025 """show changesets not found in the destination
5025
5026
5026 Show changesets not found in the specified destination repository
5027 Show changesets not found in the specified destination repository
5027 or the default push location. These are the changesets that would
5028 or the default push location. These are the changesets that would
5028 be pushed if a push was requested.
5029 be pushed if a push was requested.
5029
5030
5030 See pull for details of valid destination formats.
5031 See pull for details of valid destination formats.
5031
5032
5032 .. container:: verbose
5033 .. container:: verbose
5033
5034
5034 With -B/--bookmarks, the result of bookmark comparison between
5035 With -B/--bookmarks, the result of bookmark comparison between
5035 local and remote repositories is displayed. With -v/--verbose,
5036 local and remote repositories is displayed. With -v/--verbose,
5036 status is also displayed for each bookmark like below::
5037 status is also displayed for each bookmark like below::
5037
5038
5038 BM1 01234567890a added
5039 BM1 01234567890a added
5039 BM2 deleted
5040 BM2 deleted
5040 BM3 234567890abc advanced
5041 BM3 234567890abc advanced
5041 BM4 34567890abcd diverged
5042 BM4 34567890abcd diverged
5042 BM5 4567890abcde changed
5043 BM5 4567890abcde changed
5043
5044
5044 The action taken when pushing depends on the
5045 The action taken when pushing depends on the
5045 status of each bookmark:
5046 status of each bookmark:
5046
5047
5047 :``added``: push with ``-B`` will create it
5048 :``added``: push with ``-B`` will create it
5048 :``deleted``: push with ``-B`` will delete it
5049 :``deleted``: push with ``-B`` will delete it
5049 :``advanced``: push will update it
5050 :``advanced``: push will update it
5050 :``diverged``: push with ``-B`` will update it
5051 :``diverged``: push with ``-B`` will update it
5051 :``changed``: push with ``-B`` will update it
5052 :``changed``: push with ``-B`` will update it
5052
5053
5053 From the point of view of pushing behavior, bookmarks
5054 From the point of view of pushing behavior, bookmarks
5054 existing only in the remote repository are treated as
5055 existing only in the remote repository are treated as
5055 ``deleted``, even if it is in fact added remotely.
5056 ``deleted``, even if it is in fact added remotely.
5056
5057
5057 Returns 0 if there are outgoing changes, 1 otherwise.
5058 Returns 0 if there are outgoing changes, 1 otherwise.
5058 """
5059 """
5059 opts = pycompat.byteskwargs(opts)
5060 opts = pycompat.byteskwargs(opts)
5060 if opts.get(b'bookmarks'):
5061 if opts.get(b'bookmarks'):
5061 for path in urlutil.get_push_paths(repo, ui, dests):
5062 for path in urlutil.get_push_paths(repo, ui, dests):
5062 other = hg.peer(repo, opts, path)
5063 other = hg.peer(repo, opts, path)
5063 try:
5064 try:
5064 if b'bookmarks' not in other.listkeys(b'namespaces'):
5065 if b'bookmarks' not in other.listkeys(b'namespaces'):
5065 ui.warn(_(b"remote doesn't support bookmarks\n"))
5066 ui.warn(_(b"remote doesn't support bookmarks\n"))
5066 return 0
5067 return 0
5067 ui.status(
5068 ui.status(
5068 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
5069 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
5069 )
5070 )
5070 ui.pager(b'outgoing')
5071 ui.pager(b'outgoing')
5071 return bookmarks.outgoing(ui, repo, other)
5072 return bookmarks.outgoing(ui, repo, other)
5072 finally:
5073 finally:
5073 other.close()
5074 other.close()
5074
5075
5075 return hg.outgoing(ui, repo, dests, opts)
5076 return hg.outgoing(ui, repo, dests, opts)
5076
5077
5077
5078
5078 @command(
5079 @command(
5079 b'parents',
5080 b'parents',
5080 [
5081 [
5081 (
5082 (
5082 b'r',
5083 b'r',
5083 b'rev',
5084 b'rev',
5084 b'',
5085 b'',
5085 _(b'show parents of the specified revision'),
5086 _(b'show parents of the specified revision'),
5086 _(b'REV'),
5087 _(b'REV'),
5087 ),
5088 ),
5088 ]
5089 ]
5089 + templateopts,
5090 + templateopts,
5090 _(b'[-r REV] [FILE]'),
5091 _(b'[-r REV] [FILE]'),
5091 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5092 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5092 inferrepo=True,
5093 inferrepo=True,
5093 )
5094 )
5094 def parents(ui, repo, file_=None, **opts):
5095 def parents(ui, repo, file_=None, **opts):
5095 """show the parents of the working directory or revision (DEPRECATED)
5096 """show the parents of the working directory or revision (DEPRECATED)
5096
5097
5097 Print the working directory's parent revisions. If a revision is
5098 Print the working directory's parent revisions. If a revision is
5098 given via -r/--rev, the parent of that revision will be printed.
5099 given via -r/--rev, the parent of that revision will be printed.
5099 If a file argument is given, the revision in which the file was
5100 If a file argument is given, the revision in which the file was
5100 last changed (before the working directory revision or the
5101 last changed (before the working directory revision or the
5101 argument to --rev if given) is printed.
5102 argument to --rev if given) is printed.
5102
5103
5103 This command is equivalent to::
5104 This command is equivalent to::
5104
5105
5105 hg log -r "p1()+p2()" or
5106 hg log -r "p1()+p2()" or
5106 hg log -r "p1(REV)+p2(REV)" or
5107 hg log -r "p1(REV)+p2(REV)" or
5107 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5108 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5108 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5109 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5109
5110
5110 See :hg:`summary` and :hg:`help revsets` for related information.
5111 See :hg:`summary` and :hg:`help revsets` for related information.
5111
5112
5112 Returns 0 on success.
5113 Returns 0 on success.
5113 """
5114 """
5114
5115
5115 opts = pycompat.byteskwargs(opts)
5116 opts = pycompat.byteskwargs(opts)
5116 rev = opts.get(b'rev')
5117 rev = opts.get(b'rev')
5117 if rev:
5118 if rev:
5118 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5119 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5119 ctx = logcmdutil.revsingle(repo, rev, None)
5120 ctx = logcmdutil.revsingle(repo, rev, None)
5120
5121
5121 if file_:
5122 if file_:
5122 m = scmutil.match(ctx, (file_,), opts)
5123 m = scmutil.match(ctx, (file_,), opts)
5123 if m.anypats() or len(m.files()) != 1:
5124 if m.anypats() or len(m.files()) != 1:
5124 raise error.InputError(_(b'can only specify an explicit filename'))
5125 raise error.InputError(_(b'can only specify an explicit filename'))
5125 file_ = m.files()[0]
5126 file_ = m.files()[0]
5126 filenodes = []
5127 filenodes = []
5127 for cp in ctx.parents():
5128 for cp in ctx.parents():
5128 if not cp:
5129 if not cp:
5129 continue
5130 continue
5130 try:
5131 try:
5131 filenodes.append(cp.filenode(file_))
5132 filenodes.append(cp.filenode(file_))
5132 except error.LookupError:
5133 except error.LookupError:
5133 pass
5134 pass
5134 if not filenodes:
5135 if not filenodes:
5135 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5136 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5136 p = []
5137 p = []
5137 for fn in filenodes:
5138 for fn in filenodes:
5138 fctx = repo.filectx(file_, fileid=fn)
5139 fctx = repo.filectx(file_, fileid=fn)
5139 p.append(fctx.node())
5140 p.append(fctx.node())
5140 else:
5141 else:
5141 p = [cp.node() for cp in ctx.parents()]
5142 p = [cp.node() for cp in ctx.parents()]
5142
5143
5143 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5144 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5144 for n in p:
5145 for n in p:
5145 if n != repo.nullid:
5146 if n != repo.nullid:
5146 displayer.show(repo[n])
5147 displayer.show(repo[n])
5147 displayer.close()
5148 displayer.close()
5148
5149
5149
5150
5150 @command(
5151 @command(
5151 b'paths',
5152 b'paths',
5152 formatteropts,
5153 formatteropts,
5153 _(b'[NAME]'),
5154 _(b'[NAME]'),
5154 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5155 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5155 optionalrepo=True,
5156 optionalrepo=True,
5156 intents={INTENT_READONLY},
5157 intents={INTENT_READONLY},
5157 )
5158 )
5158 def paths(ui, repo, search=None, **opts):
5159 def paths(ui, repo, search=None, **opts):
5159 """show aliases for remote repositories
5160 """show aliases for remote repositories
5160
5161
5161 Show definition of symbolic path name NAME. If no name is given,
5162 Show definition of symbolic path name NAME. If no name is given,
5162 show definition of all available names.
5163 show definition of all available names.
5163
5164
5164 Option -q/--quiet suppresses all output when searching for NAME
5165 Option -q/--quiet suppresses all output when searching for NAME
5165 and shows only the path names when listing all definitions.
5166 and shows only the path names when listing all definitions.
5166
5167
5167 Path names are defined in the [paths] section of your
5168 Path names are defined in the [paths] section of your
5168 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5169 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5169 repository, ``.hg/hgrc`` is used, too.
5170 repository, ``.hg/hgrc`` is used, too.
5170
5171
5171 The path names ``default`` and ``default-push`` have a special
5172 The path names ``default`` and ``default-push`` have a special
5172 meaning. When performing a push or pull operation, they are used
5173 meaning. When performing a push or pull operation, they are used
5173 as fallbacks if no location is specified on the command-line.
5174 as fallbacks if no location is specified on the command-line.
5174 When ``default-push`` is set, it will be used for push and
5175 When ``default-push`` is set, it will be used for push and
5175 ``default`` will be used for pull; otherwise ``default`` is used
5176 ``default`` will be used for pull; otherwise ``default`` is used
5176 as the fallback for both. When cloning a repository, the clone
5177 as the fallback for both. When cloning a repository, the clone
5177 source is written as ``default`` in ``.hg/hgrc``.
5178 source is written as ``default`` in ``.hg/hgrc``.
5178
5179
5179 .. note::
5180 .. note::
5180
5181
5181 ``default`` and ``default-push`` apply to all inbound (e.g.
5182 ``default`` and ``default-push`` apply to all inbound (e.g.
5182 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5183 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5183 and :hg:`bundle`) operations.
5184 and :hg:`bundle`) operations.
5184
5185
5185 See :hg:`help urls` for more information.
5186 See :hg:`help urls` for more information.
5186
5187
5187 .. container:: verbose
5188 .. container:: verbose
5188
5189
5189 Template:
5190 Template:
5190
5191
5191 The following keywords are supported. See also :hg:`help templates`.
5192 The following keywords are supported. See also :hg:`help templates`.
5192
5193
5193 :name: String. Symbolic name of the path alias.
5194 :name: String. Symbolic name of the path alias.
5194 :pushurl: String. URL for push operations.
5195 :pushurl: String. URL for push operations.
5195 :url: String. URL or directory path for the other operations.
5196 :url: String. URL or directory path for the other operations.
5196
5197
5197 Returns 0 on success.
5198 Returns 0 on success.
5198 """
5199 """
5199
5200
5200 opts = pycompat.byteskwargs(opts)
5201 opts = pycompat.byteskwargs(opts)
5201
5202
5202 pathitems = urlutil.list_paths(ui, search)
5203 pathitems = urlutil.list_paths(ui, search)
5203 ui.pager(b'paths')
5204 ui.pager(b'paths')
5204
5205
5205 fm = ui.formatter(b'paths', opts)
5206 fm = ui.formatter(b'paths', opts)
5206 if fm.isplain():
5207 if fm.isplain():
5207 hidepassword = urlutil.hidepassword
5208 hidepassword = urlutil.hidepassword
5208 else:
5209 else:
5209 hidepassword = bytes
5210 hidepassword = bytes
5210 if ui.quiet:
5211 if ui.quiet:
5211 namefmt = b'%s\n'
5212 namefmt = b'%s\n'
5212 else:
5213 else:
5213 namefmt = b'%s = '
5214 namefmt = b'%s = '
5214 showsubopts = not search and not ui.quiet
5215 showsubopts = not search and not ui.quiet
5215
5216
5216 for name, path in pathitems:
5217 for name, path in pathitems:
5217 fm.startitem()
5218 fm.startitem()
5218 fm.condwrite(not search, b'name', namefmt, name)
5219 fm.condwrite(not search, b'name', namefmt, name)
5219 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5220 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5220 for subopt, value in sorted(path.suboptions.items()):
5221 for subopt, value in sorted(path.suboptions.items()):
5221 assert subopt not in (b'name', b'url')
5222 assert subopt not in (b'name', b'url')
5222 if showsubopts:
5223 if showsubopts:
5223 fm.plain(b'%s:%s = ' % (name, subopt))
5224 fm.plain(b'%s:%s = ' % (name, subopt))
5224 if isinstance(value, bool):
5225 if isinstance(value, bool):
5225 if value:
5226 if value:
5226 value = b'yes'
5227 value = b'yes'
5227 else:
5228 else:
5228 value = b'no'
5229 value = b'no'
5229 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5230 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5230
5231
5231 fm.end()
5232 fm.end()
5232
5233
5233 if search and not pathitems:
5234 if search and not pathitems:
5234 if not ui.quiet:
5235 if not ui.quiet:
5235 ui.warn(_(b"not found!\n"))
5236 ui.warn(_(b"not found!\n"))
5236 return 1
5237 return 1
5237 else:
5238 else:
5238 return 0
5239 return 0
5239
5240
5240
5241
5241 @command(
5242 @command(
5242 b'phase',
5243 b'phase',
5243 [
5244 [
5244 (b'p', b'public', False, _(b'set changeset phase to public')),
5245 (b'p', b'public', False, _(b'set changeset phase to public')),
5245 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5246 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5246 (b's', b'secret', False, _(b'set changeset phase to secret')),
5247 (b's', b'secret', False, _(b'set changeset phase to secret')),
5247 (b'f', b'force', False, _(b'allow to move boundary backward')),
5248 (b'f', b'force', False, _(b'allow to move boundary backward')),
5248 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5249 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5249 ],
5250 ],
5250 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5251 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5251 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5252 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5252 )
5253 )
5253 def phase(ui, repo, *revs, **opts):
5254 def phase(ui, repo, *revs, **opts):
5254 """set or show the current phase name
5255 """set or show the current phase name
5255
5256
5256 With no argument, show the phase name of the current revision(s).
5257 With no argument, show the phase name of the current revision(s).
5257
5258
5258 With one of -p/--public, -d/--draft or -s/--secret, change the
5259 With one of -p/--public, -d/--draft or -s/--secret, change the
5259 phase value of the specified revisions.
5260 phase value of the specified revisions.
5260
5261
5261 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5262 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5262 lower phase to a higher phase. Phases are ordered as follows::
5263 lower phase to a higher phase. Phases are ordered as follows::
5263
5264
5264 public < draft < secret
5265 public < draft < secret
5265
5266
5266 Returns 0 on success, 1 if some phases could not be changed.
5267 Returns 0 on success, 1 if some phases could not be changed.
5267
5268
5268 (For more information about the phases concept, see :hg:`help phases`.)
5269 (For more information about the phases concept, see :hg:`help phases`.)
5269 """
5270 """
5270 opts = pycompat.byteskwargs(opts)
5271 opts = pycompat.byteskwargs(opts)
5271 # search for a unique phase argument
5272 # search for a unique phase argument
5272 targetphase = None
5273 targetphase = None
5273 for idx, name in enumerate(phases.cmdphasenames):
5274 for idx, name in enumerate(phases.cmdphasenames):
5274 if opts[name]:
5275 if opts[name]:
5275 if targetphase is not None:
5276 if targetphase is not None:
5276 raise error.InputError(_(b'only one phase can be specified'))
5277 raise error.InputError(_(b'only one phase can be specified'))
5277 targetphase = idx
5278 targetphase = idx
5278
5279
5279 # look for specified revision
5280 # look for specified revision
5280 revs = list(revs)
5281 revs = list(revs)
5281 revs.extend(opts[b'rev'])
5282 revs.extend(opts[b'rev'])
5282 if revs:
5283 if revs:
5283 revs = logcmdutil.revrange(repo, revs)
5284 revs = logcmdutil.revrange(repo, revs)
5284 else:
5285 else:
5285 # display both parents as the second parent phase can influence
5286 # display both parents as the second parent phase can influence
5286 # the phase of a merge commit
5287 # the phase of a merge commit
5287 revs = [c.rev() for c in repo[None].parents()]
5288 revs = [c.rev() for c in repo[None].parents()]
5288
5289
5289 ret = 0
5290 ret = 0
5290 if targetphase is None:
5291 if targetphase is None:
5291 # display
5292 # display
5292 for r in revs:
5293 for r in revs:
5293 ctx = repo[r]
5294 ctx = repo[r]
5294 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5295 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5295 else:
5296 else:
5296 with repo.lock(), repo.transaction(b"phase") as tr:
5297 with repo.lock(), repo.transaction(b"phase") as tr:
5297 # set phase
5298 # set phase
5298 if not revs:
5299 if not revs:
5299 raise error.InputError(_(b'empty revision set'))
5300 raise error.InputError(_(b'empty revision set'))
5300 nodes = [repo[r].node() for r in revs]
5301 nodes = [repo[r].node() for r in revs]
5301 # moving revision from public to draft may hide them
5302 # moving revision from public to draft may hide them
5302 # We have to check result on an unfiltered repository
5303 # We have to check result on an unfiltered repository
5303 unfi = repo.unfiltered()
5304 unfi = repo.unfiltered()
5304 getphase = unfi._phasecache.phase
5305 getphase = unfi._phasecache.phase
5305 olddata = [getphase(unfi, r) for r in unfi]
5306 olddata = [getphase(unfi, r) for r in unfi]
5306 phases.advanceboundary(repo, tr, targetphase, nodes)
5307 phases.advanceboundary(repo, tr, targetphase, nodes)
5307 if opts[b'force']:
5308 if opts[b'force']:
5308 phases.retractboundary(repo, tr, targetphase, nodes)
5309 phases.retractboundary(repo, tr, targetphase, nodes)
5309 getphase = unfi._phasecache.phase
5310 getphase = unfi._phasecache.phase
5310 newdata = [getphase(unfi, r) for r in unfi]
5311 newdata = [getphase(unfi, r) for r in unfi]
5311 changes = sum(newdata[r] != olddata[r] for r in unfi)
5312 changes = sum(newdata[r] != olddata[r] for r in unfi)
5312 cl = unfi.changelog
5313 cl = unfi.changelog
5313 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5314 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5314 if rejected:
5315 if rejected:
5315 ui.warn(
5316 ui.warn(
5316 _(
5317 _(
5317 b'cannot move %i changesets to a higher '
5318 b'cannot move %i changesets to a higher '
5318 b'phase, use --force\n'
5319 b'phase, use --force\n'
5319 )
5320 )
5320 % len(rejected)
5321 % len(rejected)
5321 )
5322 )
5322 ret = 1
5323 ret = 1
5323 if changes:
5324 if changes:
5324 msg = _(b'phase changed for %i changesets\n') % changes
5325 msg = _(b'phase changed for %i changesets\n') % changes
5325 if ret:
5326 if ret:
5326 ui.status(msg)
5327 ui.status(msg)
5327 else:
5328 else:
5328 ui.note(msg)
5329 ui.note(msg)
5329 else:
5330 else:
5330 ui.warn(_(b'no phases changed\n'))
5331 ui.warn(_(b'no phases changed\n'))
5331 return ret
5332 return ret
5332
5333
5333
5334
5334 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5335 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5335 """Run after a changegroup has been added via pull/unbundle
5336 """Run after a changegroup has been added via pull/unbundle
5336
5337
5337 This takes arguments below:
5338 This takes arguments below:
5338
5339
5339 :modheads: change of heads by pull/unbundle
5340 :modheads: change of heads by pull/unbundle
5340 :optupdate: updating working directory is needed or not
5341 :optupdate: updating working directory is needed or not
5341 :checkout: update destination revision (or None to default destination)
5342 :checkout: update destination revision (or None to default destination)
5342 :brev: a name, which might be a bookmark to be activated after updating
5343 :brev: a name, which might be a bookmark to be activated after updating
5343
5344
5344 return True if update raise any conflict, False otherwise.
5345 return True if update raise any conflict, False otherwise.
5345 """
5346 """
5346 if modheads == 0:
5347 if modheads == 0:
5347 return False
5348 return False
5348 if optupdate:
5349 if optupdate:
5349 try:
5350 try:
5350 return hg.updatetotally(ui, repo, checkout, brev)
5351 return hg.updatetotally(ui, repo, checkout, brev)
5351 except error.UpdateAbort as inst:
5352 except error.UpdateAbort as inst:
5352 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5353 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5353 hint = inst.hint
5354 hint = inst.hint
5354 raise error.UpdateAbort(msg, hint=hint)
5355 raise error.UpdateAbort(msg, hint=hint)
5355 if modheads is not None and modheads > 1:
5356 if modheads is not None and modheads > 1:
5356 currentbranchheads = len(repo.branchheads())
5357 currentbranchheads = len(repo.branchheads())
5357 if currentbranchheads == modheads:
5358 if currentbranchheads == modheads:
5358 ui.status(
5359 ui.status(
5359 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5360 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5360 )
5361 )
5361 elif currentbranchheads > 1:
5362 elif currentbranchheads > 1:
5362 ui.status(
5363 ui.status(
5363 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5364 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5364 )
5365 )
5365 else:
5366 else:
5366 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5367 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5367 elif not ui.configbool(b'commands', b'update.requiredest'):
5368 elif not ui.configbool(b'commands', b'update.requiredest'):
5368 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5369 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5369 return False
5370 return False
5370
5371
5371
5372
5372 @command(
5373 @command(
5373 b'pull',
5374 b'pull',
5374 [
5375 [
5375 (
5376 (
5376 b'u',
5377 b'u',
5377 b'update',
5378 b'update',
5378 None,
5379 None,
5379 _(b'update to new branch head if new descendants were pulled'),
5380 _(b'update to new branch head if new descendants were pulled'),
5380 ),
5381 ),
5381 (
5382 (
5382 b'f',
5383 b'f',
5383 b'force',
5384 b'force',
5384 None,
5385 None,
5385 _(b'run even when remote repository is unrelated'),
5386 _(b'run even when remote repository is unrelated'),
5386 ),
5387 ),
5387 (
5388 (
5388 b'',
5389 b'',
5389 b'confirm',
5390 b'confirm',
5390 None,
5391 None,
5391 _(b'confirm pull before applying changes'),
5392 _(b'confirm pull before applying changes'),
5392 ),
5393 ),
5393 (
5394 (
5394 b'r',
5395 b'r',
5395 b'rev',
5396 b'rev',
5396 [],
5397 [],
5397 _(b'a remote changeset intended to be added'),
5398 _(b'a remote changeset intended to be added'),
5398 _(b'REV'),
5399 _(b'REV'),
5399 ),
5400 ),
5400 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5401 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5401 (
5402 (
5402 b'b',
5403 b'b',
5403 b'branch',
5404 b'branch',
5404 [],
5405 [],
5405 _(b'a specific branch you would like to pull'),
5406 _(b'a specific branch you would like to pull'),
5406 _(b'BRANCH'),
5407 _(b'BRANCH'),
5407 ),
5408 ),
5408 (
5409 (
5409 b'',
5410 b'',
5410 b'remote-hidden',
5411 b'remote-hidden',
5411 False,
5412 False,
5412 _(b"include changesets hidden on the remote (EXPERIMENTAL)"),
5413 _(b"include changesets hidden on the remote (EXPERIMENTAL)"),
5413 ),
5414 ),
5414 ]
5415 ]
5415 + remoteopts,
5416 + remoteopts,
5416 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5417 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5417 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5418 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5418 helpbasic=True,
5419 helpbasic=True,
5419 )
5420 )
5420 def pull(ui, repo, *sources, **opts):
5421 def pull(ui, repo, *sources, **opts):
5421 """pull changes from the specified source
5422 """pull changes from the specified source
5422
5423
5423 Pull changes from a remote repository to a local one.
5424 Pull changes from a remote repository to a local one.
5424
5425
5425 This finds all changes from the repository at the specified path
5426 This finds all changes from the repository at the specified path
5426 or URL and adds them to a local repository (the current one unless
5427 or URL and adds them to a local repository (the current one unless
5427 -R is specified). By default, this does not update the copy of the
5428 -R is specified). By default, this does not update the copy of the
5428 project in the working directory.
5429 project in the working directory.
5429
5430
5430 When cloning from servers that support it, Mercurial may fetch
5431 When cloning from servers that support it, Mercurial may fetch
5431 pre-generated data. When this is done, hooks operating on incoming
5432 pre-generated data. When this is done, hooks operating on incoming
5432 changesets and changegroups may fire more than once, once for each
5433 changesets and changegroups may fire more than once, once for each
5433 pre-generated bundle and as well as for any additional remaining
5434 pre-generated bundle and as well as for any additional remaining
5434 data. See :hg:`help -e clonebundles` for more.
5435 data. See :hg:`help -e clonebundles` for more.
5435
5436
5436 Use :hg:`incoming` if you want to see what would have been added
5437 Use :hg:`incoming` if you want to see what would have been added
5437 by a pull at the time you issued this command. If you then decide
5438 by a pull at the time you issued this command. If you then decide
5438 to add those changes to the repository, you should use :hg:`pull
5439 to add those changes to the repository, you should use :hg:`pull
5439 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5440 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5440
5441
5441 If SOURCE is omitted, the 'default' path will be used.
5442 If SOURCE is omitted, the 'default' path will be used.
5442 See :hg:`help urls` for more information.
5443 See :hg:`help urls` for more information.
5443
5444
5444 If multiple sources are specified, they will be pulled sequentially as if
5445 If multiple sources are specified, they will be pulled sequentially as if
5445 the command was run multiple time. If --update is specify and the command
5446 the command was run multiple time. If --update is specify and the command
5446 will stop at the first failed --update.
5447 will stop at the first failed --update.
5447
5448
5448 Specifying bookmark as ``.`` is equivalent to specifying the active
5449 Specifying bookmark as ``.`` is equivalent to specifying the active
5449 bookmark's name.
5450 bookmark's name.
5450
5451
5451 .. container:: verbose
5452 .. container:: verbose
5452
5453
5453 One can use the `--remote-hidden` flag to pull changesets
5454 One can use the `--remote-hidden` flag to pull changesets
5454 hidden on the remote. This flag is "best effort", and will only
5455 hidden on the remote. This flag is "best effort", and will only
5455 work if the server supports the feature and is configured to
5456 work if the server supports the feature and is configured to
5456 allow the user to access hidden changesets. This option is
5457 allow the user to access hidden changesets. This option is
5457 experimental and backwards compatibility is not garanteed.
5458 experimental and backwards compatibility is not garanteed.
5458
5459
5459 Returns 0 on success, 1 if an update had unresolved files.
5460 Returns 0 on success, 1 if an update had unresolved files.
5460 """
5461 """
5461
5462
5462 opts = pycompat.byteskwargs(opts)
5463 opts = pycompat.byteskwargs(opts)
5463 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5464 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5464 b'update'
5465 b'update'
5465 ):
5466 ):
5466 msg = _(b'update destination required by configuration')
5467 msg = _(b'update destination required by configuration')
5467 hint = _(b'use hg pull followed by hg update DEST')
5468 hint = _(b'use hg pull followed by hg update DEST')
5468 raise error.InputError(msg, hint=hint)
5469 raise error.InputError(msg, hint=hint)
5469
5470
5470 for path in urlutil.get_pull_paths(repo, ui, sources):
5471 for path in urlutil.get_pull_paths(repo, ui, sources):
5471 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(path.loc))
5472 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(path.loc))
5472 ui.flush()
5473 ui.flush()
5473 other = hg.peer(repo, opts, path, remotehidden=opts[b'remote_hidden'])
5474 other = hg.peer(repo, opts, path, remotehidden=opts[b'remote_hidden'])
5474 update_conflict = None
5475 update_conflict = None
5475 try:
5476 try:
5476 branches = (path.branch, opts.get(b'branch', []))
5477 branches = (path.branch, opts.get(b'branch', []))
5477 revs, checkout = hg.addbranchrevs(
5478 revs, checkout = hg.addbranchrevs(
5478 repo,
5479 repo,
5479 other,
5480 other,
5480 branches,
5481 branches,
5481 opts.get(b'rev'),
5482 opts.get(b'rev'),
5482 remotehidden=opts[b'remote_hidden'],
5483 remotehidden=opts[b'remote_hidden'],
5483 )
5484 )
5484
5485
5485 pullopargs = {}
5486 pullopargs = {}
5486
5487
5487 nodes = None
5488 nodes = None
5488 if opts.get(b'bookmark') or revs:
5489 if opts.get(b'bookmark') or revs:
5489 # The list of bookmark used here is the same used to actually update
5490 # The list of bookmark used here is the same used to actually update
5490 # the bookmark names, to avoid the race from issue 4689 and we do
5491 # the bookmark names, to avoid the race from issue 4689 and we do
5491 # all lookup and bookmark queries in one go so they see the same
5492 # all lookup and bookmark queries in one go so they see the same
5492 # version of the server state (issue 4700).
5493 # version of the server state (issue 4700).
5493 nodes = []
5494 nodes = []
5494 fnodes = []
5495 fnodes = []
5495 revs = revs or []
5496 revs = revs or []
5496 if revs and not other.capable(b'lookup'):
5497 if revs and not other.capable(b'lookup'):
5497 err = _(
5498 err = _(
5498 b"other repository doesn't support revision lookup, "
5499 b"other repository doesn't support revision lookup, "
5499 b"so a rev cannot be specified."
5500 b"so a rev cannot be specified."
5500 )
5501 )
5501 raise error.Abort(err)
5502 raise error.Abort(err)
5502 with other.commandexecutor() as e:
5503 with other.commandexecutor() as e:
5503 fremotebookmarks = e.callcommand(
5504 fremotebookmarks = e.callcommand(
5504 b'listkeys', {b'namespace': b'bookmarks'}
5505 b'listkeys', {b'namespace': b'bookmarks'}
5505 )
5506 )
5506 for r in revs:
5507 for r in revs:
5507 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5508 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5508 remotebookmarks = fremotebookmarks.result()
5509 remotebookmarks = fremotebookmarks.result()
5509 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5510 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5510 pullopargs[b'remotebookmarks'] = remotebookmarks
5511 pullopargs[b'remotebookmarks'] = remotebookmarks
5511 for b in opts.get(b'bookmark', []):
5512 for b in opts.get(b'bookmark', []):
5512 b = repo._bookmarks.expandname(b)
5513 b = repo._bookmarks.expandname(b)
5513 if b not in remotebookmarks:
5514 if b not in remotebookmarks:
5514 raise error.InputError(
5515 raise error.InputError(
5515 _(b'remote bookmark %s not found!') % b
5516 _(b'remote bookmark %s not found!') % b
5516 )
5517 )
5517 nodes.append(remotebookmarks[b])
5518 nodes.append(remotebookmarks[b])
5518 for i, rev in enumerate(revs):
5519 for i, rev in enumerate(revs):
5519 node = fnodes[i].result()
5520 node = fnodes[i].result()
5520 nodes.append(node)
5521 nodes.append(node)
5521 if rev == checkout:
5522 if rev == checkout:
5522 checkout = node
5523 checkout = node
5523
5524
5524 wlock = util.nullcontextmanager()
5525 wlock = util.nullcontextmanager()
5525 if opts.get(b'update'):
5526 if opts.get(b'update'):
5526 wlock = repo.wlock()
5527 wlock = repo.wlock()
5527 with wlock:
5528 with wlock:
5528 pullopargs.update(opts.get(b'opargs', {}))
5529 pullopargs.update(opts.get(b'opargs', {}))
5529 modheads = exchange.pull(
5530 modheads = exchange.pull(
5530 repo,
5531 repo,
5531 other,
5532 other,
5532 path=path,
5533 path=path,
5533 heads=nodes,
5534 heads=nodes,
5534 force=opts.get(b'force'),
5535 force=opts.get(b'force'),
5535 bookmarks=opts.get(b'bookmark', ()),
5536 bookmarks=opts.get(b'bookmark', ()),
5536 opargs=pullopargs,
5537 opargs=pullopargs,
5537 confirm=opts.get(b'confirm'),
5538 confirm=opts.get(b'confirm'),
5538 ).cgresult
5539 ).cgresult
5539
5540
5540 # brev is a name, which might be a bookmark to be activated at
5541 # brev is a name, which might be a bookmark to be activated at
5541 # the end of the update. In other words, it is an explicit
5542 # the end of the update. In other words, it is an explicit
5542 # destination of the update
5543 # destination of the update
5543 brev = None
5544 brev = None
5544
5545
5545 if checkout:
5546 if checkout:
5546 checkout = repo.unfiltered().changelog.rev(checkout)
5547 checkout = repo.unfiltered().changelog.rev(checkout)
5547
5548
5548 # order below depends on implementation of
5549 # order below depends on implementation of
5549 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5550 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5550 # because 'checkout' is determined without it.
5551 # because 'checkout' is determined without it.
5551 if opts.get(b'rev'):
5552 if opts.get(b'rev'):
5552 brev = opts[b'rev'][0]
5553 brev = opts[b'rev'][0]
5553 elif opts.get(b'branch'):
5554 elif opts.get(b'branch'):
5554 brev = opts[b'branch'][0]
5555 brev = opts[b'branch'][0]
5555 else:
5556 else:
5556 brev = path.branch
5557 brev = path.branch
5557
5558
5558 # XXX path: we are losing the `path` object here. Keeping it
5559 # XXX path: we are losing the `path` object here. Keeping it
5559 # would be valuable. For example as a "variant" as we do
5560 # would be valuable. For example as a "variant" as we do
5560 # for pushes.
5561 # for pushes.
5561 repo._subtoppath = path.loc
5562 repo._subtoppath = path.loc
5562 try:
5563 try:
5563 update_conflict = postincoming(
5564 update_conflict = postincoming(
5564 ui, repo, modheads, opts.get(b'update'), checkout, brev
5565 ui, repo, modheads, opts.get(b'update'), checkout, brev
5565 )
5566 )
5566 except error.FilteredRepoLookupError as exc:
5567 except error.FilteredRepoLookupError as exc:
5567 msg = _(b'cannot update to target: %s') % exc.args[0]
5568 msg = _(b'cannot update to target: %s') % exc.args[0]
5568 exc.args = (msg,) + exc.args[1:]
5569 exc.args = (msg,) + exc.args[1:]
5569 raise
5570 raise
5570 finally:
5571 finally:
5571 del repo._subtoppath
5572 del repo._subtoppath
5572
5573
5573 finally:
5574 finally:
5574 other.close()
5575 other.close()
5575 # skip the remaining pull source if they are some conflict.
5576 # skip the remaining pull source if they are some conflict.
5576 if update_conflict:
5577 if update_conflict:
5577 break
5578 break
5578 if update_conflict:
5579 if update_conflict:
5579 return 1
5580 return 1
5580 else:
5581 else:
5581 return 0
5582 return 0
5582
5583
5583
5584
5584 @command(
5585 @command(
5585 b'purge|clean',
5586 b'purge|clean',
5586 [
5587 [
5587 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5588 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5588 (b'', b'all', None, _(b'purge ignored files too')),
5589 (b'', b'all', None, _(b'purge ignored files too')),
5589 (b'i', b'ignored', None, _(b'purge only ignored files')),
5590 (b'i', b'ignored', None, _(b'purge only ignored files')),
5590 (b'', b'dirs', None, _(b'purge empty directories')),
5591 (b'', b'dirs', None, _(b'purge empty directories')),
5591 (b'', b'files', None, _(b'purge files')),
5592 (b'', b'files', None, _(b'purge files')),
5592 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5593 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5593 (
5594 (
5594 b'0',
5595 b'0',
5595 b'print0',
5596 b'print0',
5596 None,
5597 None,
5597 _(
5598 _(
5598 b'end filenames with NUL, for use with xargs'
5599 b'end filenames with NUL, for use with xargs'
5599 b' (implies -p/--print)'
5600 b' (implies -p/--print)'
5600 ),
5601 ),
5601 ),
5602 ),
5602 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5603 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5603 ]
5604 ]
5604 + cmdutil.walkopts,
5605 + cmdutil.walkopts,
5605 _(b'hg purge [OPTION]... [DIR]...'),
5606 _(b'hg purge [OPTION]... [DIR]...'),
5606 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5607 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5607 )
5608 )
5608 def purge(ui, repo, *dirs, **opts):
5609 def purge(ui, repo, *dirs, **opts):
5609 """removes files not tracked by Mercurial
5610 """removes files not tracked by Mercurial
5610
5611
5611 Delete files not known to Mercurial. This is useful to test local
5612 Delete files not known to Mercurial. This is useful to test local
5612 and uncommitted changes in an otherwise-clean source tree.
5613 and uncommitted changes in an otherwise-clean source tree.
5613
5614
5614 This means that purge will delete the following by default:
5615 This means that purge will delete the following by default:
5615
5616
5616 - Unknown files: files marked with "?" by :hg:`status`
5617 - Unknown files: files marked with "?" by :hg:`status`
5617 - Empty directories: in fact Mercurial ignores directories unless
5618 - Empty directories: in fact Mercurial ignores directories unless
5618 they contain files under source control management
5619 they contain files under source control management
5619
5620
5620 But it will leave untouched:
5621 But it will leave untouched:
5621
5622
5622 - Modified and unmodified tracked files
5623 - Modified and unmodified tracked files
5623 - Ignored files (unless -i or --all is specified)
5624 - Ignored files (unless -i or --all is specified)
5624 - New files added to the repository (with :hg:`add`)
5625 - New files added to the repository (with :hg:`add`)
5625
5626
5626 The --files and --dirs options can be used to direct purge to delete
5627 The --files and --dirs options can be used to direct purge to delete
5627 only files, only directories, or both. If neither option is given,
5628 only files, only directories, or both. If neither option is given,
5628 both will be deleted.
5629 both will be deleted.
5629
5630
5630 If directories are given on the command line, only files in these
5631 If directories are given on the command line, only files in these
5631 directories are considered.
5632 directories are considered.
5632
5633
5633 Be careful with purge, as you could irreversibly delete some files
5634 Be careful with purge, as you could irreversibly delete some files
5634 you forgot to add to the repository. If you only want to print the
5635 you forgot to add to the repository. If you only want to print the
5635 list of files that this program would delete, use the --print
5636 list of files that this program would delete, use the --print
5636 option.
5637 option.
5637 """
5638 """
5638 opts = pycompat.byteskwargs(opts)
5639 opts = pycompat.byteskwargs(opts)
5639 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5640 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5640
5641
5641 act = not opts.get(b'print')
5642 act = not opts.get(b'print')
5642 eol = b'\n'
5643 eol = b'\n'
5643 if opts.get(b'print0'):
5644 if opts.get(b'print0'):
5644 eol = b'\0'
5645 eol = b'\0'
5645 act = False # --print0 implies --print
5646 act = False # --print0 implies --print
5646 if opts.get(b'all', False):
5647 if opts.get(b'all', False):
5647 ignored = True
5648 ignored = True
5648 unknown = True
5649 unknown = True
5649 else:
5650 else:
5650 ignored = opts.get(b'ignored', False)
5651 ignored = opts.get(b'ignored', False)
5651 unknown = not ignored
5652 unknown = not ignored
5652
5653
5653 removefiles = opts.get(b'files')
5654 removefiles = opts.get(b'files')
5654 removedirs = opts.get(b'dirs')
5655 removedirs = opts.get(b'dirs')
5655 confirm = opts.get(b'confirm')
5656 confirm = opts.get(b'confirm')
5656 if confirm is None:
5657 if confirm is None:
5657 try:
5658 try:
5658 extensions.find(b'purge')
5659 extensions.find(b'purge')
5659 confirm = False
5660 confirm = False
5660 except KeyError:
5661 except KeyError:
5661 confirm = True
5662 confirm = True
5662
5663
5663 if not removefiles and not removedirs:
5664 if not removefiles and not removedirs:
5664 removefiles = True
5665 removefiles = True
5665 removedirs = True
5666 removedirs = True
5666
5667
5667 match = scmutil.match(repo[None], dirs, opts)
5668 match = scmutil.match(repo[None], dirs, opts)
5668
5669
5669 paths = mergemod.purge(
5670 paths = mergemod.purge(
5670 repo,
5671 repo,
5671 match,
5672 match,
5672 unknown=unknown,
5673 unknown=unknown,
5673 ignored=ignored,
5674 ignored=ignored,
5674 removeemptydirs=removedirs,
5675 removeemptydirs=removedirs,
5675 removefiles=removefiles,
5676 removefiles=removefiles,
5676 abortonerror=opts.get(b'abort_on_err'),
5677 abortonerror=opts.get(b'abort_on_err'),
5677 noop=not act,
5678 noop=not act,
5678 confirm=confirm,
5679 confirm=confirm,
5679 )
5680 )
5680
5681
5681 for path in paths:
5682 for path in paths:
5682 if not act:
5683 if not act:
5683 ui.write(b'%s%s' % (path, eol))
5684 ui.write(b'%s%s' % (path, eol))
5684
5685
5685
5686
5686 @command(
5687 @command(
5687 b'push',
5688 b'push',
5688 [
5689 [
5689 (b'f', b'force', None, _(b'force push')),
5690 (b'f', b'force', None, _(b'force push')),
5690 (
5691 (
5691 b'r',
5692 b'r',
5692 b'rev',
5693 b'rev',
5693 [],
5694 [],
5694 _(b'a changeset intended to be included in the destination'),
5695 _(b'a changeset intended to be included in the destination'),
5695 _(b'REV'),
5696 _(b'REV'),
5696 ),
5697 ),
5697 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5698 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5698 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5699 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5699 (
5700 (
5700 b'b',
5701 b'b',
5701 b'branch',
5702 b'branch',
5702 [],
5703 [],
5703 _(b'a specific branch you would like to push'),
5704 _(b'a specific branch you would like to push'),
5704 _(b'BRANCH'),
5705 _(b'BRANCH'),
5705 ),
5706 ),
5706 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5707 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5707 (
5708 (
5708 b'',
5709 b'',
5709 b'pushvars',
5710 b'pushvars',
5710 [],
5711 [],
5711 _(b'variables that can be sent to server (ADVANCED)'),
5712 _(b'variables that can be sent to server (ADVANCED)'),
5712 ),
5713 ),
5713 (
5714 (
5714 b'',
5715 b'',
5715 b'publish',
5716 b'publish',
5716 False,
5717 False,
5717 _(b'push the changeset as public (EXPERIMENTAL)'),
5718 _(b'push the changeset as public (EXPERIMENTAL)'),
5718 ),
5719 ),
5719 ]
5720 ]
5720 + remoteopts,
5721 + remoteopts,
5721 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5722 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5722 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5723 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5723 helpbasic=True,
5724 helpbasic=True,
5724 )
5725 )
5725 def push(ui, repo, *dests, **opts):
5726 def push(ui, repo, *dests, **opts):
5726 """push changes to the specified destination
5727 """push changes to the specified destination
5727
5728
5728 Push changesets from the local repository to the specified
5729 Push changesets from the local repository to the specified
5729 destination.
5730 destination.
5730
5731
5731 This operation is symmetrical to pull: it is identical to a pull
5732 This operation is symmetrical to pull: it is identical to a pull
5732 in the destination repository from the current one.
5733 in the destination repository from the current one.
5733
5734
5734 By default, push will not allow creation of new heads at the
5735 By default, push will not allow creation of new heads at the
5735 destination, since multiple heads would make it unclear which head
5736 destination, since multiple heads would make it unclear which head
5736 to use. In this situation, it is recommended to pull and merge
5737 to use. In this situation, it is recommended to pull and merge
5737 before pushing.
5738 before pushing.
5738
5739
5739 Use --new-branch if you want to allow push to create a new named
5740 Use --new-branch if you want to allow push to create a new named
5740 branch that is not present at the destination. This allows you to
5741 branch that is not present at the destination. This allows you to
5741 only create a new branch without forcing other changes.
5742 only create a new branch without forcing other changes.
5742
5743
5743 .. note::
5744 .. note::
5744
5745
5745 Extra care should be taken with the -f/--force option,
5746 Extra care should be taken with the -f/--force option,
5746 which will push all new heads on all branches, an action which will
5747 which will push all new heads on all branches, an action which will
5747 almost always cause confusion for collaborators.
5748 almost always cause confusion for collaborators.
5748
5749
5749 If -r/--rev is used, the specified revision and all its ancestors
5750 If -r/--rev is used, the specified revision and all its ancestors
5750 will be pushed to the remote repository.
5751 will be pushed to the remote repository.
5751
5752
5752 If -B/--bookmark is used, the specified bookmarked revision, its
5753 If -B/--bookmark is used, the specified bookmarked revision, its
5753 ancestors, and the bookmark will be pushed to the remote
5754 ancestors, and the bookmark will be pushed to the remote
5754 repository. Specifying ``.`` is equivalent to specifying the active
5755 repository. Specifying ``.`` is equivalent to specifying the active
5755 bookmark's name. Use the --all-bookmarks option for pushing all
5756 bookmark's name. Use the --all-bookmarks option for pushing all
5756 current bookmarks.
5757 current bookmarks.
5757
5758
5758 Please see :hg:`help urls` for important details about ``ssh://``
5759 Please see :hg:`help urls` for important details about ``ssh://``
5759 URLs. If DESTINATION is omitted, a default path will be used.
5760 URLs. If DESTINATION is omitted, a default path will be used.
5760
5761
5761 When passed multiple destinations, push will process them one after the
5762 When passed multiple destinations, push will process them one after the
5762 other, but stop should an error occur.
5763 other, but stop should an error occur.
5763
5764
5764 .. container:: verbose
5765 .. container:: verbose
5765
5766
5766 The --pushvars option sends strings to the server that become
5767 The --pushvars option sends strings to the server that become
5767 environment variables prepended with ``HG_USERVAR_``. For example,
5768 environment variables prepended with ``HG_USERVAR_``. For example,
5768 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5769 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5769 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5770 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5770
5771
5771 pushvars can provide for user-overridable hooks as well as set debug
5772 pushvars can provide for user-overridable hooks as well as set debug
5772 levels. One example is having a hook that blocks commits containing
5773 levels. One example is having a hook that blocks commits containing
5773 conflict markers, but enables the user to override the hook if the file
5774 conflict markers, but enables the user to override the hook if the file
5774 is using conflict markers for testing purposes or the file format has
5775 is using conflict markers for testing purposes or the file format has
5775 strings that look like conflict markers.
5776 strings that look like conflict markers.
5776
5777
5777 By default, servers will ignore `--pushvars`. To enable it add the
5778 By default, servers will ignore `--pushvars`. To enable it add the
5778 following to your configuration file::
5779 following to your configuration file::
5779
5780
5780 [push]
5781 [push]
5781 pushvars.server = true
5782 pushvars.server = true
5782
5783
5783 Returns 0 if push was successful, 1 if nothing to push.
5784 Returns 0 if push was successful, 1 if nothing to push.
5784 """
5785 """
5785
5786
5786 opts = pycompat.byteskwargs(opts)
5787 opts = pycompat.byteskwargs(opts)
5787
5788
5788 if opts.get(b'all_bookmarks'):
5789 if opts.get(b'all_bookmarks'):
5789 cmdutil.check_incompatible_arguments(
5790 cmdutil.check_incompatible_arguments(
5790 opts,
5791 opts,
5791 b'all_bookmarks',
5792 b'all_bookmarks',
5792 [b'bookmark', b'rev'],
5793 [b'bookmark', b'rev'],
5793 )
5794 )
5794 opts[b'bookmark'] = list(repo._bookmarks)
5795 opts[b'bookmark'] = list(repo._bookmarks)
5795
5796
5796 if opts.get(b'bookmark'):
5797 if opts.get(b'bookmark'):
5797 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5798 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5798 for b in opts[b'bookmark']:
5799 for b in opts[b'bookmark']:
5799 # translate -B options to -r so changesets get pushed
5800 # translate -B options to -r so changesets get pushed
5800 b = repo._bookmarks.expandname(b)
5801 b = repo._bookmarks.expandname(b)
5801 if b in repo._bookmarks:
5802 if b in repo._bookmarks:
5802 opts.setdefault(b'rev', []).append(b)
5803 opts.setdefault(b'rev', []).append(b)
5803 else:
5804 else:
5804 # if we try to push a deleted bookmark, translate it to null
5805 # if we try to push a deleted bookmark, translate it to null
5805 # this lets simultaneous -r, -b options continue working
5806 # this lets simultaneous -r, -b options continue working
5806 opts.setdefault(b'rev', []).append(b"null")
5807 opts.setdefault(b'rev', []).append(b"null")
5807
5808
5808 some_pushed = False
5809 some_pushed = False
5809 result = 0
5810 result = 0
5810 for path in urlutil.get_push_paths(repo, ui, dests):
5811 for path in urlutil.get_push_paths(repo, ui, dests):
5811 dest = path.loc
5812 dest = path.loc
5812 branches = (path.branch, opts.get(b'branch') or [])
5813 branches = (path.branch, opts.get(b'branch') or [])
5813 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5814 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5814 revs, checkout = hg.addbranchrevs(
5815 revs, checkout = hg.addbranchrevs(
5815 repo, repo, branches, opts.get(b'rev')
5816 repo, repo, branches, opts.get(b'rev')
5816 )
5817 )
5817 other = hg.peer(repo, opts, dest)
5818 other = hg.peer(repo, opts, dest)
5818
5819
5819 try:
5820 try:
5820 if revs:
5821 if revs:
5821 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5822 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5822 if not revs:
5823 if not revs:
5823 raise error.InputError(
5824 raise error.InputError(
5824 _(b"specified revisions evaluate to an empty set"),
5825 _(b"specified revisions evaluate to an empty set"),
5825 hint=_(b"use different revision arguments"),
5826 hint=_(b"use different revision arguments"),
5826 )
5827 )
5827 elif path.pushrev:
5828 elif path.pushrev:
5828 # It doesn't make any sense to specify ancestor revisions. So limit
5829 # It doesn't make any sense to specify ancestor revisions. So limit
5829 # to DAG heads to make discovery simpler.
5830 # to DAG heads to make discovery simpler.
5830 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5831 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5831 revs = scmutil.revrange(repo, [expr])
5832 revs = scmutil.revrange(repo, [expr])
5832 revs = [repo[rev].node() for rev in revs]
5833 revs = [repo[rev].node() for rev in revs]
5833 if not revs:
5834 if not revs:
5834 raise error.InputError(
5835 raise error.InputError(
5835 _(
5836 _(
5836 b'default push revset for path evaluates to an empty set'
5837 b'default push revset for path evaluates to an empty set'
5837 )
5838 )
5838 )
5839 )
5839 elif ui.configbool(b'commands', b'push.require-revs'):
5840 elif ui.configbool(b'commands', b'push.require-revs'):
5840 raise error.InputError(
5841 raise error.InputError(
5841 _(b'no revisions specified to push'),
5842 _(b'no revisions specified to push'),
5842 hint=_(b'did you mean "hg push -r ."?'),
5843 hint=_(b'did you mean "hg push -r ."?'),
5843 )
5844 )
5844
5845
5845 repo._subtoppath = dest
5846 repo._subtoppath = dest
5846 try:
5847 try:
5847 # push subrepos depth-first for coherent ordering
5848 # push subrepos depth-first for coherent ordering
5848 c = repo[b'.']
5849 c = repo[b'.']
5849 subs = c.substate # only repos that are committed
5850 subs = c.substate # only repos that are committed
5850 for s in sorted(subs):
5851 for s in sorted(subs):
5851 sub_result = c.sub(s).push(opts)
5852 sub_result = c.sub(s).push(opts)
5852 if sub_result == 0:
5853 if sub_result == 0:
5853 return 1
5854 return 1
5854 finally:
5855 finally:
5855 del repo._subtoppath
5856 del repo._subtoppath
5856
5857
5857 opargs = dict(
5858 opargs = dict(
5858 opts.get(b'opargs', {})
5859 opts.get(b'opargs', {})
5859 ) # copy opargs since we may mutate it
5860 ) # copy opargs since we may mutate it
5860 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5861 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5861
5862
5862 pushop = exchange.push(
5863 pushop = exchange.push(
5863 repo,
5864 repo,
5864 other,
5865 other,
5865 opts.get(b'force'),
5866 opts.get(b'force'),
5866 revs=revs,
5867 revs=revs,
5867 newbranch=opts.get(b'new_branch'),
5868 newbranch=opts.get(b'new_branch'),
5868 bookmarks=opts.get(b'bookmark', ()),
5869 bookmarks=opts.get(b'bookmark', ()),
5869 publish=opts.get(b'publish'),
5870 publish=opts.get(b'publish'),
5870 opargs=opargs,
5871 opargs=opargs,
5871 )
5872 )
5872
5873
5873 if pushop.cgresult == 0:
5874 if pushop.cgresult == 0:
5874 result = 1
5875 result = 1
5875 elif pushop.cgresult is not None:
5876 elif pushop.cgresult is not None:
5876 some_pushed = True
5877 some_pushed = True
5877
5878
5878 if pushop.bkresult is not None:
5879 if pushop.bkresult is not None:
5879 if pushop.bkresult == 2:
5880 if pushop.bkresult == 2:
5880 result = 2
5881 result = 2
5881 elif not result and pushop.bkresult:
5882 elif not result and pushop.bkresult:
5882 result = 2
5883 result = 2
5883
5884
5884 if result:
5885 if result:
5885 break
5886 break
5886
5887
5887 finally:
5888 finally:
5888 other.close()
5889 other.close()
5889 if result == 0 and not some_pushed:
5890 if result == 0 and not some_pushed:
5890 result = 1
5891 result = 1
5891 return result
5892 return result
5892
5893
5893
5894
5894 @command(
5895 @command(
5895 b'recover',
5896 b'recover',
5896 [
5897 [
5897 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5898 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5898 ],
5899 ],
5899 helpcategory=command.CATEGORY_MAINTENANCE,
5900 helpcategory=command.CATEGORY_MAINTENANCE,
5900 )
5901 )
5901 def recover(ui, repo, **opts):
5902 def recover(ui, repo, **opts):
5902 """roll back an interrupted transaction
5903 """roll back an interrupted transaction
5903
5904
5904 Recover from an interrupted commit or pull.
5905 Recover from an interrupted commit or pull.
5905
5906
5906 This command tries to fix the repository status after an
5907 This command tries to fix the repository status after an
5907 interrupted operation. It should only be necessary when Mercurial
5908 interrupted operation. It should only be necessary when Mercurial
5908 suggests it.
5909 suggests it.
5909
5910
5910 Returns 0 if successful, 1 if nothing to recover or verify fails.
5911 Returns 0 if successful, 1 if nothing to recover or verify fails.
5911 """
5912 """
5912 ret = repo.recover()
5913 ret = repo.recover()
5913 if ret:
5914 if ret:
5914 if opts['verify']:
5915 if opts['verify']:
5915 return hg.verify(repo)
5916 return hg.verify(repo)
5916 else:
5917 else:
5917 msg = _(
5918 msg = _(
5918 b"(verify step skipped, run `hg verify` to check your "
5919 b"(verify step skipped, run `hg verify` to check your "
5919 b"repository content)\n"
5920 b"repository content)\n"
5920 )
5921 )
5921 ui.warn(msg)
5922 ui.warn(msg)
5922 return 0
5923 return 0
5923 return 1
5924 return 1
5924
5925
5925
5926
5926 @command(
5927 @command(
5927 b'remove|rm',
5928 b'remove|rm',
5928 [
5929 [
5929 (b'A', b'after', None, _(b'record delete for missing files')),
5930 (b'A', b'after', None, _(b'record delete for missing files')),
5930 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5931 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5931 ]
5932 ]
5932 + subrepoopts
5933 + subrepoopts
5933 + walkopts
5934 + walkopts
5934 + dryrunopts,
5935 + dryrunopts,
5935 _(b'[OPTION]... FILE...'),
5936 _(b'[OPTION]... FILE...'),
5936 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5937 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5937 helpbasic=True,
5938 helpbasic=True,
5938 inferrepo=True,
5939 inferrepo=True,
5939 )
5940 )
5940 def remove(ui, repo, *pats, **opts):
5941 def remove(ui, repo, *pats, **opts):
5941 """remove the specified files on the next commit
5942 """remove the specified files on the next commit
5942
5943
5943 Schedule the indicated files for removal from the current branch.
5944 Schedule the indicated files for removal from the current branch.
5944
5945
5945 This command schedules the files to be removed at the next commit.
5946 This command schedules the files to be removed at the next commit.
5946 To undo a remove before that, see :hg:`revert`. To undo added
5947 To undo a remove before that, see :hg:`revert`. To undo added
5947 files, see :hg:`forget`.
5948 files, see :hg:`forget`.
5948
5949
5949 .. container:: verbose
5950 .. container:: verbose
5950
5951
5951 -A/--after can be used to remove only files that have already
5952 -A/--after can be used to remove only files that have already
5952 been deleted, -f/--force can be used to force deletion, and -Af
5953 been deleted, -f/--force can be used to force deletion, and -Af
5953 can be used to remove files from the next revision without
5954 can be used to remove files from the next revision without
5954 deleting them from the working directory.
5955 deleting them from the working directory.
5955
5956
5956 The following table details the behavior of remove for different
5957 The following table details the behavior of remove for different
5957 file states (columns) and option combinations (rows). The file
5958 file states (columns) and option combinations (rows). The file
5958 states are Added [A], Clean [C], Modified [M] and Missing [!]
5959 states are Added [A], Clean [C], Modified [M] and Missing [!]
5959 (as reported by :hg:`status`). The actions are Warn, Remove
5960 (as reported by :hg:`status`). The actions are Warn, Remove
5960 (from branch) and Delete (from disk):
5961 (from branch) and Delete (from disk):
5961
5962
5962 ========= == == == ==
5963 ========= == == == ==
5963 opt/state A C M !
5964 opt/state A C M !
5964 ========= == == == ==
5965 ========= == == == ==
5965 none W RD W R
5966 none W RD W R
5966 -f R RD RD R
5967 -f R RD RD R
5967 -A W W W R
5968 -A W W W R
5968 -Af R R R R
5969 -Af R R R R
5969 ========= == == == ==
5970 ========= == == == ==
5970
5971
5971 .. note::
5972 .. note::
5972
5973
5973 :hg:`remove` never deletes files in Added [A] state from the
5974 :hg:`remove` never deletes files in Added [A] state from the
5974 working directory, not even if ``--force`` is specified.
5975 working directory, not even if ``--force`` is specified.
5975
5976
5976 Returns 0 on success, 1 if any warnings encountered.
5977 Returns 0 on success, 1 if any warnings encountered.
5977 """
5978 """
5978
5979
5979 opts = pycompat.byteskwargs(opts)
5980 opts = pycompat.byteskwargs(opts)
5980 after, force = opts.get(b'after'), opts.get(b'force')
5981 after, force = opts.get(b'after'), opts.get(b'force')
5981 dryrun = opts.get(b'dry_run')
5982 dryrun = opts.get(b'dry_run')
5982 if not pats and not after:
5983 if not pats and not after:
5983 raise error.InputError(_(b'no files specified'))
5984 raise error.InputError(_(b'no files specified'))
5984
5985
5985 with repo.wlock(), repo.dirstate.changing_files(repo):
5986 with repo.wlock(), repo.dirstate.changing_files(repo):
5986 m = scmutil.match(repo[None], pats, opts)
5987 m = scmutil.match(repo[None], pats, opts)
5987 subrepos = opts.get(b'subrepos')
5988 subrepos = opts.get(b'subrepos')
5988 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5989 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5989 return cmdutil.remove(
5990 return cmdutil.remove(
5990 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5991 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5991 )
5992 )
5992
5993
5993
5994
5994 @command(
5995 @command(
5995 b'rename|move|mv',
5996 b'rename|move|mv',
5996 [
5997 [
5997 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5998 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5998 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5999 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5999 (
6000 (
6000 b'',
6001 b'',
6001 b'at-rev',
6002 b'at-rev',
6002 b'',
6003 b'',
6003 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
6004 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
6004 _(b'REV'),
6005 _(b'REV'),
6005 ),
6006 ),
6006 (
6007 (
6007 b'f',
6008 b'f',
6008 b'force',
6009 b'force',
6009 None,
6010 None,
6010 _(b'forcibly move over an existing managed file'),
6011 _(b'forcibly move over an existing managed file'),
6011 ),
6012 ),
6012 ]
6013 ]
6013 + walkopts
6014 + walkopts
6014 + dryrunopts,
6015 + dryrunopts,
6015 _(b'[OPTION]... SOURCE... DEST'),
6016 _(b'[OPTION]... SOURCE... DEST'),
6016 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6017 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6017 )
6018 )
6018 def rename(ui, repo, *pats, **opts):
6019 def rename(ui, repo, *pats, **opts):
6019 """rename files; equivalent of copy + remove
6020 """rename files; equivalent of copy + remove
6020
6021
6021 Mark dest as copies of sources; mark sources for deletion. If dest
6022 Mark dest as copies of sources; mark sources for deletion. If dest
6022 is a directory, copies are put in that directory. If dest is a
6023 is a directory, copies are put in that directory. If dest is a
6023 file, there can only be one source.
6024 file, there can only be one source.
6024
6025
6025 By default, this command copies the contents of files as they
6026 By default, this command copies the contents of files as they
6026 exist in the working directory. If invoked with -A/--after, the
6027 exist in the working directory. If invoked with -A/--after, the
6027 operation is recorded, but no copying is performed.
6028 operation is recorded, but no copying is performed.
6028
6029
6029 To undo marking a destination file as renamed, use --forget. With that
6030 To undo marking a destination file as renamed, use --forget. With that
6030 option, all given (positional) arguments are unmarked as renames. The
6031 option, all given (positional) arguments are unmarked as renames. The
6031 destination file(s) will be left in place (still tracked). The source
6032 destination file(s) will be left in place (still tracked). The source
6032 file(s) will not be restored. Note that :hg:`rename --forget` behaves
6033 file(s) will not be restored. Note that :hg:`rename --forget` behaves
6033 the same way as :hg:`copy --forget`.
6034 the same way as :hg:`copy --forget`.
6034
6035
6035 This command takes effect with the next commit by default.
6036 This command takes effect with the next commit by default.
6036
6037
6037 Returns 0 on success, 1 if errors are encountered.
6038 Returns 0 on success, 1 if errors are encountered.
6038 """
6039 """
6039 opts = pycompat.byteskwargs(opts)
6040 opts = pycompat.byteskwargs(opts)
6040 context = lambda repo: repo.dirstate.changing_files(repo)
6041 context = lambda repo: repo.dirstate.changing_files(repo)
6041 rev = opts.get(b'at_rev')
6042 rev = opts.get(b'at_rev')
6042 ctx = None
6043 ctx = None
6043 if rev:
6044 if rev:
6044 ctx = logcmdutil.revsingle(repo, rev)
6045 ctx = logcmdutil.revsingle(repo, rev)
6045 if ctx.rev() is not None:
6046 if ctx.rev() is not None:
6046
6047
6047 def context(repo):
6048 def context(repo):
6048 return util.nullcontextmanager()
6049 return util.nullcontextmanager()
6049
6050
6050 opts[b'at_rev'] = ctx.rev()
6051 opts[b'at_rev'] = ctx.rev()
6051 with repo.wlock(), context(repo):
6052 with repo.wlock(), context(repo):
6052 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6053 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6053
6054
6054
6055
6055 @command(
6056 @command(
6056 b'resolve',
6057 b'resolve',
6057 [
6058 [
6058 (b'a', b'all', None, _(b'select all unresolved files')),
6059 (b'a', b'all', None, _(b'select all unresolved files')),
6059 (b'l', b'list', None, _(b'list state of files needing merge')),
6060 (b'l', b'list', None, _(b'list state of files needing merge')),
6060 (b'm', b'mark', None, _(b'mark files as resolved')),
6061 (b'm', b'mark', None, _(b'mark files as resolved')),
6061 (b'u', b'unmark', None, _(b'mark files as unresolved')),
6062 (b'u', b'unmark', None, _(b'mark files as unresolved')),
6062 (b'n', b'no-status', None, _(b'hide status prefix')),
6063 (b'n', b'no-status', None, _(b'hide status prefix')),
6063 (b'', b're-merge', None, _(b're-merge files')),
6064 (b'', b're-merge', None, _(b're-merge files')),
6064 ]
6065 ]
6065 + mergetoolopts
6066 + mergetoolopts
6066 + walkopts
6067 + walkopts
6067 + formatteropts,
6068 + formatteropts,
6068 _(b'[OPTION]... [FILE]...'),
6069 _(b'[OPTION]... [FILE]...'),
6069 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6070 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6070 inferrepo=True,
6071 inferrepo=True,
6071 )
6072 )
6072 def resolve(ui, repo, *pats, **opts):
6073 def resolve(ui, repo, *pats, **opts):
6073 """redo merges or set/view the merge status of files
6074 """redo merges or set/view the merge status of files
6074
6075
6075 Merges with unresolved conflicts are often the result of
6076 Merges with unresolved conflicts are often the result of
6076 non-interactive merging using the ``internal:merge`` configuration
6077 non-interactive merging using the ``internal:merge`` configuration
6077 setting, or a command-line merge tool like ``diff3``. The resolve
6078 setting, or a command-line merge tool like ``diff3``. The resolve
6078 command is used to manage the files involved in a merge, after
6079 command is used to manage the files involved in a merge, after
6079 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6080 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6080 working directory must have two parents). See :hg:`help
6081 working directory must have two parents). See :hg:`help
6081 merge-tools` for information on configuring merge tools.
6082 merge-tools` for information on configuring merge tools.
6082
6083
6083 The resolve command can be used in the following ways:
6084 The resolve command can be used in the following ways:
6084
6085
6085 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
6086 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
6086 the specified files, discarding any previous merge attempts. Re-merging
6087 the specified files, discarding any previous merge attempts. Re-merging
6087 is not performed for files already marked as resolved. Use ``--all/-a``
6088 is not performed for files already marked as resolved. Use ``--all/-a``
6088 to select all unresolved files. ``--tool`` can be used to specify
6089 to select all unresolved files. ``--tool`` can be used to specify
6089 the merge tool used for the given files. It overrides the HGMERGE
6090 the merge tool used for the given files. It overrides the HGMERGE
6090 environment variable and your configuration files. Previous file
6091 environment variable and your configuration files. Previous file
6091 contents are saved with a ``.orig`` suffix.
6092 contents are saved with a ``.orig`` suffix.
6092
6093
6093 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6094 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6094 (e.g. after having manually fixed-up the files). The default is
6095 (e.g. after having manually fixed-up the files). The default is
6095 to mark all unresolved files.
6096 to mark all unresolved files.
6096
6097
6097 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6098 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6098 default is to mark all resolved files.
6099 default is to mark all resolved files.
6099
6100
6100 - :hg:`resolve -l`: list files which had or still have conflicts.
6101 - :hg:`resolve -l`: list files which had or still have conflicts.
6101 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6102 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6102 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6103 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6103 the list. See :hg:`help filesets` for details.
6104 the list. See :hg:`help filesets` for details.
6104
6105
6105 .. note::
6106 .. note::
6106
6107
6107 Mercurial will not let you commit files with unresolved merge
6108 Mercurial will not let you commit files with unresolved merge
6108 conflicts. You must use :hg:`resolve -m ...` before you can
6109 conflicts. You must use :hg:`resolve -m ...` before you can
6109 commit after a conflicting merge.
6110 commit after a conflicting merge.
6110
6111
6111 .. container:: verbose
6112 .. container:: verbose
6112
6113
6113 Template:
6114 Template:
6114
6115
6115 The following keywords are supported in addition to the common template
6116 The following keywords are supported in addition to the common template
6116 keywords and functions. See also :hg:`help templates`.
6117 keywords and functions. See also :hg:`help templates`.
6117
6118
6118 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6119 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6119 :path: String. Repository-absolute path of the file.
6120 :path: String. Repository-absolute path of the file.
6120
6121
6121 Returns 0 on success, 1 if any files fail a resolve attempt.
6122 Returns 0 on success, 1 if any files fail a resolve attempt.
6122 """
6123 """
6123
6124
6124 opts = pycompat.byteskwargs(opts)
6125 opts = pycompat.byteskwargs(opts)
6125 confirm = ui.configbool(b'commands', b'resolve.confirm')
6126 confirm = ui.configbool(b'commands', b'resolve.confirm')
6126 flaglist = b'all mark unmark list no_status re_merge'.split()
6127 flaglist = b'all mark unmark list no_status re_merge'.split()
6127 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6128 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6128
6129
6129 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6130 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6130 if actioncount > 1:
6131 if actioncount > 1:
6131 raise error.InputError(_(b"too many actions specified"))
6132 raise error.InputError(_(b"too many actions specified"))
6132 elif actioncount == 0 and ui.configbool(
6133 elif actioncount == 0 and ui.configbool(
6133 b'commands', b'resolve.explicit-re-merge'
6134 b'commands', b'resolve.explicit-re-merge'
6134 ):
6135 ):
6135 hint = _(b'use --mark, --unmark, --list or --re-merge')
6136 hint = _(b'use --mark, --unmark, --list or --re-merge')
6136 raise error.InputError(_(b'no action specified'), hint=hint)
6137 raise error.InputError(_(b'no action specified'), hint=hint)
6137 if pats and all:
6138 if pats and all:
6138 raise error.InputError(_(b"can't specify --all and patterns"))
6139 raise error.InputError(_(b"can't specify --all and patterns"))
6139 if not (all or pats or show or mark or unmark):
6140 if not (all or pats or show or mark or unmark):
6140 raise error.InputError(
6141 raise error.InputError(
6141 _(b'no files or directories specified'),
6142 _(b'no files or directories specified'),
6142 hint=b'use --all to re-merge all unresolved files',
6143 hint=b'use --all to re-merge all unresolved files',
6143 )
6144 )
6144
6145
6145 if confirm:
6146 if confirm:
6146 if all:
6147 if all:
6147 if ui.promptchoice(
6148 if ui.promptchoice(
6148 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6149 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6149 ):
6150 ):
6150 raise error.CanceledError(_(b'user quit'))
6151 raise error.CanceledError(_(b'user quit'))
6151 if mark and not pats:
6152 if mark and not pats:
6152 if ui.promptchoice(
6153 if ui.promptchoice(
6153 _(
6154 _(
6154 b'mark all unresolved files as resolved (yn)?'
6155 b'mark all unresolved files as resolved (yn)?'
6155 b'$$ &Yes $$ &No'
6156 b'$$ &Yes $$ &No'
6156 )
6157 )
6157 ):
6158 ):
6158 raise error.CanceledError(_(b'user quit'))
6159 raise error.CanceledError(_(b'user quit'))
6159 if unmark and not pats:
6160 if unmark and not pats:
6160 if ui.promptchoice(
6161 if ui.promptchoice(
6161 _(
6162 _(
6162 b'mark all resolved files as unresolved (yn)?'
6163 b'mark all resolved files as unresolved (yn)?'
6163 b'$$ &Yes $$ &No'
6164 b'$$ &Yes $$ &No'
6164 )
6165 )
6165 ):
6166 ):
6166 raise error.CanceledError(_(b'user quit'))
6167 raise error.CanceledError(_(b'user quit'))
6167
6168
6168 uipathfn = scmutil.getuipathfn(repo)
6169 uipathfn = scmutil.getuipathfn(repo)
6169
6170
6170 if show:
6171 if show:
6171 ui.pager(b'resolve')
6172 ui.pager(b'resolve')
6172 fm = ui.formatter(b'resolve', opts)
6173 fm = ui.formatter(b'resolve', opts)
6173 ms = mergestatemod.mergestate.read(repo)
6174 ms = mergestatemod.mergestate.read(repo)
6174 wctx = repo[None]
6175 wctx = repo[None]
6175 m = scmutil.match(wctx, pats, opts)
6176 m = scmutil.match(wctx, pats, opts)
6176
6177
6177 # Labels and keys based on merge state. Unresolved path conflicts show
6178 # Labels and keys based on merge state. Unresolved path conflicts show
6178 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6179 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6179 # resolved conflicts.
6180 # resolved conflicts.
6180 mergestateinfo = {
6181 mergestateinfo = {
6181 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6182 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6182 b'resolve.unresolved',
6183 b'resolve.unresolved',
6183 b'U',
6184 b'U',
6184 ),
6185 ),
6185 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6186 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6186 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6187 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6187 b'resolve.unresolved',
6188 b'resolve.unresolved',
6188 b'P',
6189 b'P',
6189 ),
6190 ),
6190 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6191 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6191 b'resolve.resolved',
6192 b'resolve.resolved',
6192 b'R',
6193 b'R',
6193 ),
6194 ),
6194 }
6195 }
6195
6196
6196 for f in ms:
6197 for f in ms:
6197 if not m(f):
6198 if not m(f):
6198 continue
6199 continue
6199
6200
6200 label, key = mergestateinfo[ms[f]]
6201 label, key = mergestateinfo[ms[f]]
6201 fm.startitem()
6202 fm.startitem()
6202 fm.context(ctx=wctx)
6203 fm.context(ctx=wctx)
6203 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6204 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6204 fm.data(path=f)
6205 fm.data(path=f)
6205 fm.plain(b'%s\n' % uipathfn(f), label=label)
6206 fm.plain(b'%s\n' % uipathfn(f), label=label)
6206 fm.end()
6207 fm.end()
6207 return 0
6208 return 0
6208
6209
6209 with repo.wlock():
6210 with repo.wlock():
6210 ms = mergestatemod.mergestate.read(repo)
6211 ms = mergestatemod.mergestate.read(repo)
6211
6212
6212 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6213 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6213 raise error.StateError(
6214 raise error.StateError(
6214 _(b'resolve command not applicable when not merging')
6215 _(b'resolve command not applicable when not merging')
6215 )
6216 )
6216
6217
6217 wctx = repo[None]
6218 wctx = repo[None]
6218 m = scmutil.match(wctx, pats, opts)
6219 m = scmutil.match(wctx, pats, opts)
6219 ret = 0
6220 ret = 0
6220 didwork = False
6221 didwork = False
6221
6222
6222 hasconflictmarkers = []
6223 hasconflictmarkers = []
6223 if mark:
6224 if mark:
6224 markcheck = ui.config(b'commands', b'resolve.mark-check')
6225 markcheck = ui.config(b'commands', b'resolve.mark-check')
6225 if markcheck not in [b'warn', b'abort']:
6226 if markcheck not in [b'warn', b'abort']:
6226 # Treat all invalid / unrecognized values as 'none'.
6227 # Treat all invalid / unrecognized values as 'none'.
6227 markcheck = False
6228 markcheck = False
6228 for f in ms:
6229 for f in ms:
6229 if not m(f):
6230 if not m(f):
6230 continue
6231 continue
6231
6232
6232 didwork = True
6233 didwork = True
6233
6234
6234 # path conflicts must be resolved manually
6235 # path conflicts must be resolved manually
6235 if ms[f] in (
6236 if ms[f] in (
6236 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6237 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6237 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6238 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6238 ):
6239 ):
6239 if mark:
6240 if mark:
6240 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6241 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6241 elif unmark:
6242 elif unmark:
6242 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6243 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6243 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6244 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6244 ui.warn(
6245 ui.warn(
6245 _(b'%s: path conflict must be resolved manually\n')
6246 _(b'%s: path conflict must be resolved manually\n')
6246 % uipathfn(f)
6247 % uipathfn(f)
6247 )
6248 )
6248 continue
6249 continue
6249
6250
6250 if mark:
6251 if mark:
6251 if markcheck:
6252 if markcheck:
6252 fdata = repo.wvfs.tryread(f)
6253 fdata = repo.wvfs.tryread(f)
6253 if (
6254 if (
6254 filemerge.hasconflictmarkers(fdata)
6255 filemerge.hasconflictmarkers(fdata)
6255 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6256 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6256 ):
6257 ):
6257 hasconflictmarkers.append(f)
6258 hasconflictmarkers.append(f)
6258 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6259 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6259 elif unmark:
6260 elif unmark:
6260 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6261 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6261 else:
6262 else:
6262 # backup pre-resolve (merge uses .orig for its own purposes)
6263 # backup pre-resolve (merge uses .orig for its own purposes)
6263 a = repo.wjoin(f)
6264 a = repo.wjoin(f)
6264 try:
6265 try:
6265 util.copyfile(a, a + b".resolve")
6266 util.copyfile(a, a + b".resolve")
6266 except FileNotFoundError:
6267 except FileNotFoundError:
6267 pass
6268 pass
6268
6269
6269 try:
6270 try:
6270 # preresolve file
6271 # preresolve file
6271 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6272 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6272 with ui.configoverride(overrides, b'resolve'):
6273 with ui.configoverride(overrides, b'resolve'):
6273 r = ms.resolve(f, wctx)
6274 r = ms.resolve(f, wctx)
6274 if r:
6275 if r:
6275 ret = 1
6276 ret = 1
6276 finally:
6277 finally:
6277 ms.commit()
6278 ms.commit()
6278
6279
6279 # replace filemerge's .orig file with our resolve file
6280 # replace filemerge's .orig file with our resolve file
6280 try:
6281 try:
6281 util.rename(
6282 util.rename(
6282 a + b".resolve", scmutil.backuppath(ui, repo, f)
6283 a + b".resolve", scmutil.backuppath(ui, repo, f)
6283 )
6284 )
6284 except FileNotFoundError:
6285 except FileNotFoundError:
6285 pass
6286 pass
6286
6287
6287 if hasconflictmarkers:
6288 if hasconflictmarkers:
6288 ui.warn(
6289 ui.warn(
6289 _(
6290 _(
6290 b'warning: the following files still have conflict '
6291 b'warning: the following files still have conflict '
6291 b'markers:\n'
6292 b'markers:\n'
6292 )
6293 )
6293 + b''.join(
6294 + b''.join(
6294 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6295 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6295 )
6296 )
6296 )
6297 )
6297 if markcheck == b'abort' and not all and not pats:
6298 if markcheck == b'abort' and not all and not pats:
6298 raise error.StateError(
6299 raise error.StateError(
6299 _(b'conflict markers detected'),
6300 _(b'conflict markers detected'),
6300 hint=_(b'use --all to mark anyway'),
6301 hint=_(b'use --all to mark anyway'),
6301 )
6302 )
6302
6303
6303 ms.commit()
6304 ms.commit()
6304 branchmerge = repo.dirstate.p2() != repo.nullid
6305 branchmerge = repo.dirstate.p2() != repo.nullid
6305 # resolve is not doing a parent change here, however, `record updates`
6306 # resolve is not doing a parent change here, however, `record updates`
6306 # will call some dirstate API that at intended for parent changes call.
6307 # will call some dirstate API that at intended for parent changes call.
6307 # Ideally we would not need this and could implement a lighter version
6308 # Ideally we would not need this and could implement a lighter version
6308 # of the recordupdateslogic that will not have to deal with the part
6309 # of the recordupdateslogic that will not have to deal with the part
6309 # related to parent changes. However this would requires that:
6310 # related to parent changes. However this would requires that:
6310 # - we are sure we passed around enough information at update/merge
6311 # - we are sure we passed around enough information at update/merge
6311 # time to no longer needs it at `hg resolve time`
6312 # time to no longer needs it at `hg resolve time`
6312 # - we are sure we store that information well enough to be able to reuse it
6313 # - we are sure we store that information well enough to be able to reuse it
6313 # - we are the necessary logic to reuse it right.
6314 # - we are the necessary logic to reuse it right.
6314 #
6315 #
6315 # All this should eventually happens, but in the mean time, we use this
6316 # All this should eventually happens, but in the mean time, we use this
6316 # context manager slightly out of the context it should be.
6317 # context manager slightly out of the context it should be.
6317 with repo.dirstate.changing_parents(repo):
6318 with repo.dirstate.changing_parents(repo):
6318 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6319 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6319
6320
6320 if not didwork and pats:
6321 if not didwork and pats:
6321 hint = None
6322 hint = None
6322 if not any([p for p in pats if p.find(b':') >= 0]):
6323 if not any([p for p in pats if p.find(b':') >= 0]):
6323 pats = [b'path:%s' % p for p in pats]
6324 pats = [b'path:%s' % p for p in pats]
6324 m = scmutil.match(wctx, pats, opts)
6325 m = scmutil.match(wctx, pats, opts)
6325 for f in ms:
6326 for f in ms:
6326 if not m(f):
6327 if not m(f):
6327 continue
6328 continue
6328
6329
6329 def flag(o):
6330 def flag(o):
6330 if o == b're_merge':
6331 if o == b're_merge':
6331 return b'--re-merge '
6332 return b'--re-merge '
6332 return b'-%s ' % o[0:1]
6333 return b'-%s ' % o[0:1]
6333
6334
6334 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6335 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6335 hint = _(b"(try: hg resolve %s%s)\n") % (
6336 hint = _(b"(try: hg resolve %s%s)\n") % (
6336 flags,
6337 flags,
6337 b' '.join(pats),
6338 b' '.join(pats),
6338 )
6339 )
6339 break
6340 break
6340 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6341 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6341 if hint:
6342 if hint:
6342 ui.warn(hint)
6343 ui.warn(hint)
6343
6344
6344 unresolvedf = ms.unresolvedcount()
6345 unresolvedf = ms.unresolvedcount()
6345 if not unresolvedf:
6346 if not unresolvedf:
6346 ui.status(_(b'(no more unresolved files)\n'))
6347 ui.status(_(b'(no more unresolved files)\n'))
6347 cmdutil.checkafterresolved(repo)
6348 cmdutil.checkafterresolved(repo)
6348
6349
6349 return ret
6350 return ret
6350
6351
6351
6352
6352 @command(
6353 @command(
6353 b'revert',
6354 b'revert',
6354 [
6355 [
6355 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6356 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6356 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6357 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6357 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6358 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6358 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6359 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6359 (b'i', b'interactive', None, _(b'interactively select the changes')),
6360 (b'i', b'interactive', None, _(b'interactively select the changes')),
6360 ]
6361 ]
6361 + walkopts
6362 + walkopts
6362 + dryrunopts,
6363 + dryrunopts,
6363 _(b'[OPTION]... [-r REV] [NAME]...'),
6364 _(b'[OPTION]... [-r REV] [NAME]...'),
6364 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6365 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6365 )
6366 )
6366 def revert(ui, repo, *pats, **opts):
6367 def revert(ui, repo, *pats, **opts):
6367 """restore files to their checkout state
6368 """restore files to their checkout state
6368
6369
6369 .. note::
6370 .. note::
6370
6371
6371 To check out earlier revisions, you should use :hg:`update REV`.
6372 To check out earlier revisions, you should use :hg:`update REV`.
6372 To cancel an uncommitted merge (and lose your changes),
6373 To cancel an uncommitted merge (and lose your changes),
6373 use :hg:`merge --abort`.
6374 use :hg:`merge --abort`.
6374
6375
6375 With no revision specified, revert the specified files or directories
6376 With no revision specified, revert the specified files or directories
6376 to the contents they had in the parent of the working directory.
6377 to the contents they had in the parent of the working directory.
6377 This restores the contents of files to an unmodified
6378 This restores the contents of files to an unmodified
6378 state and unschedules adds, removes, copies, and renames. If the
6379 state and unschedules adds, removes, copies, and renames. If the
6379 working directory has two parents, you must explicitly specify a
6380 working directory has two parents, you must explicitly specify a
6380 revision.
6381 revision.
6381
6382
6382 Using the -r/--rev or -d/--date options, revert the given files or
6383 Using the -r/--rev or -d/--date options, revert the given files or
6383 directories to their states as of a specific revision. Because
6384 directories to their states as of a specific revision. Because
6384 revert does not change the working directory parents, this will
6385 revert does not change the working directory parents, this will
6385 cause these files to appear modified. This can be helpful to "back
6386 cause these files to appear modified. This can be helpful to "back
6386 out" some or all of an earlier change. See :hg:`backout` for a
6387 out" some or all of an earlier change. See :hg:`backout` for a
6387 related method.
6388 related method.
6388
6389
6389 Modified files are saved with a .orig suffix before reverting.
6390 Modified files are saved with a .orig suffix before reverting.
6390 To disable these backups, use --no-backup. It is possible to store
6391 To disable these backups, use --no-backup. It is possible to store
6391 the backup files in a custom directory relative to the root of the
6392 the backup files in a custom directory relative to the root of the
6392 repository by setting the ``ui.origbackuppath`` configuration
6393 repository by setting the ``ui.origbackuppath`` configuration
6393 option.
6394 option.
6394
6395
6395 See :hg:`help dates` for a list of formats valid for -d/--date.
6396 See :hg:`help dates` for a list of formats valid for -d/--date.
6396
6397
6397 See :hg:`help backout` for a way to reverse the effect of an
6398 See :hg:`help backout` for a way to reverse the effect of an
6398 earlier changeset.
6399 earlier changeset.
6399
6400
6400 Returns 0 on success.
6401 Returns 0 on success.
6401 """
6402 """
6402
6403
6403 opts = pycompat.byteskwargs(opts)
6404 opts = pycompat.byteskwargs(opts)
6404 if opts.get(b"date"):
6405 if opts.get(b"date"):
6405 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6406 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6406 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6407 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6407
6408
6408 parent, p2 = repo.dirstate.parents()
6409 parent, p2 = repo.dirstate.parents()
6409 if not opts.get(b'rev') and p2 != repo.nullid:
6410 if not opts.get(b'rev') and p2 != repo.nullid:
6410 # revert after merge is a trap for new users (issue2915)
6411 # revert after merge is a trap for new users (issue2915)
6411 raise error.InputError(
6412 raise error.InputError(
6412 _(b'uncommitted merge with no revision specified'),
6413 _(b'uncommitted merge with no revision specified'),
6413 hint=_(b"use 'hg update' or see 'hg help revert'"),
6414 hint=_(b"use 'hg update' or see 'hg help revert'"),
6414 )
6415 )
6415
6416
6416 rev = opts.get(b'rev')
6417 rev = opts.get(b'rev')
6417 if rev:
6418 if rev:
6418 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6419 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6419 ctx = logcmdutil.revsingle(repo, rev)
6420 ctx = logcmdutil.revsingle(repo, rev)
6420
6421
6421 if not (
6422 if not (
6422 pats
6423 pats
6423 or opts.get(b'include')
6424 or opts.get(b'include')
6424 or opts.get(b'exclude')
6425 or opts.get(b'exclude')
6425 or opts.get(b'all')
6426 or opts.get(b'all')
6426 or opts.get(b'interactive')
6427 or opts.get(b'interactive')
6427 ):
6428 ):
6428 msg = _(b"no files or directories specified")
6429 msg = _(b"no files or directories specified")
6429 if p2 != repo.nullid:
6430 if p2 != repo.nullid:
6430 hint = _(
6431 hint = _(
6431 b"uncommitted merge, use --all to discard all changes,"
6432 b"uncommitted merge, use --all to discard all changes,"
6432 b" or 'hg update -C .' to abort the merge"
6433 b" or 'hg update -C .' to abort the merge"
6433 )
6434 )
6434 raise error.InputError(msg, hint=hint)
6435 raise error.InputError(msg, hint=hint)
6435 dirty = any(repo.status())
6436 dirty = any(repo.status())
6436 node = ctx.node()
6437 node = ctx.node()
6437 if node != parent:
6438 if node != parent:
6438 if dirty:
6439 if dirty:
6439 hint = (
6440 hint = (
6440 _(
6441 _(
6441 b"uncommitted changes, use --all to discard all"
6442 b"uncommitted changes, use --all to discard all"
6442 b" changes, or 'hg update %d' to update"
6443 b" changes, or 'hg update %d' to update"
6443 )
6444 )
6444 % ctx.rev()
6445 % ctx.rev()
6445 )
6446 )
6446 else:
6447 else:
6447 hint = (
6448 hint = (
6448 _(
6449 _(
6449 b"use --all to revert all files,"
6450 b"use --all to revert all files,"
6450 b" or 'hg update %d' to update"
6451 b" or 'hg update %d' to update"
6451 )
6452 )
6452 % ctx.rev()
6453 % ctx.rev()
6453 )
6454 )
6454 elif dirty:
6455 elif dirty:
6455 hint = _(b"uncommitted changes, use --all to discard all changes")
6456 hint = _(b"uncommitted changes, use --all to discard all changes")
6456 else:
6457 else:
6457 hint = _(b"use --all to revert all files")
6458 hint = _(b"use --all to revert all files")
6458 raise error.InputError(msg, hint=hint)
6459 raise error.InputError(msg, hint=hint)
6459
6460
6460 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6461 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6461
6462
6462
6463
6463 @command(
6464 @command(
6464 b'rollback',
6465 b'rollback',
6465 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6466 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6466 helpcategory=command.CATEGORY_MAINTENANCE,
6467 helpcategory=command.CATEGORY_MAINTENANCE,
6467 )
6468 )
6468 def rollback(ui, repo, **opts):
6469 def rollback(ui, repo, **opts):
6469 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6470 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6470
6471
6471 Please use :hg:`commit --amend` instead of rollback to correct
6472 Please use :hg:`commit --amend` instead of rollback to correct
6472 mistakes in the last commit.
6473 mistakes in the last commit.
6473
6474
6474 This command should be used with care. There is only one level of
6475 This command should be used with care. There is only one level of
6475 rollback, and there is no way to undo a rollback. It will also
6476 rollback, and there is no way to undo a rollback. It will also
6476 restore the dirstate at the time of the last transaction, losing
6477 restore the dirstate at the time of the last transaction, losing
6477 any dirstate changes since that time. This command does not alter
6478 any dirstate changes since that time. This command does not alter
6478 the working directory.
6479 the working directory.
6479
6480
6480 Transactions are used to encapsulate the effects of all commands
6481 Transactions are used to encapsulate the effects of all commands
6481 that create new changesets or propagate existing changesets into a
6482 that create new changesets or propagate existing changesets into a
6482 repository.
6483 repository.
6483
6484
6484 .. container:: verbose
6485 .. container:: verbose
6485
6486
6486 For example, the following commands are transactional, and their
6487 For example, the following commands are transactional, and their
6487 effects can be rolled back:
6488 effects can be rolled back:
6488
6489
6489 - commit
6490 - commit
6490 - import
6491 - import
6491 - pull
6492 - pull
6492 - push (with this repository as the destination)
6493 - push (with this repository as the destination)
6493 - unbundle
6494 - unbundle
6494
6495
6495 To avoid permanent data loss, rollback will refuse to rollback a
6496 To avoid permanent data loss, rollback will refuse to rollback a
6496 commit transaction if it isn't checked out. Use --force to
6497 commit transaction if it isn't checked out. Use --force to
6497 override this protection.
6498 override this protection.
6498
6499
6499 The rollback command can be entirely disabled by setting the
6500 The rollback command can be entirely disabled by setting the
6500 ``ui.rollback`` configuration setting to false. If you're here
6501 ``ui.rollback`` configuration setting to false. If you're here
6501 because you want to use rollback and it's disabled, you can
6502 because you want to use rollback and it's disabled, you can
6502 re-enable the command by setting ``ui.rollback`` to true.
6503 re-enable the command by setting ``ui.rollback`` to true.
6503
6504
6504 This command is not intended for use on public repositories. Once
6505 This command is not intended for use on public repositories. Once
6505 changes are visible for pull by other users, rolling a transaction
6506 changes are visible for pull by other users, rolling a transaction
6506 back locally is ineffective (someone else may already have pulled
6507 back locally is ineffective (someone else may already have pulled
6507 the changes). Furthermore, a race is possible with readers of the
6508 the changes). Furthermore, a race is possible with readers of the
6508 repository; for example an in-progress pull from the repository
6509 repository; for example an in-progress pull from the repository
6509 may fail if a rollback is performed.
6510 may fail if a rollback is performed.
6510
6511
6511 Returns 0 on success, 1 if no rollback data is available.
6512 Returns 0 on success, 1 if no rollback data is available.
6512 """
6513 """
6513 if not ui.configbool(b'ui', b'rollback'):
6514 if not ui.configbool(b'ui', b'rollback'):
6514 raise error.Abort(
6515 raise error.Abort(
6515 _(b'rollback is disabled because it is unsafe'),
6516 _(b'rollback is disabled because it is unsafe'),
6516 hint=b'see `hg help -v rollback` for information',
6517 hint=b'see `hg help -v rollback` for information',
6517 )
6518 )
6518 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6519 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6519
6520
6520
6521
6521 @command(
6522 @command(
6522 b'root',
6523 b'root',
6523 [] + formatteropts,
6524 [] + formatteropts,
6524 intents={INTENT_READONLY},
6525 intents={INTENT_READONLY},
6525 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6526 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6526 )
6527 )
6527 def root(ui, repo, **opts):
6528 def root(ui, repo, **opts):
6528 """print the root (top) of the current working directory
6529 """print the root (top) of the current working directory
6529
6530
6530 Print the root directory of the current repository.
6531 Print the root directory of the current repository.
6531
6532
6532 .. container:: verbose
6533 .. container:: verbose
6533
6534
6534 Template:
6535 Template:
6535
6536
6536 The following keywords are supported in addition to the common template
6537 The following keywords are supported in addition to the common template
6537 keywords and functions. See also :hg:`help templates`.
6538 keywords and functions. See also :hg:`help templates`.
6538
6539
6539 :hgpath: String. Path to the .hg directory.
6540 :hgpath: String. Path to the .hg directory.
6540 :storepath: String. Path to the directory holding versioned data.
6541 :storepath: String. Path to the directory holding versioned data.
6541
6542
6542 Returns 0 on success.
6543 Returns 0 on success.
6543 """
6544 """
6544 opts = pycompat.byteskwargs(opts)
6545 opts = pycompat.byteskwargs(opts)
6545 with ui.formatter(b'root', opts) as fm:
6546 with ui.formatter(b'root', opts) as fm:
6546 fm.startitem()
6547 fm.startitem()
6547 fm.write(b'reporoot', b'%s\n', repo.root)
6548 fm.write(b'reporoot', b'%s\n', repo.root)
6548 fm.data(hgpath=repo.path, storepath=repo.spath)
6549 fm.data(hgpath=repo.path, storepath=repo.spath)
6549
6550
6550
6551
6551 @command(
6552 @command(
6552 b'serve',
6553 b'serve',
6553 [
6554 [
6554 (
6555 (
6555 b'A',
6556 b'A',
6556 b'accesslog',
6557 b'accesslog',
6557 b'',
6558 b'',
6558 _(b'name of access log file to write to'),
6559 _(b'name of access log file to write to'),
6559 _(b'FILE'),
6560 _(b'FILE'),
6560 ),
6561 ),
6561 (b'd', b'daemon', None, _(b'run server in background')),
6562 (b'd', b'daemon', None, _(b'run server in background')),
6562 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6563 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6563 (
6564 (
6564 b'E',
6565 b'E',
6565 b'errorlog',
6566 b'errorlog',
6566 b'',
6567 b'',
6567 _(b'name of error log file to write to'),
6568 _(b'name of error log file to write to'),
6568 _(b'FILE'),
6569 _(b'FILE'),
6569 ),
6570 ),
6570 # use string type, then we can check if something was passed
6571 # use string type, then we can check if something was passed
6571 (
6572 (
6572 b'p',
6573 b'p',
6573 b'port',
6574 b'port',
6574 b'',
6575 b'',
6575 _(b'port to listen on (default: 8000)'),
6576 _(b'port to listen on (default: 8000)'),
6576 _(b'PORT'),
6577 _(b'PORT'),
6577 ),
6578 ),
6578 (
6579 (
6579 b'a',
6580 b'a',
6580 b'address',
6581 b'address',
6581 b'',
6582 b'',
6582 _(b'address to listen on (default: all interfaces)'),
6583 _(b'address to listen on (default: all interfaces)'),
6583 _(b'ADDR'),
6584 _(b'ADDR'),
6584 ),
6585 ),
6585 (
6586 (
6586 b'',
6587 b'',
6587 b'prefix',
6588 b'prefix',
6588 b'',
6589 b'',
6589 _(b'prefix path to serve from (default: server root)'),
6590 _(b'prefix path to serve from (default: server root)'),
6590 _(b'PREFIX'),
6591 _(b'PREFIX'),
6591 ),
6592 ),
6592 (
6593 (
6593 b'n',
6594 b'n',
6594 b'name',
6595 b'name',
6595 b'',
6596 b'',
6596 _(b'name to show in web pages (default: working directory)'),
6597 _(b'name to show in web pages (default: working directory)'),
6597 _(b'NAME'),
6598 _(b'NAME'),
6598 ),
6599 ),
6599 (
6600 (
6600 b'',
6601 b'',
6601 b'web-conf',
6602 b'web-conf',
6602 b'',
6603 b'',
6603 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6604 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6604 _(b'FILE'),
6605 _(b'FILE'),
6605 ),
6606 ),
6606 (
6607 (
6607 b'',
6608 b'',
6608 b'webdir-conf',
6609 b'webdir-conf',
6609 b'',
6610 b'',
6610 _(b'name of the hgweb config file (DEPRECATED)'),
6611 _(b'name of the hgweb config file (DEPRECATED)'),
6611 _(b'FILE'),
6612 _(b'FILE'),
6612 ),
6613 ),
6613 (
6614 (
6614 b'',
6615 b'',
6615 b'pid-file',
6616 b'pid-file',
6616 b'',
6617 b'',
6617 _(b'name of file to write process ID to'),
6618 _(b'name of file to write process ID to'),
6618 _(b'FILE'),
6619 _(b'FILE'),
6619 ),
6620 ),
6620 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6621 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6621 (
6622 (
6622 b'',
6623 b'',
6623 b'cmdserver',
6624 b'cmdserver',
6624 b'',
6625 b'',
6625 _(b'for remote clients (ADVANCED)'),
6626 _(b'for remote clients (ADVANCED)'),
6626 _(b'MODE'),
6627 _(b'MODE'),
6627 ),
6628 ),
6628 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6629 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6629 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6630 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6630 (b'6', b'ipv6', None, _(b'use IPv6 instead of IPv4')),
6631 (b'6', b'ipv6', None, _(b'use IPv6 instead of IPv4')),
6631 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6632 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6632 (b'', b'print-url', None, _(b'start and print only the URL')),
6633 (b'', b'print-url', None, _(b'start and print only the URL')),
6633 ]
6634 ]
6634 + subrepoopts,
6635 + subrepoopts,
6635 _(b'[OPTION]...'),
6636 _(b'[OPTION]...'),
6636 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6637 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6637 helpbasic=True,
6638 helpbasic=True,
6638 optionalrepo=True,
6639 optionalrepo=True,
6639 )
6640 )
6640 def serve(ui, repo, **opts):
6641 def serve(ui, repo, **opts):
6641 """start stand-alone webserver
6642 """start stand-alone webserver
6642
6643
6643 Start a local HTTP repository browser and pull server. You can use
6644 Start a local HTTP repository browser and pull server. You can use
6644 this for ad-hoc sharing and browsing of repositories. It is
6645 this for ad-hoc sharing and browsing of repositories. It is
6645 recommended to use a real web server to serve a repository for
6646 recommended to use a real web server to serve a repository for
6646 longer periods of time.
6647 longer periods of time.
6647
6648
6648 Please note that the server does not implement access control.
6649 Please note that the server does not implement access control.
6649 This means that, by default, anybody can read from the server and
6650 This means that, by default, anybody can read from the server and
6650 nobody can write to it by default. Set the ``web.allow-push``
6651 nobody can write to it by default. Set the ``web.allow-push``
6651 option to ``*`` to allow everybody to push to the server. You
6652 option to ``*`` to allow everybody to push to the server. You
6652 should use a real web server if you need to authenticate users.
6653 should use a real web server if you need to authenticate users.
6653
6654
6654 By default, the server logs accesses to stdout and errors to
6655 By default, the server logs accesses to stdout and errors to
6655 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6656 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6656 files.
6657 files.
6657
6658
6658 To have the server choose a free port number to listen on, specify
6659 To have the server choose a free port number to listen on, specify
6659 a port number of 0; in this case, the server will print the port
6660 a port number of 0; in this case, the server will print the port
6660 number it uses.
6661 number it uses.
6661
6662
6662 Returns 0 on success.
6663 Returns 0 on success.
6663 """
6664 """
6664
6665
6665 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6666 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6666 opts = pycompat.byteskwargs(opts)
6667 opts = pycompat.byteskwargs(opts)
6667 if opts[b"print_url"] and ui.verbose:
6668 if opts[b"print_url"] and ui.verbose:
6668 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6669 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6669
6670
6670 if opts[b"stdio"]:
6671 if opts[b"stdio"]:
6671 if repo is None:
6672 if repo is None:
6672 raise error.RepoError(
6673 raise error.RepoError(
6673 _(b"there is no Mercurial repository here (.hg not found)")
6674 _(b"there is no Mercurial repository here (.hg not found)")
6674 )
6675 )
6675 s = wireprotoserver.sshserver(ui, repo)
6676 accesshidden = False
6677 if repo.filtername is None:
6678 allow = ui.configlist(
6679 b'experimental', b'server.allow-hidden-access'
6680 )
6681 user = procutil.getuser()
6682 if allow and scmutil.ismember(ui, user, allow):
6683 accesshidden = True
6684 else:
6685 msg = (
6686 _(
6687 b'ignoring request to access hidden changeset by '
6688 b'unauthorized user: %s\n'
6689 )
6690 % user
6691 )
6692 ui.warn(msg)
6693
6694 s = wireprotoserver.sshserver(ui, repo, accesshidden=accesshidden)
6676 s.serve_forever()
6695 s.serve_forever()
6677 return
6696 return
6678
6697
6679 service = server.createservice(ui, repo, opts)
6698 service = server.createservice(ui, repo, opts)
6680 return server.runservice(opts, initfn=service.init, runfn=service.run)
6699 return server.runservice(opts, initfn=service.init, runfn=service.run)
6681
6700
6682
6701
6683 @command(
6702 @command(
6684 b'shelve',
6703 b'shelve',
6685 [
6704 [
6686 (
6705 (
6687 b'A',
6706 b'A',
6688 b'addremove',
6707 b'addremove',
6689 None,
6708 None,
6690 _(b'mark new/missing files as added/removed before shelving'),
6709 _(b'mark new/missing files as added/removed before shelving'),
6691 ),
6710 ),
6692 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6711 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6693 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6712 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6694 (
6713 (
6695 b'',
6714 b'',
6696 b'date',
6715 b'date',
6697 b'',
6716 b'',
6698 _(b'shelve with the specified commit date'),
6717 _(b'shelve with the specified commit date'),
6699 _(b'DATE'),
6718 _(b'DATE'),
6700 ),
6719 ),
6701 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6720 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6702 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6721 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6703 (
6722 (
6704 b'k',
6723 b'k',
6705 b'keep',
6724 b'keep',
6706 False,
6725 False,
6707 _(b'shelve, but keep changes in the working directory'),
6726 _(b'shelve, but keep changes in the working directory'),
6708 ),
6727 ),
6709 (b'l', b'list', None, _(b'list current shelves')),
6728 (b'l', b'list', None, _(b'list current shelves')),
6710 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6729 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6711 (
6730 (
6712 b'n',
6731 b'n',
6713 b'name',
6732 b'name',
6714 b'',
6733 b'',
6715 _(b'use the given name for the shelved commit'),
6734 _(b'use the given name for the shelved commit'),
6716 _(b'NAME'),
6735 _(b'NAME'),
6717 ),
6736 ),
6718 (
6737 (
6719 b'p',
6738 b'p',
6720 b'patch',
6739 b'patch',
6721 None,
6740 None,
6722 _(
6741 _(
6723 b'output patches for changes (provide the names of the shelved '
6742 b'output patches for changes (provide the names of the shelved '
6724 b'changes as positional arguments)'
6743 b'changes as positional arguments)'
6725 ),
6744 ),
6726 ),
6745 ),
6727 (b'i', b'interactive', None, _(b'interactive mode')),
6746 (b'i', b'interactive', None, _(b'interactive mode')),
6728 (
6747 (
6729 b'',
6748 b'',
6730 b'stat',
6749 b'stat',
6731 None,
6750 None,
6732 _(
6751 _(
6733 b'output diffstat-style summary of changes (provide the names of '
6752 b'output diffstat-style summary of changes (provide the names of '
6734 b'the shelved changes as positional arguments)'
6753 b'the shelved changes as positional arguments)'
6735 ),
6754 ),
6736 ),
6755 ),
6737 ]
6756 ]
6738 + cmdutil.walkopts,
6757 + cmdutil.walkopts,
6739 _(b'hg shelve [OPTION]... [FILE]...'),
6758 _(b'hg shelve [OPTION]... [FILE]...'),
6740 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6759 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6741 )
6760 )
6742 def shelve(ui, repo, *pats, **opts):
6761 def shelve(ui, repo, *pats, **opts):
6743 """save and set aside changes from the working directory
6762 """save and set aside changes from the working directory
6744
6763
6745 Shelving takes files that "hg status" reports as not clean, saves
6764 Shelving takes files that "hg status" reports as not clean, saves
6746 the modifications to a bundle (a shelved change), and reverts the
6765 the modifications to a bundle (a shelved change), and reverts the
6747 files so that their state in the working directory becomes clean.
6766 files so that their state in the working directory becomes clean.
6748
6767
6749 To restore these changes to the working directory, using "hg
6768 To restore these changes to the working directory, using "hg
6750 unshelve"; this will work even if you switch to a different
6769 unshelve"; this will work even if you switch to a different
6751 commit.
6770 commit.
6752
6771
6753 When no files are specified, "hg shelve" saves all not-clean
6772 When no files are specified, "hg shelve" saves all not-clean
6754 files. If specific files or directories are named, only changes to
6773 files. If specific files or directories are named, only changes to
6755 those files are shelved.
6774 those files are shelved.
6756
6775
6757 In bare shelve (when no files are specified, without interactive,
6776 In bare shelve (when no files are specified, without interactive,
6758 include and exclude option), shelving remembers information if the
6777 include and exclude option), shelving remembers information if the
6759 working directory was on newly created branch, in other words working
6778 working directory was on newly created branch, in other words working
6760 directory was on different branch than its first parent. In this
6779 directory was on different branch than its first parent. In this
6761 situation unshelving restores branch information to the working directory.
6780 situation unshelving restores branch information to the working directory.
6762
6781
6763 Each shelved change has a name that makes it easier to find later.
6782 Each shelved change has a name that makes it easier to find later.
6764 The name of a shelved change defaults to being based on the active
6783 The name of a shelved change defaults to being based on the active
6765 bookmark, or if there is no active bookmark, the current named
6784 bookmark, or if there is no active bookmark, the current named
6766 branch. To specify a different name, use ``--name``.
6785 branch. To specify a different name, use ``--name``.
6767
6786
6768 To see a list of existing shelved changes, use the ``--list``
6787 To see a list of existing shelved changes, use the ``--list``
6769 option. For each shelved change, this will print its name, age,
6788 option. For each shelved change, this will print its name, age,
6770 and description; use ``--patch`` or ``--stat`` for more details.
6789 and description; use ``--patch`` or ``--stat`` for more details.
6771
6790
6772 To delete specific shelved changes, use ``--delete``. To delete
6791 To delete specific shelved changes, use ``--delete``. To delete
6773 all shelved changes, use ``--cleanup``.
6792 all shelved changes, use ``--cleanup``.
6774 """
6793 """
6775 opts = pycompat.byteskwargs(opts)
6794 opts = pycompat.byteskwargs(opts)
6776 allowables = [
6795 allowables = [
6777 (b'addremove', {b'create'}), # 'create' is pseudo action
6796 (b'addremove', {b'create'}), # 'create' is pseudo action
6778 (b'unknown', {b'create'}),
6797 (b'unknown', {b'create'}),
6779 (b'cleanup', {b'cleanup'}),
6798 (b'cleanup', {b'cleanup'}),
6780 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6799 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6781 (b'delete', {b'delete'}),
6800 (b'delete', {b'delete'}),
6782 (b'edit', {b'create'}),
6801 (b'edit', {b'create'}),
6783 (b'keep', {b'create'}),
6802 (b'keep', {b'create'}),
6784 (b'list', {b'list'}),
6803 (b'list', {b'list'}),
6785 (b'message', {b'create'}),
6804 (b'message', {b'create'}),
6786 (b'name', {b'create'}),
6805 (b'name', {b'create'}),
6787 (b'patch', {b'patch', b'list'}),
6806 (b'patch', {b'patch', b'list'}),
6788 (b'stat', {b'stat', b'list'}),
6807 (b'stat', {b'stat', b'list'}),
6789 ]
6808 ]
6790
6809
6791 def checkopt(opt):
6810 def checkopt(opt):
6792 if opts.get(opt):
6811 if opts.get(opt):
6793 for i, allowable in allowables:
6812 for i, allowable in allowables:
6794 if opts[i] and opt not in allowable:
6813 if opts[i] and opt not in allowable:
6795 raise error.InputError(
6814 raise error.InputError(
6796 _(
6815 _(
6797 b"options '--%s' and '--%s' may not be "
6816 b"options '--%s' and '--%s' may not be "
6798 b"used together"
6817 b"used together"
6799 )
6818 )
6800 % (opt, i)
6819 % (opt, i)
6801 )
6820 )
6802 return True
6821 return True
6803
6822
6804 if checkopt(b'cleanup'):
6823 if checkopt(b'cleanup'):
6805 if pats:
6824 if pats:
6806 raise error.InputError(
6825 raise error.InputError(
6807 _(b"cannot specify names when using '--cleanup'")
6826 _(b"cannot specify names when using '--cleanup'")
6808 )
6827 )
6809 return shelvemod.cleanupcmd(ui, repo)
6828 return shelvemod.cleanupcmd(ui, repo)
6810 elif checkopt(b'delete'):
6829 elif checkopt(b'delete'):
6811 return shelvemod.deletecmd(ui, repo, pats)
6830 return shelvemod.deletecmd(ui, repo, pats)
6812 elif checkopt(b'list'):
6831 elif checkopt(b'list'):
6813 return shelvemod.listcmd(ui, repo, pats, opts)
6832 return shelvemod.listcmd(ui, repo, pats, opts)
6814 elif checkopt(b'patch') or checkopt(b'stat'):
6833 elif checkopt(b'patch') or checkopt(b'stat'):
6815 return shelvemod.patchcmds(ui, repo, pats, opts)
6834 return shelvemod.patchcmds(ui, repo, pats, opts)
6816 else:
6835 else:
6817 return shelvemod.createcmd(ui, repo, pats, opts)
6836 return shelvemod.createcmd(ui, repo, pats, opts)
6818
6837
6819
6838
6820 _NOTTERSE = b'nothing'
6839 _NOTTERSE = b'nothing'
6821
6840
6822
6841
6823 @command(
6842 @command(
6824 b'status|st',
6843 b'status|st',
6825 [
6844 [
6826 (b'A', b'all', None, _(b'show status of all files')),
6845 (b'A', b'all', None, _(b'show status of all files')),
6827 (b'm', b'modified', None, _(b'show only modified files')),
6846 (b'm', b'modified', None, _(b'show only modified files')),
6828 (b'a', b'added', None, _(b'show only added files')),
6847 (b'a', b'added', None, _(b'show only added files')),
6829 (b'r', b'removed', None, _(b'show only removed files')),
6848 (b'r', b'removed', None, _(b'show only removed files')),
6830 (b'd', b'deleted', None, _(b'show only missing files')),
6849 (b'd', b'deleted', None, _(b'show only missing files')),
6831 (b'c', b'clean', None, _(b'show only files without changes')),
6850 (b'c', b'clean', None, _(b'show only files without changes')),
6832 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6851 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6833 (b'i', b'ignored', None, _(b'show only ignored files')),
6852 (b'i', b'ignored', None, _(b'show only ignored files')),
6834 (b'n', b'no-status', None, _(b'hide status prefix')),
6853 (b'n', b'no-status', None, _(b'hide status prefix')),
6835 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6854 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6836 (
6855 (
6837 b'C',
6856 b'C',
6838 b'copies',
6857 b'copies',
6839 None,
6858 None,
6840 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6859 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6841 ),
6860 ),
6842 (
6861 (
6843 b'0',
6862 b'0',
6844 b'print0',
6863 b'print0',
6845 None,
6864 None,
6846 _(b'end filenames with NUL, for use with xargs'),
6865 _(b'end filenames with NUL, for use with xargs'),
6847 ),
6866 ),
6848 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6867 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6849 (
6868 (
6850 b'',
6869 b'',
6851 b'change',
6870 b'change',
6852 b'',
6871 b'',
6853 _(b'list the changed files of a revision'),
6872 _(b'list the changed files of a revision'),
6854 _(b'REV'),
6873 _(b'REV'),
6855 ),
6874 ),
6856 ]
6875 ]
6857 + walkopts
6876 + walkopts
6858 + subrepoopts
6877 + subrepoopts
6859 + formatteropts,
6878 + formatteropts,
6860 _(b'[OPTION]... [FILE]...'),
6879 _(b'[OPTION]... [FILE]...'),
6861 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6880 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6862 helpbasic=True,
6881 helpbasic=True,
6863 inferrepo=True,
6882 inferrepo=True,
6864 intents={INTENT_READONLY},
6883 intents={INTENT_READONLY},
6865 )
6884 )
6866 def status(ui, repo, *pats, **opts):
6885 def status(ui, repo, *pats, **opts):
6867 """show changed files in the working directory
6886 """show changed files in the working directory
6868
6887
6869 Show status of files in the repository. If names are given, only
6888 Show status of files in the repository. If names are given, only
6870 files that match are shown. Files that are clean or ignored or
6889 files that match are shown. Files that are clean or ignored or
6871 the source of a copy/move operation, are not listed unless
6890 the source of a copy/move operation, are not listed unless
6872 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6891 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6873 Unless options described with "show only ..." are given, the
6892 Unless options described with "show only ..." are given, the
6874 options -mardu are used.
6893 options -mardu are used.
6875
6894
6876 Option -q/--quiet hides untracked (unknown and ignored) files
6895 Option -q/--quiet hides untracked (unknown and ignored) files
6877 unless explicitly requested with -u/--unknown or -i/--ignored.
6896 unless explicitly requested with -u/--unknown or -i/--ignored.
6878
6897
6879 .. note::
6898 .. note::
6880
6899
6881 :hg:`status` may appear to disagree with diff if permissions have
6900 :hg:`status` may appear to disagree with diff if permissions have
6882 changed or a merge has occurred. The standard diff format does
6901 changed or a merge has occurred. The standard diff format does
6883 not report permission changes and diff only reports changes
6902 not report permission changes and diff only reports changes
6884 relative to one merge parent.
6903 relative to one merge parent.
6885
6904
6886 If one revision is given, it is used as the base revision.
6905 If one revision is given, it is used as the base revision.
6887 If two revisions are given, the differences between them are
6906 If two revisions are given, the differences between them are
6888 shown. The --change option can also be used as a shortcut to list
6907 shown. The --change option can also be used as a shortcut to list
6889 the changed files of a revision from its first parent.
6908 the changed files of a revision from its first parent.
6890
6909
6891 The codes used to show the status of files are::
6910 The codes used to show the status of files are::
6892
6911
6893 M = modified
6912 M = modified
6894 A = added
6913 A = added
6895 R = removed
6914 R = removed
6896 C = clean
6915 C = clean
6897 ! = missing (deleted by non-hg command, but still tracked)
6916 ! = missing (deleted by non-hg command, but still tracked)
6898 ? = not tracked
6917 ? = not tracked
6899 I = ignored
6918 I = ignored
6900 = origin of the previous file (with --copies)
6919 = origin of the previous file (with --copies)
6901
6920
6902 .. container:: verbose
6921 .. container:: verbose
6903
6922
6904 The -t/--terse option abbreviates the output by showing only the directory
6923 The -t/--terse option abbreviates the output by showing only the directory
6905 name if all the files in it share the same status. The option takes an
6924 name if all the files in it share the same status. The option takes an
6906 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6925 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6907 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6926 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6908 for 'ignored' and 'c' for clean.
6927 for 'ignored' and 'c' for clean.
6909
6928
6910 It abbreviates only those statuses which are passed. Note that clean and
6929 It abbreviates only those statuses which are passed. Note that clean and
6911 ignored files are not displayed with '--terse ic' unless the -c/--clean
6930 ignored files are not displayed with '--terse ic' unless the -c/--clean
6912 and -i/--ignored options are also used.
6931 and -i/--ignored options are also used.
6913
6932
6914 The -v/--verbose option shows information when the repository is in an
6933 The -v/--verbose option shows information when the repository is in an
6915 unfinished merge, shelve, rebase state etc. You can have this behavior
6934 unfinished merge, shelve, rebase state etc. You can have this behavior
6916 turned on by default by enabling the ``commands.status.verbose`` option.
6935 turned on by default by enabling the ``commands.status.verbose`` option.
6917
6936
6918 You can skip displaying some of these states by setting
6937 You can skip displaying some of these states by setting
6919 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6938 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6920 'histedit', 'merge', 'rebase', or 'unshelve'.
6939 'histedit', 'merge', 'rebase', or 'unshelve'.
6921
6940
6922 Template:
6941 Template:
6923
6942
6924 The following keywords are supported in addition to the common template
6943 The following keywords are supported in addition to the common template
6925 keywords and functions. See also :hg:`help templates`.
6944 keywords and functions. See also :hg:`help templates`.
6926
6945
6927 :path: String. Repository-absolute path of the file.
6946 :path: String. Repository-absolute path of the file.
6928 :source: String. Repository-absolute path of the file originated from.
6947 :source: String. Repository-absolute path of the file originated from.
6929 Available if ``--copies`` is specified.
6948 Available if ``--copies`` is specified.
6930 :status: String. Character denoting file's status.
6949 :status: String. Character denoting file's status.
6931
6950
6932 Examples:
6951 Examples:
6933
6952
6934 - show changes in the working directory relative to a
6953 - show changes in the working directory relative to a
6935 changeset::
6954 changeset::
6936
6955
6937 hg status --rev 9353
6956 hg status --rev 9353
6938
6957
6939 - show changes in the working directory relative to the
6958 - show changes in the working directory relative to the
6940 current directory (see :hg:`help patterns` for more information)::
6959 current directory (see :hg:`help patterns` for more information)::
6941
6960
6942 hg status re:
6961 hg status re:
6943
6962
6944 - show all changes including copies in an existing changeset::
6963 - show all changes including copies in an existing changeset::
6945
6964
6946 hg status --copies --change 9353
6965 hg status --copies --change 9353
6947
6966
6948 - get a NUL separated list of added files, suitable for xargs::
6967 - get a NUL separated list of added files, suitable for xargs::
6949
6968
6950 hg status -an0
6969 hg status -an0
6951
6970
6952 - show more information about the repository status, abbreviating
6971 - show more information about the repository status, abbreviating
6953 added, removed, modified, deleted, and untracked paths::
6972 added, removed, modified, deleted, and untracked paths::
6954
6973
6955 hg status -v -t mardu
6974 hg status -v -t mardu
6956
6975
6957 Returns 0 on success.
6976 Returns 0 on success.
6958
6977
6959 """
6978 """
6960
6979
6961 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6980 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6962 opts = pycompat.byteskwargs(opts)
6981 opts = pycompat.byteskwargs(opts)
6963 revs = opts.get(b'rev', [])
6982 revs = opts.get(b'rev', [])
6964 change = opts.get(b'change', b'')
6983 change = opts.get(b'change', b'')
6965 terse = opts.get(b'terse', _NOTTERSE)
6984 terse = opts.get(b'terse', _NOTTERSE)
6966 if terse is _NOTTERSE:
6985 if terse is _NOTTERSE:
6967 if revs:
6986 if revs:
6968 terse = b''
6987 terse = b''
6969 else:
6988 else:
6970 terse = ui.config(b'commands', b'status.terse')
6989 terse = ui.config(b'commands', b'status.terse')
6971
6990
6972 if revs and terse:
6991 if revs and terse:
6973 msg = _(b'cannot use --terse with --rev')
6992 msg = _(b'cannot use --terse with --rev')
6974 raise error.InputError(msg)
6993 raise error.InputError(msg)
6975 elif change:
6994 elif change:
6976 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6995 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6977 ctx2 = logcmdutil.revsingle(repo, change, None)
6996 ctx2 = logcmdutil.revsingle(repo, change, None)
6978 ctx1 = ctx2.p1()
6997 ctx1 = ctx2.p1()
6979 else:
6998 else:
6980 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6999 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6981 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
7000 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6982
7001
6983 forcerelativevalue = None
7002 forcerelativevalue = None
6984 if ui.hasconfig(b'commands', b'status.relative'):
7003 if ui.hasconfig(b'commands', b'status.relative'):
6985 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
7004 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6986 uipathfn = scmutil.getuipathfn(
7005 uipathfn = scmutil.getuipathfn(
6987 repo,
7006 repo,
6988 legacyrelativevalue=bool(pats),
7007 legacyrelativevalue=bool(pats),
6989 forcerelativevalue=forcerelativevalue,
7008 forcerelativevalue=forcerelativevalue,
6990 )
7009 )
6991
7010
6992 if opts.get(b'print0'):
7011 if opts.get(b'print0'):
6993 end = b'\0'
7012 end = b'\0'
6994 else:
7013 else:
6995 end = b'\n'
7014 end = b'\n'
6996 states = b'modified added removed deleted unknown ignored clean'.split()
7015 states = b'modified added removed deleted unknown ignored clean'.split()
6997 show = [k for k in states if opts.get(k)]
7016 show = [k for k in states if opts.get(k)]
6998 if opts.get(b'all'):
7017 if opts.get(b'all'):
6999 show += ui.quiet and (states[:4] + [b'clean']) or states
7018 show += ui.quiet and (states[:4] + [b'clean']) or states
7000
7019
7001 if not show:
7020 if not show:
7002 if ui.quiet:
7021 if ui.quiet:
7003 show = states[:4]
7022 show = states[:4]
7004 else:
7023 else:
7005 show = states[:5]
7024 show = states[:5]
7006
7025
7007 m = scmutil.match(ctx2, pats, opts)
7026 m = scmutil.match(ctx2, pats, opts)
7008 if terse:
7027 if terse:
7009 # we need to compute clean and unknown to terse
7028 # we need to compute clean and unknown to terse
7010 stat = repo.status(
7029 stat = repo.status(
7011 ctx1.node(),
7030 ctx1.node(),
7012 ctx2.node(),
7031 ctx2.node(),
7013 m,
7032 m,
7014 b'ignored' in show or b'i' in terse,
7033 b'ignored' in show or b'i' in terse,
7015 clean=True,
7034 clean=True,
7016 unknown=True,
7035 unknown=True,
7017 listsubrepos=opts.get(b'subrepos'),
7036 listsubrepos=opts.get(b'subrepos'),
7018 )
7037 )
7019
7038
7020 stat = cmdutil.tersedir(stat, terse)
7039 stat = cmdutil.tersedir(stat, terse)
7021 else:
7040 else:
7022 stat = repo.status(
7041 stat = repo.status(
7023 ctx1.node(),
7042 ctx1.node(),
7024 ctx2.node(),
7043 ctx2.node(),
7025 m,
7044 m,
7026 b'ignored' in show,
7045 b'ignored' in show,
7027 b'clean' in show,
7046 b'clean' in show,
7028 b'unknown' in show,
7047 b'unknown' in show,
7029 opts.get(b'subrepos'),
7048 opts.get(b'subrepos'),
7030 )
7049 )
7031
7050
7032 changestates = zip(
7051 changestates = zip(
7033 states,
7052 states,
7034 pycompat.iterbytestr(b'MAR!?IC'),
7053 pycompat.iterbytestr(b'MAR!?IC'),
7035 [getattr(stat, s.decode('utf8')) for s in states],
7054 [getattr(stat, s.decode('utf8')) for s in states],
7036 )
7055 )
7037
7056
7038 copy = {}
7057 copy = {}
7039 show_copies = ui.configbool(b'ui', b'statuscopies')
7058 show_copies = ui.configbool(b'ui', b'statuscopies')
7040 if opts.get(b'copies') is not None:
7059 if opts.get(b'copies') is not None:
7041 show_copies = opts.get(b'copies')
7060 show_copies = opts.get(b'copies')
7042 show_copies = (show_copies or opts.get(b'all')) and not opts.get(
7061 show_copies = (show_copies or opts.get(b'all')) and not opts.get(
7043 b'no_status'
7062 b'no_status'
7044 )
7063 )
7045 if show_copies:
7064 if show_copies:
7046 copy = copies.pathcopies(ctx1, ctx2, m)
7065 copy = copies.pathcopies(ctx1, ctx2, m)
7047
7066
7048 morestatus = None
7067 morestatus = None
7049 if (
7068 if (
7050 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
7069 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
7051 and not ui.plain()
7070 and not ui.plain()
7052 and not opts.get(b'print0')
7071 and not opts.get(b'print0')
7053 ):
7072 ):
7054 morestatus = cmdutil.readmorestatus(repo)
7073 morestatus = cmdutil.readmorestatus(repo)
7055
7074
7056 ui.pager(b'status')
7075 ui.pager(b'status')
7057 fm = ui.formatter(b'status', opts)
7076 fm = ui.formatter(b'status', opts)
7058 fmt = b'%s' + end
7077 fmt = b'%s' + end
7059 showchar = not opts.get(b'no_status')
7078 showchar = not opts.get(b'no_status')
7060
7079
7061 for state, char, files in changestates:
7080 for state, char, files in changestates:
7062 if state in show:
7081 if state in show:
7063 label = b'status.' + state
7082 label = b'status.' + state
7064 for f in files:
7083 for f in files:
7065 fm.startitem()
7084 fm.startitem()
7066 fm.context(ctx=ctx2)
7085 fm.context(ctx=ctx2)
7067 fm.data(itemtype=b'file', path=f)
7086 fm.data(itemtype=b'file', path=f)
7068 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7087 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7069 fm.plain(fmt % uipathfn(f), label=label)
7088 fm.plain(fmt % uipathfn(f), label=label)
7070 if f in copy:
7089 if f in copy:
7071 fm.data(source=copy[f])
7090 fm.data(source=copy[f])
7072 fm.plain(
7091 fm.plain(
7073 (b' %s' + end) % uipathfn(copy[f]),
7092 (b' %s' + end) % uipathfn(copy[f]),
7074 label=b'status.copied',
7093 label=b'status.copied',
7075 )
7094 )
7076 if morestatus:
7095 if morestatus:
7077 morestatus.formatfile(f, fm)
7096 morestatus.formatfile(f, fm)
7078
7097
7079 if morestatus:
7098 if morestatus:
7080 morestatus.formatfooter(fm)
7099 morestatus.formatfooter(fm)
7081 fm.end()
7100 fm.end()
7082
7101
7083
7102
7084 @command(
7103 @command(
7085 b'summary|sum',
7104 b'summary|sum',
7086 [(b'', b'remote', None, _(b'check for push and pull'))],
7105 [(b'', b'remote', None, _(b'check for push and pull'))],
7087 b'[--remote]',
7106 b'[--remote]',
7088 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7107 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7089 helpbasic=True,
7108 helpbasic=True,
7090 intents={INTENT_READONLY},
7109 intents={INTENT_READONLY},
7091 )
7110 )
7092 def summary(ui, repo, **opts):
7111 def summary(ui, repo, **opts):
7093 """summarize working directory state
7112 """summarize working directory state
7094
7113
7095 This generates a brief summary of the working directory state,
7114 This generates a brief summary of the working directory state,
7096 including parents, branch, commit status, phase and available updates.
7115 including parents, branch, commit status, phase and available updates.
7097
7116
7098 With the --remote option, this will check the default paths for
7117 With the --remote option, this will check the default paths for
7099 incoming and outgoing changes. This can be time-consuming.
7118 incoming and outgoing changes. This can be time-consuming.
7100
7119
7101 Returns 0 on success.
7120 Returns 0 on success.
7102 """
7121 """
7103
7122
7104 opts = pycompat.byteskwargs(opts)
7123 opts = pycompat.byteskwargs(opts)
7105 ui.pager(b'summary')
7124 ui.pager(b'summary')
7106 ctx = repo[None]
7125 ctx = repo[None]
7107 parents = ctx.parents()
7126 parents = ctx.parents()
7108 pnode = parents[0].node()
7127 pnode = parents[0].node()
7109 marks = []
7128 marks = []
7110
7129
7111 try:
7130 try:
7112 ms = mergestatemod.mergestate.read(repo)
7131 ms = mergestatemod.mergestate.read(repo)
7113 except error.UnsupportedMergeRecords as e:
7132 except error.UnsupportedMergeRecords as e:
7114 s = b' '.join(e.recordtypes)
7133 s = b' '.join(e.recordtypes)
7115 ui.warn(
7134 ui.warn(
7116 _(b'warning: merge state has unsupported record types: %s\n') % s
7135 _(b'warning: merge state has unsupported record types: %s\n') % s
7117 )
7136 )
7118 unresolved = []
7137 unresolved = []
7119 else:
7138 else:
7120 unresolved = list(ms.unresolved())
7139 unresolved = list(ms.unresolved())
7121
7140
7122 for p in parents:
7141 for p in parents:
7123 # label with log.changeset (instead of log.parent) since this
7142 # label with log.changeset (instead of log.parent) since this
7124 # shows a working directory parent *changeset*:
7143 # shows a working directory parent *changeset*:
7125 # i18n: column positioning for "hg summary"
7144 # i18n: column positioning for "hg summary"
7126 ui.write(
7145 ui.write(
7127 _(b'parent: %d:%s ') % (p.rev(), p),
7146 _(b'parent: %d:%s ') % (p.rev(), p),
7128 label=logcmdutil.changesetlabels(p),
7147 label=logcmdutil.changesetlabels(p),
7129 )
7148 )
7130 ui.write(b' '.join(p.tags()), label=b'log.tag')
7149 ui.write(b' '.join(p.tags()), label=b'log.tag')
7131 if p.bookmarks():
7150 if p.bookmarks():
7132 marks.extend(p.bookmarks())
7151 marks.extend(p.bookmarks())
7133 if p.rev() == -1:
7152 if p.rev() == -1:
7134 if not len(repo):
7153 if not len(repo):
7135 ui.write(_(b' (empty repository)'))
7154 ui.write(_(b' (empty repository)'))
7136 else:
7155 else:
7137 ui.write(_(b' (no revision checked out)'))
7156 ui.write(_(b' (no revision checked out)'))
7138 if p.obsolete():
7157 if p.obsolete():
7139 ui.write(_(b' (obsolete)'))
7158 ui.write(_(b' (obsolete)'))
7140 if p.isunstable():
7159 if p.isunstable():
7141 instabilities = (
7160 instabilities = (
7142 ui.label(instability, b'trouble.%s' % instability)
7161 ui.label(instability, b'trouble.%s' % instability)
7143 for instability in p.instabilities()
7162 for instability in p.instabilities()
7144 )
7163 )
7145 ui.write(b' (' + b', '.join(instabilities) + b')')
7164 ui.write(b' (' + b', '.join(instabilities) + b')')
7146 ui.write(b'\n')
7165 ui.write(b'\n')
7147 if p.description():
7166 if p.description():
7148 ui.status(
7167 ui.status(
7149 b' ' + p.description().splitlines()[0].strip() + b'\n',
7168 b' ' + p.description().splitlines()[0].strip() + b'\n',
7150 label=b'log.summary',
7169 label=b'log.summary',
7151 )
7170 )
7152
7171
7153 branch = ctx.branch()
7172 branch = ctx.branch()
7154 bheads = repo.branchheads(branch)
7173 bheads = repo.branchheads(branch)
7155 # i18n: column positioning for "hg summary"
7174 # i18n: column positioning for "hg summary"
7156 m = _(b'branch: %s\n') % branch
7175 m = _(b'branch: %s\n') % branch
7157 if branch != b'default':
7176 if branch != b'default':
7158 ui.write(m, label=b'log.branch')
7177 ui.write(m, label=b'log.branch')
7159 else:
7178 else:
7160 ui.status(m, label=b'log.branch')
7179 ui.status(m, label=b'log.branch')
7161
7180
7162 if marks:
7181 if marks:
7163 active = repo._activebookmark
7182 active = repo._activebookmark
7164 # i18n: column positioning for "hg summary"
7183 # i18n: column positioning for "hg summary"
7165 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7184 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7166 if active is not None:
7185 if active is not None:
7167 if active in marks:
7186 if active in marks:
7168 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7187 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7169 marks.remove(active)
7188 marks.remove(active)
7170 else:
7189 else:
7171 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7190 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7172 for m in marks:
7191 for m in marks:
7173 ui.write(b' ' + m, label=b'log.bookmark')
7192 ui.write(b' ' + m, label=b'log.bookmark')
7174 ui.write(b'\n', label=b'log.bookmark')
7193 ui.write(b'\n', label=b'log.bookmark')
7175
7194
7176 status = repo.status(unknown=True)
7195 status = repo.status(unknown=True)
7177
7196
7178 c = repo.dirstate.copies()
7197 c = repo.dirstate.copies()
7179 copied, renamed = [], []
7198 copied, renamed = [], []
7180 for d, s in c.items():
7199 for d, s in c.items():
7181 if s in status.removed:
7200 if s in status.removed:
7182 status.removed.remove(s)
7201 status.removed.remove(s)
7183 renamed.append(d)
7202 renamed.append(d)
7184 else:
7203 else:
7185 copied.append(d)
7204 copied.append(d)
7186 if d in status.added:
7205 if d in status.added:
7187 status.added.remove(d)
7206 status.added.remove(d)
7188
7207
7189 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7208 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7190
7209
7191 labels = [
7210 labels = [
7192 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7211 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7193 (ui.label(_(b'%d added'), b'status.added'), status.added),
7212 (ui.label(_(b'%d added'), b'status.added'), status.added),
7194 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7213 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7195 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7214 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7196 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7215 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7197 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7216 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7198 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7217 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7199 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7218 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7200 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7219 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7201 ]
7220 ]
7202 t = []
7221 t = []
7203 for l, s in labels:
7222 for l, s in labels:
7204 if s:
7223 if s:
7205 t.append(l % len(s))
7224 t.append(l % len(s))
7206
7225
7207 t = b', '.join(t)
7226 t = b', '.join(t)
7208 cleanworkdir = False
7227 cleanworkdir = False
7209
7228
7210 if repo.vfs.exists(b'graftstate'):
7229 if repo.vfs.exists(b'graftstate'):
7211 t += _(b' (graft in progress)')
7230 t += _(b' (graft in progress)')
7212 if repo.vfs.exists(b'updatestate'):
7231 if repo.vfs.exists(b'updatestate'):
7213 t += _(b' (interrupted update)')
7232 t += _(b' (interrupted update)')
7214 elif len(parents) > 1:
7233 elif len(parents) > 1:
7215 t += _(b' (merge)')
7234 t += _(b' (merge)')
7216 elif branch != parents[0].branch():
7235 elif branch != parents[0].branch():
7217 t += _(b' (new branch)')
7236 t += _(b' (new branch)')
7218 elif parents[0].closesbranch() and pnode in repo.branchheads(
7237 elif parents[0].closesbranch() and pnode in repo.branchheads(
7219 branch, closed=True
7238 branch, closed=True
7220 ):
7239 ):
7221 t += _(b' (head closed)')
7240 t += _(b' (head closed)')
7222 elif not (
7241 elif not (
7223 status.modified
7242 status.modified
7224 or status.added
7243 or status.added
7225 or status.removed
7244 or status.removed
7226 or renamed
7245 or renamed
7227 or copied
7246 or copied
7228 or subs
7247 or subs
7229 ):
7248 ):
7230 t += _(b' (clean)')
7249 t += _(b' (clean)')
7231 cleanworkdir = True
7250 cleanworkdir = True
7232 elif pnode not in bheads:
7251 elif pnode not in bheads:
7233 t += _(b' (new branch head)')
7252 t += _(b' (new branch head)')
7234
7253
7235 if parents:
7254 if parents:
7236 pendingphase = max(p.phase() for p in parents)
7255 pendingphase = max(p.phase() for p in parents)
7237 else:
7256 else:
7238 pendingphase = phases.public
7257 pendingphase = phases.public
7239
7258
7240 if pendingphase > phases.newcommitphase(ui):
7259 if pendingphase > phases.newcommitphase(ui):
7241 t += b' (%s)' % phases.phasenames[pendingphase]
7260 t += b' (%s)' % phases.phasenames[pendingphase]
7242
7261
7243 if cleanworkdir:
7262 if cleanworkdir:
7244 # i18n: column positioning for "hg summary"
7263 # i18n: column positioning for "hg summary"
7245 ui.status(_(b'commit: %s\n') % t.strip())
7264 ui.status(_(b'commit: %s\n') % t.strip())
7246 else:
7265 else:
7247 # i18n: column positioning for "hg summary"
7266 # i18n: column positioning for "hg summary"
7248 ui.write(_(b'commit: %s\n') % t.strip())
7267 ui.write(_(b'commit: %s\n') % t.strip())
7249
7268
7250 # all ancestors of branch heads - all ancestors of parent = new csets
7269 # all ancestors of branch heads - all ancestors of parent = new csets
7251 new = len(
7270 new = len(
7252 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7271 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7253 )
7272 )
7254
7273
7255 if new == 0:
7274 if new == 0:
7256 # i18n: column positioning for "hg summary"
7275 # i18n: column positioning for "hg summary"
7257 ui.status(_(b'update: (current)\n'))
7276 ui.status(_(b'update: (current)\n'))
7258 elif pnode not in bheads:
7277 elif pnode not in bheads:
7259 # i18n: column positioning for "hg summary"
7278 # i18n: column positioning for "hg summary"
7260 ui.write(_(b'update: %d new changesets (update)\n') % new)
7279 ui.write(_(b'update: %d new changesets (update)\n') % new)
7261 else:
7280 else:
7262 # i18n: column positioning for "hg summary"
7281 # i18n: column positioning for "hg summary"
7263 ui.write(
7282 ui.write(
7264 _(b'update: %d new changesets, %d branch heads (merge)\n')
7283 _(b'update: %d new changesets, %d branch heads (merge)\n')
7265 % (new, len(bheads))
7284 % (new, len(bheads))
7266 )
7285 )
7267
7286
7268 t = []
7287 t = []
7269 draft = len(repo.revs(b'draft()'))
7288 draft = len(repo.revs(b'draft()'))
7270 if draft:
7289 if draft:
7271 t.append(_(b'%d draft') % draft)
7290 t.append(_(b'%d draft') % draft)
7272 secret = len(repo.revs(b'secret()'))
7291 secret = len(repo.revs(b'secret()'))
7273 if secret:
7292 if secret:
7274 t.append(_(b'%d secret') % secret)
7293 t.append(_(b'%d secret') % secret)
7275
7294
7276 if draft or secret:
7295 if draft or secret:
7277 ui.status(_(b'phases: %s\n') % b', '.join(t))
7296 ui.status(_(b'phases: %s\n') % b', '.join(t))
7278
7297
7279 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7298 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7280 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7299 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7281 numtrouble = len(repo.revs(trouble + b"()"))
7300 numtrouble = len(repo.revs(trouble + b"()"))
7282 # We write all the possibilities to ease translation
7301 # We write all the possibilities to ease translation
7283 troublemsg = {
7302 troublemsg = {
7284 b"orphan": _(b"orphan: %d changesets"),
7303 b"orphan": _(b"orphan: %d changesets"),
7285 b"contentdivergent": _(b"content-divergent: %d changesets"),
7304 b"contentdivergent": _(b"content-divergent: %d changesets"),
7286 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7305 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7287 }
7306 }
7288 if numtrouble > 0:
7307 if numtrouble > 0:
7289 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7308 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7290
7309
7291 cmdutil.summaryhooks(ui, repo)
7310 cmdutil.summaryhooks(ui, repo)
7292
7311
7293 if opts.get(b'remote'):
7312 if opts.get(b'remote'):
7294 needsincoming, needsoutgoing = True, True
7313 needsincoming, needsoutgoing = True, True
7295 else:
7314 else:
7296 needsincoming, needsoutgoing = False, False
7315 needsincoming, needsoutgoing = False, False
7297 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7316 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7298 if i:
7317 if i:
7299 needsincoming = True
7318 needsincoming = True
7300 if o:
7319 if o:
7301 needsoutgoing = True
7320 needsoutgoing = True
7302 if not needsincoming and not needsoutgoing:
7321 if not needsincoming and not needsoutgoing:
7303 return
7322 return
7304
7323
7305 def getincoming():
7324 def getincoming():
7306 # XXX We should actually skip this if no default is specified, instead
7325 # XXX We should actually skip this if no default is specified, instead
7307 # of passing "default" which will resolve as "./default/" if no default
7326 # of passing "default" which will resolve as "./default/" if no default
7308 # path is defined.
7327 # path is defined.
7309 path = urlutil.get_unique_pull_path_obj(b'summary', ui, b'default')
7328 path = urlutil.get_unique_pull_path_obj(b'summary', ui, b'default')
7310 sbranch = path.branch
7329 sbranch = path.branch
7311 try:
7330 try:
7312 other = hg.peer(repo, {}, path)
7331 other = hg.peer(repo, {}, path)
7313 except error.RepoError:
7332 except error.RepoError:
7314 if opts.get(b'remote'):
7333 if opts.get(b'remote'):
7315 raise
7334 raise
7316 return path.loc, sbranch, None, None, None
7335 return path.loc, sbranch, None, None, None
7317 branches = (path.branch, [])
7336 branches = (path.branch, [])
7318 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7337 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7319 if revs:
7338 if revs:
7320 revs = [other.lookup(rev) for rev in revs]
7339 revs = [other.lookup(rev) for rev in revs]
7321 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(path.loc))
7340 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(path.loc))
7322 with repo.ui.silent():
7341 with repo.ui.silent():
7323 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7342 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7324 return path.loc, sbranch, other, commoninc, commoninc[1]
7343 return path.loc, sbranch, other, commoninc, commoninc[1]
7325
7344
7326 if needsincoming:
7345 if needsincoming:
7327 source, sbranch, sother, commoninc, incoming = getincoming()
7346 source, sbranch, sother, commoninc, incoming = getincoming()
7328 else:
7347 else:
7329 source = sbranch = sother = commoninc = incoming = None
7348 source = sbranch = sother = commoninc = incoming = None
7330
7349
7331 def getoutgoing():
7350 def getoutgoing():
7332 # XXX We should actually skip this if no default is specified, instead
7351 # XXX We should actually skip this if no default is specified, instead
7333 # of passing "default" which will resolve as "./default/" if no default
7352 # of passing "default" which will resolve as "./default/" if no default
7334 # path is defined.
7353 # path is defined.
7335 d = None
7354 d = None
7336 if b'default-push' in ui.paths:
7355 if b'default-push' in ui.paths:
7337 d = b'default-push'
7356 d = b'default-push'
7338 elif b'default' in ui.paths:
7357 elif b'default' in ui.paths:
7339 d = b'default'
7358 d = b'default'
7340 path = None
7359 path = None
7341 if d is not None:
7360 if d is not None:
7342 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7361 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7343 dest = path.loc
7362 dest = path.loc
7344 dbranch = path.branch
7363 dbranch = path.branch
7345 else:
7364 else:
7346 dest = b'default'
7365 dest = b'default'
7347 dbranch = None
7366 dbranch = None
7348 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7367 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7349 if source != dest:
7368 if source != dest:
7350 try:
7369 try:
7351 dother = hg.peer(repo, {}, path if path is not None else dest)
7370 dother = hg.peer(repo, {}, path if path is not None else dest)
7352 except error.RepoError:
7371 except error.RepoError:
7353 if opts.get(b'remote'):
7372 if opts.get(b'remote'):
7354 raise
7373 raise
7355 return dest, dbranch, None, None
7374 return dest, dbranch, None, None
7356 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7375 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7357 elif sother is None:
7376 elif sother is None:
7358 # there is no explicit destination peer, but source one is invalid
7377 # there is no explicit destination peer, but source one is invalid
7359 return dest, dbranch, None, None
7378 return dest, dbranch, None, None
7360 else:
7379 else:
7361 dother = sother
7380 dother = sother
7362 if source != dest or (sbranch is not None and sbranch != dbranch):
7381 if source != dest or (sbranch is not None and sbranch != dbranch):
7363 common = None
7382 common = None
7364 else:
7383 else:
7365 common = commoninc
7384 common = commoninc
7366 if revs:
7385 if revs:
7367 revs = [repo.lookup(rev) for rev in revs]
7386 revs = [repo.lookup(rev) for rev in revs]
7368 with repo.ui.silent():
7387 with repo.ui.silent():
7369 outgoing = discovery.findcommonoutgoing(
7388 outgoing = discovery.findcommonoutgoing(
7370 repo, dother, onlyheads=revs, commoninc=common
7389 repo, dother, onlyheads=revs, commoninc=common
7371 )
7390 )
7372 return dest, dbranch, dother, outgoing
7391 return dest, dbranch, dother, outgoing
7373
7392
7374 if needsoutgoing:
7393 if needsoutgoing:
7375 dest, dbranch, dother, outgoing = getoutgoing()
7394 dest, dbranch, dother, outgoing = getoutgoing()
7376 else:
7395 else:
7377 dest = dbranch = dother = outgoing = None
7396 dest = dbranch = dother = outgoing = None
7378
7397
7379 if opts.get(b'remote'):
7398 if opts.get(b'remote'):
7380 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7399 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7381 # The former always sets `sother` (or raises an exception if it can't);
7400 # The former always sets `sother` (or raises an exception if it can't);
7382 # the latter always sets `outgoing`.
7401 # the latter always sets `outgoing`.
7383 assert sother is not None
7402 assert sother is not None
7384 assert outgoing is not None
7403 assert outgoing is not None
7385
7404
7386 t = []
7405 t = []
7387 if incoming:
7406 if incoming:
7388 t.append(_(b'1 or more incoming'))
7407 t.append(_(b'1 or more incoming'))
7389 o = outgoing.missing
7408 o = outgoing.missing
7390 if o:
7409 if o:
7391 t.append(_(b'%d outgoing') % len(o))
7410 t.append(_(b'%d outgoing') % len(o))
7392 other = dother or sother
7411 other = dother or sother
7393 if b'bookmarks' in other.listkeys(b'namespaces'):
7412 if b'bookmarks' in other.listkeys(b'namespaces'):
7394 counts = bookmarks.summary(repo, other)
7413 counts = bookmarks.summary(repo, other)
7395 if counts[0] > 0:
7414 if counts[0] > 0:
7396 t.append(_(b'%d incoming bookmarks') % counts[0])
7415 t.append(_(b'%d incoming bookmarks') % counts[0])
7397 if counts[1] > 0:
7416 if counts[1] > 0:
7398 t.append(_(b'%d outgoing bookmarks') % counts[1])
7417 t.append(_(b'%d outgoing bookmarks') % counts[1])
7399
7418
7400 if t:
7419 if t:
7401 # i18n: column positioning for "hg summary"
7420 # i18n: column positioning for "hg summary"
7402 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7421 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7403 else:
7422 else:
7404 # i18n: column positioning for "hg summary"
7423 # i18n: column positioning for "hg summary"
7405 ui.status(_(b'remote: (synced)\n'))
7424 ui.status(_(b'remote: (synced)\n'))
7406
7425
7407 cmdutil.summaryremotehooks(
7426 cmdutil.summaryremotehooks(
7408 ui,
7427 ui,
7409 repo,
7428 repo,
7410 opts,
7429 opts,
7411 (
7430 (
7412 (source, sbranch, sother, commoninc),
7431 (source, sbranch, sother, commoninc),
7413 (dest, dbranch, dother, outgoing),
7432 (dest, dbranch, dother, outgoing),
7414 ),
7433 ),
7415 )
7434 )
7416
7435
7417
7436
7418 @command(
7437 @command(
7419 b'tag',
7438 b'tag',
7420 [
7439 [
7421 (b'f', b'force', None, _(b'force tag')),
7440 (b'f', b'force', None, _(b'force tag')),
7422 (b'l', b'local', None, _(b'make the tag local')),
7441 (b'l', b'local', None, _(b'make the tag local')),
7423 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7442 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7424 (b'', b'remove', None, _(b'remove a tag')),
7443 (b'', b'remove', None, _(b'remove a tag')),
7425 # -l/--local is already there, commitopts cannot be used
7444 # -l/--local is already there, commitopts cannot be used
7426 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7445 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7427 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7446 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7428 ]
7447 ]
7429 + commitopts2,
7448 + commitopts2,
7430 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7449 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7431 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7450 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7432 )
7451 )
7433 def tag(ui, repo, name1, *names, **opts):
7452 def tag(ui, repo, name1, *names, **opts):
7434 """add one or more tags for the current or given revision
7453 """add one or more tags for the current or given revision
7435
7454
7436 Name a particular revision using <name>.
7455 Name a particular revision using <name>.
7437
7456
7438 Tags are used to name particular revisions of the repository and are
7457 Tags are used to name particular revisions of the repository and are
7439 very useful to compare different revisions, to go back to significant
7458 very useful to compare different revisions, to go back to significant
7440 earlier versions or to mark branch points as releases, etc. Changing
7459 earlier versions or to mark branch points as releases, etc. Changing
7441 an existing tag is normally disallowed; use -f/--force to override.
7460 an existing tag is normally disallowed; use -f/--force to override.
7442
7461
7443 If no revision is given, the parent of the working directory is
7462 If no revision is given, the parent of the working directory is
7444 used.
7463 used.
7445
7464
7446 To facilitate version control, distribution, and merging of tags,
7465 To facilitate version control, distribution, and merging of tags,
7447 they are stored as a file named ".hgtags" which is managed similarly
7466 they are stored as a file named ".hgtags" which is managed similarly
7448 to other project files and can be hand-edited if necessary. This
7467 to other project files and can be hand-edited if necessary. This
7449 also means that tagging creates a new commit. The file
7468 also means that tagging creates a new commit. The file
7450 ".hg/localtags" is used for local tags (not shared among
7469 ".hg/localtags" is used for local tags (not shared among
7451 repositories).
7470 repositories).
7452
7471
7453 Tag commits are usually made at the head of a branch. If the parent
7472 Tag commits are usually made at the head of a branch. If the parent
7454 of the working directory is not a branch head, :hg:`tag` aborts; use
7473 of the working directory is not a branch head, :hg:`tag` aborts; use
7455 -f/--force to force the tag commit to be based on a non-head
7474 -f/--force to force the tag commit to be based on a non-head
7456 changeset.
7475 changeset.
7457
7476
7458 See :hg:`help dates` for a list of formats valid for -d/--date.
7477 See :hg:`help dates` for a list of formats valid for -d/--date.
7459
7478
7460 Since tag names have priority over branch names during revision
7479 Since tag names have priority over branch names during revision
7461 lookup, using an existing branch name as a tag name is discouraged.
7480 lookup, using an existing branch name as a tag name is discouraged.
7462
7481
7463 Returns 0 on success.
7482 Returns 0 on success.
7464 """
7483 """
7465 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7484 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7466 opts = pycompat.byteskwargs(opts)
7485 opts = pycompat.byteskwargs(opts)
7467 with repo.wlock(), repo.lock():
7486 with repo.wlock(), repo.lock():
7468 rev_ = b"."
7487 rev_ = b"."
7469 names = [t.strip() for t in (name1,) + names]
7488 names = [t.strip() for t in (name1,) + names]
7470 if len(names) != len(set(names)):
7489 if len(names) != len(set(names)):
7471 raise error.InputError(_(b'tag names must be unique'))
7490 raise error.InputError(_(b'tag names must be unique'))
7472 for n in names:
7491 for n in names:
7473 scmutil.checknewlabel(repo, n, b'tag')
7492 scmutil.checknewlabel(repo, n, b'tag')
7474 if not n:
7493 if not n:
7475 raise error.InputError(
7494 raise error.InputError(
7476 _(b'tag names cannot consist entirely of whitespace')
7495 _(b'tag names cannot consist entirely of whitespace')
7477 )
7496 )
7478 if opts.get(b'rev'):
7497 if opts.get(b'rev'):
7479 rev_ = opts[b'rev']
7498 rev_ = opts[b'rev']
7480 message = opts.get(b'message')
7499 message = opts.get(b'message')
7481 if opts.get(b'remove'):
7500 if opts.get(b'remove'):
7482 if opts.get(b'local'):
7501 if opts.get(b'local'):
7483 expectedtype = b'local'
7502 expectedtype = b'local'
7484 else:
7503 else:
7485 expectedtype = b'global'
7504 expectedtype = b'global'
7486
7505
7487 for n in names:
7506 for n in names:
7488 if repo.tagtype(n) == b'global':
7507 if repo.tagtype(n) == b'global':
7489 alltags = tagsmod.findglobaltags(ui, repo)
7508 alltags = tagsmod.findglobaltags(ui, repo)
7490 if alltags[n][0] == repo.nullid:
7509 if alltags[n][0] == repo.nullid:
7491 raise error.InputError(
7510 raise error.InputError(
7492 _(b"tag '%s' is already removed") % n
7511 _(b"tag '%s' is already removed") % n
7493 )
7512 )
7494 if not repo.tagtype(n):
7513 if not repo.tagtype(n):
7495 raise error.InputError(_(b"tag '%s' does not exist") % n)
7514 raise error.InputError(_(b"tag '%s' does not exist") % n)
7496 if repo.tagtype(n) != expectedtype:
7515 if repo.tagtype(n) != expectedtype:
7497 if expectedtype == b'global':
7516 if expectedtype == b'global':
7498 raise error.InputError(
7517 raise error.InputError(
7499 _(b"tag '%s' is not a global tag") % n
7518 _(b"tag '%s' is not a global tag") % n
7500 )
7519 )
7501 else:
7520 else:
7502 raise error.InputError(
7521 raise error.InputError(
7503 _(b"tag '%s' is not a local tag") % n
7522 _(b"tag '%s' is not a local tag") % n
7504 )
7523 )
7505 rev_ = b'null'
7524 rev_ = b'null'
7506 if not message:
7525 if not message:
7507 # we don't translate commit messages
7526 # we don't translate commit messages
7508 message = b'Removed tag %s' % b', '.join(names)
7527 message = b'Removed tag %s' % b', '.join(names)
7509 elif not opts.get(b'force'):
7528 elif not opts.get(b'force'):
7510 for n in names:
7529 for n in names:
7511 if n in repo.tags():
7530 if n in repo.tags():
7512 raise error.InputError(
7531 raise error.InputError(
7513 _(b"tag '%s' already exists (use -f to force)") % n
7532 _(b"tag '%s' already exists (use -f to force)") % n
7514 )
7533 )
7515 if not opts.get(b'local'):
7534 if not opts.get(b'local'):
7516 p1, p2 = repo.dirstate.parents()
7535 p1, p2 = repo.dirstate.parents()
7517 if p2 != repo.nullid:
7536 if p2 != repo.nullid:
7518 raise error.StateError(_(b'uncommitted merge'))
7537 raise error.StateError(_(b'uncommitted merge'))
7519 bheads = repo.branchheads()
7538 bheads = repo.branchheads()
7520 if not opts.get(b'force') and bheads and p1 not in bheads:
7539 if not opts.get(b'force') and bheads and p1 not in bheads:
7521 raise error.InputError(
7540 raise error.InputError(
7522 _(
7541 _(
7523 b'working directory is not at a branch head '
7542 b'working directory is not at a branch head '
7524 b'(use -f to force)'
7543 b'(use -f to force)'
7525 )
7544 )
7526 )
7545 )
7527 node = logcmdutil.revsingle(repo, rev_).node()
7546 node = logcmdutil.revsingle(repo, rev_).node()
7528
7547
7529 # don't allow tagging the null rev or the working directory
7548 # don't allow tagging the null rev or the working directory
7530 if node is None:
7549 if node is None:
7531 raise error.InputError(_(b"cannot tag working directory"))
7550 raise error.InputError(_(b"cannot tag working directory"))
7532 elif not opts.get(b'remove') and node == nullid:
7551 elif not opts.get(b'remove') and node == nullid:
7533 raise error.InputError(_(b"cannot tag null revision"))
7552 raise error.InputError(_(b"cannot tag null revision"))
7534
7553
7535 if not message:
7554 if not message:
7536 # we don't translate commit messages
7555 # we don't translate commit messages
7537 message = b'Added tag %s for changeset %s' % (
7556 message = b'Added tag %s for changeset %s' % (
7538 b', '.join(names),
7557 b', '.join(names),
7539 short(node),
7558 short(node),
7540 )
7559 )
7541
7560
7542 date = opts.get(b'date')
7561 date = opts.get(b'date')
7543 if date:
7562 if date:
7544 date = dateutil.parsedate(date)
7563 date = dateutil.parsedate(date)
7545
7564
7546 if opts.get(b'remove'):
7565 if opts.get(b'remove'):
7547 editform = b'tag.remove'
7566 editform = b'tag.remove'
7548 else:
7567 else:
7549 editform = b'tag.add'
7568 editform = b'tag.add'
7550 editor = cmdutil.getcommiteditor(
7569 editor = cmdutil.getcommiteditor(
7551 editform=editform, **pycompat.strkwargs(opts)
7570 editform=editform, **pycompat.strkwargs(opts)
7552 )
7571 )
7553
7572
7554 tagsmod.tag(
7573 tagsmod.tag(
7555 repo,
7574 repo,
7556 names,
7575 names,
7557 node,
7576 node,
7558 message,
7577 message,
7559 opts.get(b'local'),
7578 opts.get(b'local'),
7560 opts.get(b'user'),
7579 opts.get(b'user'),
7561 date,
7580 date,
7562 editor=editor,
7581 editor=editor,
7563 )
7582 )
7564
7583
7565
7584
7566 @command(
7585 @command(
7567 b'tags',
7586 b'tags',
7568 formatteropts,
7587 formatteropts,
7569 b'',
7588 b'',
7570 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7589 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7571 intents={INTENT_READONLY},
7590 intents={INTENT_READONLY},
7572 )
7591 )
7573 def tags(ui, repo, **opts):
7592 def tags(ui, repo, **opts):
7574 """list repository tags
7593 """list repository tags
7575
7594
7576 This lists both regular and local tags. When the -v/--verbose
7595 This lists both regular and local tags. When the -v/--verbose
7577 switch is used, a third column "local" is printed for local tags.
7596 switch is used, a third column "local" is printed for local tags.
7578 When the -q/--quiet switch is used, only the tag name is printed.
7597 When the -q/--quiet switch is used, only the tag name is printed.
7579
7598
7580 .. container:: verbose
7599 .. container:: verbose
7581
7600
7582 Template:
7601 Template:
7583
7602
7584 The following keywords are supported in addition to the common template
7603 The following keywords are supported in addition to the common template
7585 keywords and functions such as ``{tag}``. See also
7604 keywords and functions such as ``{tag}``. See also
7586 :hg:`help templates`.
7605 :hg:`help templates`.
7587
7606
7588 :type: String. ``local`` for local tags.
7607 :type: String. ``local`` for local tags.
7589
7608
7590 Returns 0 on success.
7609 Returns 0 on success.
7591 """
7610 """
7592
7611
7593 opts = pycompat.byteskwargs(opts)
7612 opts = pycompat.byteskwargs(opts)
7594 ui.pager(b'tags')
7613 ui.pager(b'tags')
7595 fm = ui.formatter(b'tags', opts)
7614 fm = ui.formatter(b'tags', opts)
7596 hexfunc = fm.hexfunc
7615 hexfunc = fm.hexfunc
7597
7616
7598 for t, n in reversed(repo.tagslist()):
7617 for t, n in reversed(repo.tagslist()):
7599 hn = hexfunc(n)
7618 hn = hexfunc(n)
7600 label = b'tags.normal'
7619 label = b'tags.normal'
7601 tagtype = repo.tagtype(t)
7620 tagtype = repo.tagtype(t)
7602 if not tagtype or tagtype == b'global':
7621 if not tagtype or tagtype == b'global':
7603 tagtype = b''
7622 tagtype = b''
7604 else:
7623 else:
7605 label = b'tags.' + tagtype
7624 label = b'tags.' + tagtype
7606
7625
7607 fm.startitem()
7626 fm.startitem()
7608 fm.context(repo=repo)
7627 fm.context(repo=repo)
7609 fm.write(b'tag', b'%s', t, label=label)
7628 fm.write(b'tag', b'%s', t, label=label)
7610 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7629 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7611 fm.condwrite(
7630 fm.condwrite(
7612 not ui.quiet,
7631 not ui.quiet,
7613 b'rev node',
7632 b'rev node',
7614 fmt,
7633 fmt,
7615 repo.changelog.rev(n),
7634 repo.changelog.rev(n),
7616 hn,
7635 hn,
7617 label=label,
7636 label=label,
7618 )
7637 )
7619 fm.condwrite(
7638 fm.condwrite(
7620 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7639 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7621 )
7640 )
7622 fm.plain(b'\n')
7641 fm.plain(b'\n')
7623 fm.end()
7642 fm.end()
7624
7643
7625
7644
7626 @command(
7645 @command(
7627 b'tip',
7646 b'tip',
7628 [
7647 [
7629 (b'p', b'patch', None, _(b'show patch')),
7648 (b'p', b'patch', None, _(b'show patch')),
7630 (b'g', b'git', None, _(b'use git extended diff format')),
7649 (b'g', b'git', None, _(b'use git extended diff format')),
7631 ]
7650 ]
7632 + templateopts,
7651 + templateopts,
7633 _(b'[-p] [-g]'),
7652 _(b'[-p] [-g]'),
7634 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7653 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7635 )
7654 )
7636 def tip(ui, repo, **opts):
7655 def tip(ui, repo, **opts):
7637 """show the tip revision (DEPRECATED)
7656 """show the tip revision (DEPRECATED)
7638
7657
7639 The tip revision (usually just called the tip) is the changeset
7658 The tip revision (usually just called the tip) is the changeset
7640 most recently added to the repository (and therefore the most
7659 most recently added to the repository (and therefore the most
7641 recently changed head).
7660 recently changed head).
7642
7661
7643 If you have just made a commit, that commit will be the tip. If
7662 If you have just made a commit, that commit will be the tip. If
7644 you have just pulled changes from another repository, the tip of
7663 you have just pulled changes from another repository, the tip of
7645 that repository becomes the current tip. The "tip" tag is special
7664 that repository becomes the current tip. The "tip" tag is special
7646 and cannot be renamed or assigned to a different changeset.
7665 and cannot be renamed or assigned to a different changeset.
7647
7666
7648 This command is deprecated, please use :hg:`heads` instead.
7667 This command is deprecated, please use :hg:`heads` instead.
7649
7668
7650 Returns 0 on success.
7669 Returns 0 on success.
7651 """
7670 """
7652 opts = pycompat.byteskwargs(opts)
7671 opts = pycompat.byteskwargs(opts)
7653 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7672 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7654 displayer.show(repo[b'tip'])
7673 displayer.show(repo[b'tip'])
7655 displayer.close()
7674 displayer.close()
7656
7675
7657
7676
7658 @command(
7677 @command(
7659 b'unbundle',
7678 b'unbundle',
7660 [
7679 [
7661 (
7680 (
7662 b'u',
7681 b'u',
7663 b'update',
7682 b'update',
7664 None,
7683 None,
7665 _(b'update to new branch head if changesets were unbundled'),
7684 _(b'update to new branch head if changesets were unbundled'),
7666 )
7685 )
7667 ],
7686 ],
7668 _(b'[-u] FILE...'),
7687 _(b'[-u] FILE...'),
7669 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7688 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7670 )
7689 )
7671 def unbundle(ui, repo, fname1, *fnames, **opts):
7690 def unbundle(ui, repo, fname1, *fnames, **opts):
7672 """apply one or more bundle files
7691 """apply one or more bundle files
7673
7692
7674 Apply one or more bundle files generated by :hg:`bundle`.
7693 Apply one or more bundle files generated by :hg:`bundle`.
7675
7694
7676 Returns 0 on success, 1 if an update has unresolved files.
7695 Returns 0 on success, 1 if an update has unresolved files.
7677 """
7696 """
7678 fnames = (fname1,) + fnames
7697 fnames = (fname1,) + fnames
7679
7698
7680 with repo.lock():
7699 with repo.lock():
7681 for fname in fnames:
7700 for fname in fnames:
7682 f = hg.openpath(ui, fname)
7701 f = hg.openpath(ui, fname)
7683 gen = exchange.readbundle(ui, f, fname)
7702 gen = exchange.readbundle(ui, f, fname)
7684 if isinstance(gen, streamclone.streamcloneapplier):
7703 if isinstance(gen, streamclone.streamcloneapplier):
7685 raise error.InputError(
7704 raise error.InputError(
7686 _(
7705 _(
7687 b'packed bundles cannot be applied with '
7706 b'packed bundles cannot be applied with '
7688 b'"hg unbundle"'
7707 b'"hg unbundle"'
7689 ),
7708 ),
7690 hint=_(b'use "hg debugapplystreamclonebundle"'),
7709 hint=_(b'use "hg debugapplystreamclonebundle"'),
7691 )
7710 )
7692 url = b'bundle:' + fname
7711 url = b'bundle:' + fname
7693 try:
7712 try:
7694 txnname = b'unbundle'
7713 txnname = b'unbundle'
7695 if not isinstance(gen, bundle2.unbundle20):
7714 if not isinstance(gen, bundle2.unbundle20):
7696 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7715 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7697 with repo.transaction(txnname) as tr:
7716 with repo.transaction(txnname) as tr:
7698 op = bundle2.applybundle(
7717 op = bundle2.applybundle(
7699 repo, gen, tr, source=b'unbundle', url=url
7718 repo, gen, tr, source=b'unbundle', url=url
7700 )
7719 )
7701 except error.BundleUnknownFeatureError as exc:
7720 except error.BundleUnknownFeatureError as exc:
7702 raise error.Abort(
7721 raise error.Abort(
7703 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7722 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7704 hint=_(
7723 hint=_(
7705 b"see https://mercurial-scm.org/"
7724 b"see https://mercurial-scm.org/"
7706 b"wiki/BundleFeature for more "
7725 b"wiki/BundleFeature for more "
7707 b"information"
7726 b"information"
7708 ),
7727 ),
7709 )
7728 )
7710 modheads = bundle2.combinechangegroupresults(op)
7729 modheads = bundle2.combinechangegroupresults(op)
7711
7730
7712 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7731 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7713 return 1
7732 return 1
7714 else:
7733 else:
7715 return 0
7734 return 0
7716
7735
7717
7736
7718 @command(
7737 @command(
7719 b'unshelve',
7738 b'unshelve',
7720 [
7739 [
7721 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7740 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7722 (
7741 (
7723 b'c',
7742 b'c',
7724 b'continue',
7743 b'continue',
7725 None,
7744 None,
7726 _(b'continue an incomplete unshelve operation'),
7745 _(b'continue an incomplete unshelve operation'),
7727 ),
7746 ),
7728 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7747 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7729 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7748 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7730 (
7749 (
7731 b'n',
7750 b'n',
7732 b'name',
7751 b'name',
7733 b'',
7752 b'',
7734 _(b'restore shelved change with given name'),
7753 _(b'restore shelved change with given name'),
7735 _(b'NAME'),
7754 _(b'NAME'),
7736 ),
7755 ),
7737 (b't', b'tool', b'', _(b'specify merge tool')),
7756 (b't', b'tool', b'', _(b'specify merge tool')),
7738 (
7757 (
7739 b'',
7758 b'',
7740 b'date',
7759 b'date',
7741 b'',
7760 b'',
7742 _(b'set date for temporary commits (DEPRECATED)'),
7761 _(b'set date for temporary commits (DEPRECATED)'),
7743 _(b'DATE'),
7762 _(b'DATE'),
7744 ),
7763 ),
7745 ],
7764 ],
7746 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7765 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7747 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7766 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7748 )
7767 )
7749 def unshelve(ui, repo, *shelved, **opts):
7768 def unshelve(ui, repo, *shelved, **opts):
7750 """restore a shelved change to the working directory
7769 """restore a shelved change to the working directory
7751
7770
7752 This command accepts an optional name of a shelved change to
7771 This command accepts an optional name of a shelved change to
7753 restore. If none is given, the most recent shelved change is used.
7772 restore. If none is given, the most recent shelved change is used.
7754
7773
7755 If a shelved change is applied successfully, the bundle that
7774 If a shelved change is applied successfully, the bundle that
7756 contains the shelved changes is moved to a backup location
7775 contains the shelved changes is moved to a backup location
7757 (.hg/shelve-backup).
7776 (.hg/shelve-backup).
7758
7777
7759 Since you can restore a shelved change on top of an arbitrary
7778 Since you can restore a shelved change on top of an arbitrary
7760 commit, it is possible that unshelving will result in a conflict
7779 commit, it is possible that unshelving will result in a conflict
7761 between your changes and the commits you are unshelving onto. If
7780 between your changes and the commits you are unshelving onto. If
7762 this occurs, you must resolve the conflict, then use
7781 this occurs, you must resolve the conflict, then use
7763 ``--continue`` to complete the unshelve operation. (The bundle
7782 ``--continue`` to complete the unshelve operation. (The bundle
7764 will not be moved until you successfully complete the unshelve.)
7783 will not be moved until you successfully complete the unshelve.)
7765
7784
7766 (Alternatively, you can use ``--abort`` to abandon an unshelve
7785 (Alternatively, you can use ``--abort`` to abandon an unshelve
7767 that causes a conflict. This reverts the unshelved changes, and
7786 that causes a conflict. This reverts the unshelved changes, and
7768 leaves the bundle in place.)
7787 leaves the bundle in place.)
7769
7788
7770 If bare shelved change (without interactive, include and exclude
7789 If bare shelved change (without interactive, include and exclude
7771 option) was done on newly created branch it would restore branch
7790 option) was done on newly created branch it would restore branch
7772 information to the working directory.
7791 information to the working directory.
7773
7792
7774 After a successful unshelve, the shelved changes are stored in a
7793 After a successful unshelve, the shelved changes are stored in a
7775 backup directory. Only the N most recent backups are kept. N
7794 backup directory. Only the N most recent backups are kept. N
7776 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7795 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7777 configuration option.
7796 configuration option.
7778
7797
7779 .. container:: verbose
7798 .. container:: verbose
7780
7799
7781 Timestamp in seconds is used to decide order of backups. More
7800 Timestamp in seconds is used to decide order of backups. More
7782 than ``maxbackups`` backups are kept, if same timestamp
7801 than ``maxbackups`` backups are kept, if same timestamp
7783 prevents from deciding exact order of them, for safety.
7802 prevents from deciding exact order of them, for safety.
7784
7803
7785 Selected changes can be unshelved with ``--interactive`` flag.
7804 Selected changes can be unshelved with ``--interactive`` flag.
7786 The working directory is updated with the selected changes, and
7805 The working directory is updated with the selected changes, and
7787 only the unselected changes remain shelved.
7806 only the unselected changes remain shelved.
7788 Note: The whole shelve is applied to working directory first before
7807 Note: The whole shelve is applied to working directory first before
7789 running interactively. So, this will bring up all the conflicts between
7808 running interactively. So, this will bring up all the conflicts between
7790 working directory and the shelve, irrespective of which changes will be
7809 working directory and the shelve, irrespective of which changes will be
7791 unshelved.
7810 unshelved.
7792 """
7811 """
7793 with repo.wlock():
7812 with repo.wlock():
7794 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7813 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7795
7814
7796
7815
7797 statemod.addunfinished(
7816 statemod.addunfinished(
7798 b'unshelve',
7817 b'unshelve',
7799 fname=b'shelvedstate',
7818 fname=b'shelvedstate',
7800 continueflag=True,
7819 continueflag=True,
7801 abortfunc=shelvemod.hgabortunshelve,
7820 abortfunc=shelvemod.hgabortunshelve,
7802 continuefunc=shelvemod.hgcontinueunshelve,
7821 continuefunc=shelvemod.hgcontinueunshelve,
7803 cmdmsg=_(b'unshelve already in progress'),
7822 cmdmsg=_(b'unshelve already in progress'),
7804 )
7823 )
7805
7824
7806
7825
7807 @command(
7826 @command(
7808 b'update|up|checkout|co',
7827 b'update|up|checkout|co',
7809 [
7828 [
7810 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7829 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7811 (b'c', b'check', None, _(b'require clean working directory')),
7830 (b'c', b'check', None, _(b'require clean working directory')),
7812 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7831 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7813 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7832 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7814 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7833 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7815 ]
7834 ]
7816 + mergetoolopts,
7835 + mergetoolopts,
7817 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7836 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7818 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7837 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7819 helpbasic=True,
7838 helpbasic=True,
7820 )
7839 )
7821 def update(ui, repo, node=None, **opts):
7840 def update(ui, repo, node=None, **opts):
7822 """update working directory (or switch revisions)
7841 """update working directory (or switch revisions)
7823
7842
7824 Update the repository's working directory to the specified
7843 Update the repository's working directory to the specified
7825 changeset. If no changeset is specified, update to the tip of the
7844 changeset. If no changeset is specified, update to the tip of the
7826 current named branch and move the active bookmark (see :hg:`help
7845 current named branch and move the active bookmark (see :hg:`help
7827 bookmarks`).
7846 bookmarks`).
7828
7847
7829 Update sets the working directory's parent revision to the specified
7848 Update sets the working directory's parent revision to the specified
7830 changeset (see :hg:`help parents`).
7849 changeset (see :hg:`help parents`).
7831
7850
7832 If the changeset is not a descendant or ancestor of the working
7851 If the changeset is not a descendant or ancestor of the working
7833 directory's parent and there are uncommitted changes, the update is
7852 directory's parent and there are uncommitted changes, the update is
7834 aborted. With the -c/--check option, the working directory is checked
7853 aborted. With the -c/--check option, the working directory is checked
7835 for uncommitted changes; if none are found, the working directory is
7854 for uncommitted changes; if none are found, the working directory is
7836 updated to the specified changeset.
7855 updated to the specified changeset.
7837
7856
7838 .. container:: verbose
7857 .. container:: verbose
7839
7858
7840 The -C/--clean, -c/--check, and -m/--merge options control what
7859 The -C/--clean, -c/--check, and -m/--merge options control what
7841 happens if the working directory contains uncommitted changes.
7860 happens if the working directory contains uncommitted changes.
7842 At most of one of them can be specified.
7861 At most of one of them can be specified.
7843
7862
7844 1. If no option is specified, and if
7863 1. If no option is specified, and if
7845 the requested changeset is an ancestor or descendant of
7864 the requested changeset is an ancestor or descendant of
7846 the working directory's parent, the uncommitted changes
7865 the working directory's parent, the uncommitted changes
7847 are merged into the requested changeset and the merged
7866 are merged into the requested changeset and the merged
7848 result is left uncommitted. If the requested changeset is
7867 result is left uncommitted. If the requested changeset is
7849 not an ancestor or descendant (that is, it is on another
7868 not an ancestor or descendant (that is, it is on another
7850 branch), the update is aborted and the uncommitted changes
7869 branch), the update is aborted and the uncommitted changes
7851 are preserved.
7870 are preserved.
7852
7871
7853 2. With the -m/--merge option, the update is allowed even if the
7872 2. With the -m/--merge option, the update is allowed even if the
7854 requested changeset is not an ancestor or descendant of
7873 requested changeset is not an ancestor or descendant of
7855 the working directory's parent.
7874 the working directory's parent.
7856
7875
7857 3. With the -c/--check option, the update is aborted and the
7876 3. With the -c/--check option, the update is aborted and the
7858 uncommitted changes are preserved.
7877 uncommitted changes are preserved.
7859
7878
7860 4. With the -C/--clean option, uncommitted changes are discarded and
7879 4. With the -C/--clean option, uncommitted changes are discarded and
7861 the working directory is updated to the requested changeset.
7880 the working directory is updated to the requested changeset.
7862
7881
7863 To cancel an uncommitted merge (and lose your changes), use
7882 To cancel an uncommitted merge (and lose your changes), use
7864 :hg:`merge --abort`.
7883 :hg:`merge --abort`.
7865
7884
7866 Use null as the changeset to remove the working directory (like
7885 Use null as the changeset to remove the working directory (like
7867 :hg:`clone -U`).
7886 :hg:`clone -U`).
7868
7887
7869 If you want to revert just one file to an older revision, use
7888 If you want to revert just one file to an older revision, use
7870 :hg:`revert [-r REV] NAME`.
7889 :hg:`revert [-r REV] NAME`.
7871
7890
7872 See :hg:`help dates` for a list of formats valid for -d/--date.
7891 See :hg:`help dates` for a list of formats valid for -d/--date.
7873
7892
7874 Returns 0 on success, 1 if there are unresolved files.
7893 Returns 0 on success, 1 if there are unresolved files.
7875 """
7894 """
7876 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7895 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7877 rev = opts.get('rev')
7896 rev = opts.get('rev')
7878 date = opts.get('date')
7897 date = opts.get('date')
7879 clean = opts.get('clean')
7898 clean = opts.get('clean')
7880 check = opts.get('check')
7899 check = opts.get('check')
7881 merge = opts.get('merge')
7900 merge = opts.get('merge')
7882 if rev and node:
7901 if rev and node:
7883 raise error.InputError(_(b"please specify just one revision"))
7902 raise error.InputError(_(b"please specify just one revision"))
7884
7903
7885 if ui.configbool(b'commands', b'update.requiredest'):
7904 if ui.configbool(b'commands', b'update.requiredest'):
7886 if not node and not rev and not date:
7905 if not node and not rev and not date:
7887 raise error.InputError(
7906 raise error.InputError(
7888 _(b'you must specify a destination'),
7907 _(b'you must specify a destination'),
7889 hint=_(b'for example: hg update ".::"'),
7908 hint=_(b'for example: hg update ".::"'),
7890 )
7909 )
7891
7910
7892 if rev is None or rev == b'':
7911 if rev is None or rev == b'':
7893 rev = node
7912 rev = node
7894
7913
7895 if date and rev is not None:
7914 if date and rev is not None:
7896 raise error.InputError(_(b"you can't specify a revision and a date"))
7915 raise error.InputError(_(b"you can't specify a revision and a date"))
7897
7916
7898 updatecheck = None
7917 updatecheck = None
7899 if check or merge is not None and not merge:
7918 if check or merge is not None and not merge:
7900 updatecheck = b'abort'
7919 updatecheck = b'abort'
7901 elif merge or check is not None and not check:
7920 elif merge or check is not None and not check:
7902 updatecheck = b'none'
7921 updatecheck = b'none'
7903
7922
7904 with repo.wlock():
7923 with repo.wlock():
7905 cmdutil.clearunfinished(repo)
7924 cmdutil.clearunfinished(repo)
7906 if date:
7925 if date:
7907 rev = cmdutil.finddate(ui, repo, date)
7926 rev = cmdutil.finddate(ui, repo, date)
7908
7927
7909 # if we defined a bookmark, we have to remember the original name
7928 # if we defined a bookmark, we have to remember the original name
7910 brev = rev
7929 brev = rev
7911 if rev:
7930 if rev:
7912 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7931 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7913 ctx = logcmdutil.revsingle(repo, rev, default=None)
7932 ctx = logcmdutil.revsingle(repo, rev, default=None)
7914 rev = ctx.rev()
7933 rev = ctx.rev()
7915 hidden = ctx.hidden()
7934 hidden = ctx.hidden()
7916 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7935 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7917 with ui.configoverride(overrides, b'update'):
7936 with ui.configoverride(overrides, b'update'):
7918 ret = hg.updatetotally(
7937 ret = hg.updatetotally(
7919 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7938 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7920 )
7939 )
7921 if hidden:
7940 if hidden:
7922 ctxstr = ctx.hex()[:12]
7941 ctxstr = ctx.hex()[:12]
7923 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7942 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7924
7943
7925 if ctx.obsolete():
7944 if ctx.obsolete():
7926 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7945 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7927 ui.warn(b"(%s)\n" % obsfatemsg)
7946 ui.warn(b"(%s)\n" % obsfatemsg)
7928 return ret
7947 return ret
7929
7948
7930
7949
7931 @command(
7950 @command(
7932 b'verify',
7951 b'verify',
7933 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7952 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7934 helpcategory=command.CATEGORY_MAINTENANCE,
7953 helpcategory=command.CATEGORY_MAINTENANCE,
7935 )
7954 )
7936 def verify(ui, repo, **opts):
7955 def verify(ui, repo, **opts):
7937 """verify the integrity of the repository
7956 """verify the integrity of the repository
7938
7957
7939 Verify the integrity of the current repository.
7958 Verify the integrity of the current repository.
7940
7959
7941 This will perform an extensive check of the repository's
7960 This will perform an extensive check of the repository's
7942 integrity, validating the hashes and checksums of each entry in
7961 integrity, validating the hashes and checksums of each entry in
7943 the changelog, manifest, and tracked files, as well as the
7962 the changelog, manifest, and tracked files, as well as the
7944 integrity of their crosslinks and indices.
7963 integrity of their crosslinks and indices.
7945
7964
7946 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7965 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7947 for more information about recovery from corruption of the
7966 for more information about recovery from corruption of the
7948 repository.
7967 repository.
7949
7968
7950 Returns 0 on success, 1 if errors are encountered.
7969 Returns 0 on success, 1 if errors are encountered.
7951 """
7970 """
7952 opts = pycompat.byteskwargs(opts)
7971 opts = pycompat.byteskwargs(opts)
7953
7972
7954 level = None
7973 level = None
7955 if opts[b'full']:
7974 if opts[b'full']:
7956 level = verifymod.VERIFY_FULL
7975 level = verifymod.VERIFY_FULL
7957 return hg.verify(repo, level)
7976 return hg.verify(repo, level)
7958
7977
7959
7978
7960 @command(
7979 @command(
7961 b'version',
7980 b'version',
7962 [] + formatteropts,
7981 [] + formatteropts,
7963 helpcategory=command.CATEGORY_HELP,
7982 helpcategory=command.CATEGORY_HELP,
7964 norepo=True,
7983 norepo=True,
7965 intents={INTENT_READONLY},
7984 intents={INTENT_READONLY},
7966 )
7985 )
7967 def version_(ui, **opts):
7986 def version_(ui, **opts):
7968 """output version and copyright information
7987 """output version and copyright information
7969
7988
7970 .. container:: verbose
7989 .. container:: verbose
7971
7990
7972 Template:
7991 Template:
7973
7992
7974 The following keywords are supported. See also :hg:`help templates`.
7993 The following keywords are supported. See also :hg:`help templates`.
7975
7994
7976 :extensions: List of extensions.
7995 :extensions: List of extensions.
7977 :ver: String. Version number.
7996 :ver: String. Version number.
7978
7997
7979 And each entry of ``{extensions}`` provides the following sub-keywords
7998 And each entry of ``{extensions}`` provides the following sub-keywords
7980 in addition to ``{ver}``.
7999 in addition to ``{ver}``.
7981
8000
7982 :bundled: Boolean. True if included in the release.
8001 :bundled: Boolean. True if included in the release.
7983 :name: String. Extension name.
8002 :name: String. Extension name.
7984 """
8003 """
7985 opts = pycompat.byteskwargs(opts)
8004 opts = pycompat.byteskwargs(opts)
7986 if ui.verbose:
8005 if ui.verbose:
7987 ui.pager(b'version')
8006 ui.pager(b'version')
7988 fm = ui.formatter(b"version", opts)
8007 fm = ui.formatter(b"version", opts)
7989 fm.startitem()
8008 fm.startitem()
7990 fm.write(
8009 fm.write(
7991 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
8010 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7992 )
8011 )
7993 license = _(
8012 license = _(
7994 b"(see https://mercurial-scm.org for more information)\n"
8013 b"(see https://mercurial-scm.org for more information)\n"
7995 b"\nCopyright (C) 2005-2023 Olivia Mackall and others\n"
8014 b"\nCopyright (C) 2005-2023 Olivia Mackall and others\n"
7996 b"This is free software; see the source for copying conditions. "
8015 b"This is free software; see the source for copying conditions. "
7997 b"There is NO\nwarranty; "
8016 b"There is NO\nwarranty; "
7998 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
8017 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7999 )
8018 )
8000 if not ui.quiet:
8019 if not ui.quiet:
8001 fm.plain(license)
8020 fm.plain(license)
8002
8021
8003 if ui.verbose:
8022 if ui.verbose:
8004 fm.plain(_(b"\nEnabled extensions:\n\n"))
8023 fm.plain(_(b"\nEnabled extensions:\n\n"))
8005 # format names and versions into columns
8024 # format names and versions into columns
8006 names = []
8025 names = []
8007 vers = []
8026 vers = []
8008 isinternals = []
8027 isinternals = []
8009 for name, module in sorted(extensions.extensions()):
8028 for name, module in sorted(extensions.extensions()):
8010 names.append(name)
8029 names.append(name)
8011 vers.append(extensions.moduleversion(module) or None)
8030 vers.append(extensions.moduleversion(module) or None)
8012 isinternals.append(extensions.ismoduleinternal(module))
8031 isinternals.append(extensions.ismoduleinternal(module))
8013 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
8032 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
8014 if names:
8033 if names:
8015 namefmt = b" %%-%ds " % max(len(n) for n in names)
8034 namefmt = b" %%-%ds " % max(len(n) for n in names)
8016 places = [_(b"external"), _(b"internal")]
8035 places = [_(b"external"), _(b"internal")]
8017 for n, v, p in zip(names, vers, isinternals):
8036 for n, v, p in zip(names, vers, isinternals):
8018 fn.startitem()
8037 fn.startitem()
8019 fn.condwrite(ui.verbose, b"name", namefmt, n)
8038 fn.condwrite(ui.verbose, b"name", namefmt, n)
8020 if ui.verbose:
8039 if ui.verbose:
8021 fn.plain(b"%s " % places[p])
8040 fn.plain(b"%s " % places[p])
8022 fn.data(bundled=p)
8041 fn.data(bundled=p)
8023 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
8042 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
8024 if ui.verbose:
8043 if ui.verbose:
8025 fn.plain(b"\n")
8044 fn.plain(b"\n")
8026 fn.end()
8045 fn.end()
8027 fm.end()
8046 fm.end()
8028
8047
8029
8048
8030 def loadcmdtable(ui, name, cmdtable):
8049 def loadcmdtable(ui, name, cmdtable):
8031 """Load command functions from specified cmdtable"""
8050 """Load command functions from specified cmdtable"""
8032 overrides = [cmd for cmd in cmdtable if cmd in table]
8051 overrides = [cmd for cmd in cmdtable if cmd in table]
8033 if overrides:
8052 if overrides:
8034 ui.warn(
8053 ui.warn(
8035 _(b"extension '%s' overrides commands: %s\n")
8054 _(b"extension '%s' overrides commands: %s\n")
8036 % (name, b" ".join(overrides))
8055 % (name, b" ".join(overrides))
8037 )
8056 )
8038 table.update(cmdtable)
8057 table.update(cmdtable)
@@ -1,704 +1,710 b''
1 # sshpeer.py - ssh repository proxy class for mercurial
1 # sshpeer.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005, 2006 Olivia Mackall <olivia@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
8
9 import re
9 import re
10 import uuid
10 import uuid
11
11
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14 from . import (
14 from . import (
15 error,
15 error,
16 pycompat,
16 pycompat,
17 util,
17 util,
18 wireprototypes,
18 wireprototypes,
19 wireprotov1peer,
19 wireprotov1peer,
20 wireprotov1server,
20 wireprotov1server,
21 )
21 )
22 from .utils import (
22 from .utils import (
23 procutil,
23 procutil,
24 stringutil,
24 stringutil,
25 urlutil,
25 urlutil,
26 )
26 )
27
27
28
28
29 def _serverquote(s):
29 def _serverquote(s):
30 """quote a string for the remote shell ... which we assume is sh"""
30 """quote a string for the remote shell ... which we assume is sh"""
31 if not s:
31 if not s:
32 return s
32 return s
33 if re.match(b'[a-zA-Z0-9@%_+=:,./-]*$', s):
33 if re.match(b'[a-zA-Z0-9@%_+=:,./-]*$', s):
34 return s
34 return s
35 return b"'%s'" % s.replace(b"'", b"'\\''")
35 return b"'%s'" % s.replace(b"'", b"'\\''")
36
36
37
37
38 def _forwardoutput(ui, pipe, warn=False):
38 def _forwardoutput(ui, pipe, warn=False):
39 """display all data currently available on pipe as remote output.
39 """display all data currently available on pipe as remote output.
40
40
41 This is non blocking."""
41 This is non blocking."""
42 if pipe and not pipe.closed:
42 if pipe and not pipe.closed:
43 s = procutil.readpipe(pipe)
43 s = procutil.readpipe(pipe)
44 if s:
44 if s:
45 display = ui.warn if warn else ui.status
45 display = ui.warn if warn else ui.status
46 for l in s.splitlines():
46 for l in s.splitlines():
47 display(_(b"remote: "), l, b'\n')
47 display(_(b"remote: "), l, b'\n')
48
48
49
49
50 class doublepipe:
50 class doublepipe:
51 """Operate a side-channel pipe in addition of a main one
51 """Operate a side-channel pipe in addition of a main one
52
52
53 The side-channel pipe contains server output to be forwarded to the user
53 The side-channel pipe contains server output to be forwarded to the user
54 input. The double pipe will behave as the "main" pipe, but will ensure the
54 input. The double pipe will behave as the "main" pipe, but will ensure the
55 content of the "side" pipe is properly processed while we wait for blocking
55 content of the "side" pipe is properly processed while we wait for blocking
56 call on the "main" pipe.
56 call on the "main" pipe.
57
57
58 If large amounts of data are read from "main", the forward will cease after
58 If large amounts of data are read from "main", the forward will cease after
59 the first bytes start to appear. This simplifies the implementation
59 the first bytes start to appear. This simplifies the implementation
60 without affecting actual output of sshpeer too much as we rarely issue
60 without affecting actual output of sshpeer too much as we rarely issue
61 large read for data not yet emitted by the server.
61 large read for data not yet emitted by the server.
62
62
63 The main pipe is expected to be a 'bufferedinputpipe' from the util module
63 The main pipe is expected to be a 'bufferedinputpipe' from the util module
64 that handle all the os specific bits. This class lives in this module
64 that handle all the os specific bits. This class lives in this module
65 because it focus on behavior specific to the ssh protocol."""
65 because it focus on behavior specific to the ssh protocol."""
66
66
67 def __init__(self, ui, main, side):
67 def __init__(self, ui, main, side):
68 self._ui = ui
68 self._ui = ui
69 self._main = main
69 self._main = main
70 self._side = side
70 self._side = side
71
71
72 def _wait(self):
72 def _wait(self):
73 """wait until some data are available on main or side
73 """wait until some data are available on main or side
74
74
75 return a pair of boolean (ismainready, issideready)
75 return a pair of boolean (ismainready, issideready)
76
76
77 (This will only wait for data if the setup is supported by `util.poll`)
77 (This will only wait for data if the setup is supported by `util.poll`)
78 """
78 """
79 if (
79 if (
80 isinstance(self._main, util.bufferedinputpipe)
80 isinstance(self._main, util.bufferedinputpipe)
81 and self._main.hasbuffer
81 and self._main.hasbuffer
82 ):
82 ):
83 # Main has data. Assume side is worth poking at.
83 # Main has data. Assume side is worth poking at.
84 return True, True
84 return True, True
85
85
86 fds = [self._main.fileno(), self._side.fileno()]
86 fds = [self._main.fileno(), self._side.fileno()]
87 try:
87 try:
88 act = util.poll(fds)
88 act = util.poll(fds)
89 except NotImplementedError:
89 except NotImplementedError:
90 # non supported yet case, assume all have data.
90 # non supported yet case, assume all have data.
91 act = fds
91 act = fds
92 return (self._main.fileno() in act, self._side.fileno() in act)
92 return (self._main.fileno() in act, self._side.fileno() in act)
93
93
94 def write(self, data):
94 def write(self, data):
95 return self._call(b'write', data)
95 return self._call(b'write', data)
96
96
97 def read(self, size):
97 def read(self, size):
98 r = self._call(b'read', size)
98 r = self._call(b'read', size)
99 if size != 0 and not r:
99 if size != 0 and not r:
100 # We've observed a condition that indicates the
100 # We've observed a condition that indicates the
101 # stdout closed unexpectedly. Check stderr one
101 # stdout closed unexpectedly. Check stderr one
102 # more time and snag anything that's there before
102 # more time and snag anything that's there before
103 # letting anyone know the main part of the pipe
103 # letting anyone know the main part of the pipe
104 # closed prematurely.
104 # closed prematurely.
105 _forwardoutput(self._ui, self._side)
105 _forwardoutput(self._ui, self._side)
106 return r
106 return r
107
107
108 def unbufferedread(self, size):
108 def unbufferedread(self, size):
109 r = self._call(b'unbufferedread', size)
109 r = self._call(b'unbufferedread', size)
110 if size != 0 and not r:
110 if size != 0 and not r:
111 # We've observed a condition that indicates the
111 # We've observed a condition that indicates the
112 # stdout closed unexpectedly. Check stderr one
112 # stdout closed unexpectedly. Check stderr one
113 # more time and snag anything that's there before
113 # more time and snag anything that's there before
114 # letting anyone know the main part of the pipe
114 # letting anyone know the main part of the pipe
115 # closed prematurely.
115 # closed prematurely.
116 _forwardoutput(self._ui, self._side)
116 _forwardoutput(self._ui, self._side)
117 return r
117 return r
118
118
119 def readline(self):
119 def readline(self):
120 return self._call(b'readline')
120 return self._call(b'readline')
121
121
122 def _call(self, methname, data=None):
122 def _call(self, methname, data=None):
123 """call <methname> on "main", forward output of "side" while blocking"""
123 """call <methname> on "main", forward output of "side" while blocking"""
124 # data can be '' or 0
124 # data can be '' or 0
125 if (data is not None and not data) or self._main.closed:
125 if (data is not None and not data) or self._main.closed:
126 _forwardoutput(self._ui, self._side)
126 _forwardoutput(self._ui, self._side)
127 return b''
127 return b''
128 while True:
128 while True:
129 mainready, sideready = self._wait()
129 mainready, sideready = self._wait()
130 if sideready:
130 if sideready:
131 _forwardoutput(self._ui, self._side)
131 _forwardoutput(self._ui, self._side)
132 if mainready:
132 if mainready:
133 meth = getattr(self._main, methname)
133 meth = getattr(self._main, methname)
134 if data is None:
134 if data is None:
135 return meth()
135 return meth()
136 else:
136 else:
137 return meth(data)
137 return meth(data)
138
138
139 def close(self):
139 def close(self):
140 return self._main.close()
140 return self._main.close()
141
141
142 @property
142 @property
143 def closed(self):
143 def closed(self):
144 return self._main.closed
144 return self._main.closed
145
145
146 def flush(self):
146 def flush(self):
147 return self._main.flush()
147 return self._main.flush()
148
148
149
149
150 def _cleanuppipes(ui, pipei, pipeo, pipee, warn):
150 def _cleanuppipes(ui, pipei, pipeo, pipee, warn):
151 """Clean up pipes used by an SSH connection."""
151 """Clean up pipes used by an SSH connection."""
152 didsomething = False
152 didsomething = False
153 if pipeo and not pipeo.closed:
153 if pipeo and not pipeo.closed:
154 didsomething = True
154 didsomething = True
155 pipeo.close()
155 pipeo.close()
156 if pipei and not pipei.closed:
156 if pipei and not pipei.closed:
157 didsomething = True
157 didsomething = True
158 pipei.close()
158 pipei.close()
159
159
160 if pipee and not pipee.closed:
160 if pipee and not pipee.closed:
161 didsomething = True
161 didsomething = True
162 # Try to read from the err descriptor until EOF.
162 # Try to read from the err descriptor until EOF.
163 try:
163 try:
164 for l in pipee:
164 for l in pipee:
165 ui.status(_(b'remote: '), l)
165 ui.status(_(b'remote: '), l)
166 except (IOError, ValueError):
166 except (IOError, ValueError):
167 pass
167 pass
168
168
169 pipee.close()
169 pipee.close()
170
170
171 if didsomething and warn is not None:
171 if didsomething and warn is not None:
172 # Encourage explicit close of sshpeers. Closing via __del__ is
172 # Encourage explicit close of sshpeers. Closing via __del__ is
173 # not very predictable when exceptions are thrown, which has led
173 # not very predictable when exceptions are thrown, which has led
174 # to deadlocks due to a peer get gc'ed in a fork
174 # to deadlocks due to a peer get gc'ed in a fork
175 # We add our own stack trace, because the stacktrace when called
175 # We add our own stack trace, because the stacktrace when called
176 # from __del__ is useless.
176 # from __del__ is useless.
177 ui.develwarn(b'missing close on SSH connection created at:\n%s' % warn)
177 ui.develwarn(b'missing close on SSH connection created at:\n%s' % warn)
178
178
179
179
180 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
180 def _makeconnection(
181 ui, sshcmd, args, remotecmd, path, sshenv=None, remotehidden=False
182 ):
181 """Create an SSH connection to a server.
183 """Create an SSH connection to a server.
182
184
183 Returns a tuple of (process, stdin, stdout, stderr) for the
185 Returns a tuple of (process, stdin, stdout, stderr) for the
184 spawned process.
186 spawned process.
185 """
187 """
186 cmd = b'%s %s %s' % (
188 cmd = b'%s %s %s' % (
187 sshcmd,
189 sshcmd,
188 args,
190 args,
189 procutil.shellquote(
191 procutil.shellquote(
190 b'%s -R %s serve --stdio'
192 b'%s -R %s serve --stdio%s'
191 % (_serverquote(remotecmd), _serverquote(path))
193 % (
194 _serverquote(remotecmd),
195 _serverquote(path),
196 b' --hidden' if remotehidden else b'',
197 )
192 ),
198 ),
193 )
199 )
194
200
195 ui.debug(b'running %s\n' % cmd)
201 ui.debug(b'running %s\n' % cmd)
196
202
197 # no buffer allow the use of 'select'
203 # no buffer allow the use of 'select'
198 # feel free to remove buffering and select usage when we ultimately
204 # feel free to remove buffering and select usage when we ultimately
199 # move to threading.
205 # move to threading.
200 stdin, stdout, stderr, proc = procutil.popen4(cmd, bufsize=0, env=sshenv)
206 stdin, stdout, stderr, proc = procutil.popen4(cmd, bufsize=0, env=sshenv)
201
207
202 return proc, stdin, stdout, stderr
208 return proc, stdin, stdout, stderr
203
209
204
210
205 def _clientcapabilities():
211 def _clientcapabilities():
206 """Return list of capabilities of this client.
212 """Return list of capabilities of this client.
207
213
208 Returns a list of capabilities that are supported by this client.
214 Returns a list of capabilities that are supported by this client.
209 """
215 """
210 protoparams = {b'partial-pull'}
216 protoparams = {b'partial-pull'}
211 comps = [
217 comps = [
212 e.wireprotosupport().name
218 e.wireprotosupport().name
213 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
219 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
214 ]
220 ]
215 protoparams.add(b'comp=%s' % b','.join(comps))
221 protoparams.add(b'comp=%s' % b','.join(comps))
216 return protoparams
222 return protoparams
217
223
218
224
219 def _performhandshake(ui, stdin, stdout, stderr):
225 def _performhandshake(ui, stdin, stdout, stderr):
220 def badresponse():
226 def badresponse():
221 # Flush any output on stderr. In general, the stderr contains errors
227 # Flush any output on stderr. In general, the stderr contains errors
222 # from the remote (ssh errors, some hg errors), and status indications
228 # from the remote (ssh errors, some hg errors), and status indications
223 # (like "adding changes"), with no current way to tell them apart.
229 # (like "adding changes"), with no current way to tell them apart.
224 # Here we failed so early that it's almost certainly only errors, so
230 # Here we failed so early that it's almost certainly only errors, so
225 # use warn=True so -q doesn't hide them.
231 # use warn=True so -q doesn't hide them.
226 _forwardoutput(ui, stderr, warn=True)
232 _forwardoutput(ui, stderr, warn=True)
227
233
228 msg = _(b'no suitable response from remote hg')
234 msg = _(b'no suitable response from remote hg')
229 hint = ui.config(b'ui', b'ssherrorhint')
235 hint = ui.config(b'ui', b'ssherrorhint')
230 raise error.RepoError(msg, hint=hint)
236 raise error.RepoError(msg, hint=hint)
231
237
232 # The handshake consists of sending wire protocol commands in reverse
238 # The handshake consists of sending wire protocol commands in reverse
233 # order of protocol implementation and then sniffing for a response
239 # order of protocol implementation and then sniffing for a response
234 # to one of them.
240 # to one of them.
235 #
241 #
236 # Those commands (from oldest to newest) are:
242 # Those commands (from oldest to newest) are:
237 #
243 #
238 # ``between``
244 # ``between``
239 # Asks for the set of revisions between a pair of revisions. Command
245 # Asks for the set of revisions between a pair of revisions. Command
240 # present in all Mercurial server implementations.
246 # present in all Mercurial server implementations.
241 #
247 #
242 # ``hello``
248 # ``hello``
243 # Instructs the server to advertise its capabilities. Introduced in
249 # Instructs the server to advertise its capabilities. Introduced in
244 # Mercurial 0.9.1.
250 # Mercurial 0.9.1.
245 #
251 #
246 # ``upgrade``
252 # ``upgrade``
247 # Requests upgrade from default transport protocol version 1 to
253 # Requests upgrade from default transport protocol version 1 to
248 # a newer version. Introduced in Mercurial 4.6 as an experimental
254 # a newer version. Introduced in Mercurial 4.6 as an experimental
249 # feature.
255 # feature.
250 #
256 #
251 # The ``between`` command is issued with a request for the null
257 # The ``between`` command is issued with a request for the null
252 # range. If the remote is a Mercurial server, this request will
258 # range. If the remote is a Mercurial server, this request will
253 # generate a specific response: ``1\n\n``. This represents the
259 # generate a specific response: ``1\n\n``. This represents the
254 # wire protocol encoded value for ``\n``. We look for ``1\n\n``
260 # wire protocol encoded value for ``\n``. We look for ``1\n\n``
255 # in the output stream and know this is the response to ``between``
261 # in the output stream and know this is the response to ``between``
256 # and we're at the end of our handshake reply.
262 # and we're at the end of our handshake reply.
257 #
263 #
258 # The response to the ``hello`` command will be a line with the
264 # The response to the ``hello`` command will be a line with the
259 # length of the value returned by that command followed by that
265 # length of the value returned by that command followed by that
260 # value. If the server doesn't support ``hello`` (which should be
266 # value. If the server doesn't support ``hello`` (which should be
261 # rare), that line will be ``0\n``. Otherwise, the value will contain
267 # rare), that line will be ``0\n``. Otherwise, the value will contain
262 # RFC 822 like lines. Of these, the ``capabilities:`` line contains
268 # RFC 822 like lines. Of these, the ``capabilities:`` line contains
263 # the capabilities of the server.
269 # the capabilities of the server.
264 #
270 #
265 # The ``upgrade`` command isn't really a command in the traditional
271 # The ``upgrade`` command isn't really a command in the traditional
266 # sense of version 1 of the transport because it isn't using the
272 # sense of version 1 of the transport because it isn't using the
267 # proper mechanism for formatting insteads: instead, it just encodes
273 # proper mechanism for formatting insteads: instead, it just encodes
268 # arguments on the line, delimited by spaces.
274 # arguments on the line, delimited by spaces.
269 #
275 #
270 # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
276 # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
271 # If the server doesn't support protocol upgrades, it will reply to
277 # If the server doesn't support protocol upgrades, it will reply to
272 # this line with ``0\n``. Otherwise, it emits an
278 # this line with ``0\n``. Otherwise, it emits an
273 # ``upgraded <token> <protocol>`` line to both stdout and stderr.
279 # ``upgraded <token> <protocol>`` line to both stdout and stderr.
274 # Content immediately following this line describes additional
280 # Content immediately following this line describes additional
275 # protocol and server state.
281 # protocol and server state.
276 #
282 #
277 # In addition to the responses to our command requests, the server
283 # In addition to the responses to our command requests, the server
278 # may emit "banner" output on stdout. SSH servers are allowed to
284 # may emit "banner" output on stdout. SSH servers are allowed to
279 # print messages to stdout on login. Issuing commands on connection
285 # print messages to stdout on login. Issuing commands on connection
280 # allows us to flush this banner output from the server by scanning
286 # allows us to flush this banner output from the server by scanning
281 # for output to our well-known ``between`` command. Of course, if
287 # for output to our well-known ``between`` command. Of course, if
282 # the banner contains ``1\n\n``, this will throw off our detection.
288 # the banner contains ``1\n\n``, this will throw off our detection.
283
289
284 requestlog = ui.configbool(b'devel', b'debug.peer-request')
290 requestlog = ui.configbool(b'devel', b'debug.peer-request')
285
291
286 # Generate a random token to help identify responses to version 2
292 # Generate a random token to help identify responses to version 2
287 # upgrade request.
293 # upgrade request.
288 token = pycompat.sysbytes(str(uuid.uuid4()))
294 token = pycompat.sysbytes(str(uuid.uuid4()))
289
295
290 try:
296 try:
291 pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40)
297 pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40)
292 handshake = [
298 handshake = [
293 b'hello\n',
299 b'hello\n',
294 b'between\n',
300 b'between\n',
295 b'pairs %d\n' % len(pairsarg),
301 b'pairs %d\n' % len(pairsarg),
296 pairsarg,
302 pairsarg,
297 ]
303 ]
298
304
299 if requestlog:
305 if requestlog:
300 ui.debug(b'devel-peer-request: hello+between\n')
306 ui.debug(b'devel-peer-request: hello+between\n')
301 ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
307 ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
302 ui.debug(b'sending hello command\n')
308 ui.debug(b'sending hello command\n')
303 ui.debug(b'sending between command\n')
309 ui.debug(b'sending between command\n')
304
310
305 stdin.write(b''.join(handshake))
311 stdin.write(b''.join(handshake))
306 stdin.flush()
312 stdin.flush()
307 except IOError:
313 except IOError:
308 badresponse()
314 badresponse()
309
315
310 # Assume version 1 of wire protocol by default.
316 # Assume version 1 of wire protocol by default.
311 protoname = wireprototypes.SSHV1
317 protoname = wireprototypes.SSHV1
312 reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
318 reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
313
319
314 lines = [b'', b'dummy']
320 lines = [b'', b'dummy']
315 max_noise = 500
321 max_noise = 500
316 while lines[-1] and max_noise:
322 while lines[-1] and max_noise:
317 try:
323 try:
318 l = stdout.readline()
324 l = stdout.readline()
319 _forwardoutput(ui, stderr, warn=True)
325 _forwardoutput(ui, stderr, warn=True)
320
326
321 # Look for reply to protocol upgrade request. It has a token
327 # Look for reply to protocol upgrade request. It has a token
322 # in it, so there should be no false positives.
328 # in it, so there should be no false positives.
323 m = reupgraded.match(l)
329 m = reupgraded.match(l)
324 if m:
330 if m:
325 protoname = m.group(1)
331 protoname = m.group(1)
326 ui.debug(b'protocol upgraded to %s\n' % protoname)
332 ui.debug(b'protocol upgraded to %s\n' % protoname)
327 # If an upgrade was handled, the ``hello`` and ``between``
333 # If an upgrade was handled, the ``hello`` and ``between``
328 # requests are ignored. The next output belongs to the
334 # requests are ignored. The next output belongs to the
329 # protocol, so stop scanning lines.
335 # protocol, so stop scanning lines.
330 break
336 break
331
337
332 # Otherwise it could be a banner, ``0\n`` response if server
338 # Otherwise it could be a banner, ``0\n`` response if server
333 # doesn't support upgrade.
339 # doesn't support upgrade.
334
340
335 if lines[-1] == b'1\n' and l == b'\n':
341 if lines[-1] == b'1\n' and l == b'\n':
336 break
342 break
337 if l:
343 if l:
338 ui.debug(b'remote: ', l)
344 ui.debug(b'remote: ', l)
339 lines.append(l)
345 lines.append(l)
340 max_noise -= 1
346 max_noise -= 1
341 except IOError:
347 except IOError:
342 badresponse()
348 badresponse()
343 else:
349 else:
344 badresponse()
350 badresponse()
345
351
346 caps = set()
352 caps = set()
347
353
348 # For version 1, we should see a ``capabilities`` line in response to the
354 # For version 1, we should see a ``capabilities`` line in response to the
349 # ``hello`` command.
355 # ``hello`` command.
350 if protoname == wireprototypes.SSHV1:
356 if protoname == wireprototypes.SSHV1:
351 for l in reversed(lines):
357 for l in reversed(lines):
352 # Look for response to ``hello`` command. Scan from the back so
358 # Look for response to ``hello`` command. Scan from the back so
353 # we don't misinterpret banner output as the command reply.
359 # we don't misinterpret banner output as the command reply.
354 if l.startswith(b'capabilities:'):
360 if l.startswith(b'capabilities:'):
355 caps.update(l[:-1].split(b':')[1].split())
361 caps.update(l[:-1].split(b':')[1].split())
356 break
362 break
357
363
358 # Error if we couldn't find capabilities, this means:
364 # Error if we couldn't find capabilities, this means:
359 #
365 #
360 # 1. Remote isn't a Mercurial server
366 # 1. Remote isn't a Mercurial server
361 # 2. Remote is a <0.9.1 Mercurial server
367 # 2. Remote is a <0.9.1 Mercurial server
362 # 3. Remote is a future Mercurial server that dropped ``hello``
368 # 3. Remote is a future Mercurial server that dropped ``hello``
363 # and other attempted handshake mechanisms.
369 # and other attempted handshake mechanisms.
364 if not caps:
370 if not caps:
365 badresponse()
371 badresponse()
366
372
367 # Flush any output on stderr before proceeding.
373 # Flush any output on stderr before proceeding.
368 _forwardoutput(ui, stderr, warn=True)
374 _forwardoutput(ui, stderr, warn=True)
369
375
370 return protoname, caps
376 return protoname, caps
371
377
372
378
373 class sshv1peer(wireprotov1peer.wirepeer):
379 class sshv1peer(wireprotov1peer.wirepeer):
374 def __init__(
380 def __init__(
375 self,
381 self,
376 ui,
382 ui,
377 path,
383 path,
378 proc,
384 proc,
379 stdin,
385 stdin,
380 stdout,
386 stdout,
381 stderr,
387 stderr,
382 caps,
388 caps,
383 autoreadstderr=True,
389 autoreadstderr=True,
384 remotehidden=False,
390 remotehidden=False,
385 ):
391 ):
386 """Create a peer from an existing SSH connection.
392 """Create a peer from an existing SSH connection.
387
393
388 ``proc`` is a handle on the underlying SSH process.
394 ``proc`` is a handle on the underlying SSH process.
389 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
395 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
390 pipes for that process.
396 pipes for that process.
391 ``caps`` is a set of capabilities supported by the remote.
397 ``caps`` is a set of capabilities supported by the remote.
392 ``autoreadstderr`` denotes whether to automatically read from
398 ``autoreadstderr`` denotes whether to automatically read from
393 stderr and to forward its output.
399 stderr and to forward its output.
394 """
400 """
395 super().__init__(ui, path=path, remotehidden=remotehidden)
401 super().__init__(ui, path=path, remotehidden=remotehidden)
396 if remotehidden:
397 msg = _(
398 b"ignoring `--remote-hidden` request\n"
399 b"(access to hidden changeset for ssh peers not supported "
400 b"yet)\n"
401 )
402 ui.warn(msg)
403 # self._subprocess is unused. Keeping a handle on the process
402 # self._subprocess is unused. Keeping a handle on the process
404 # holds a reference and prevents it from being garbage collected.
403 # holds a reference and prevents it from being garbage collected.
405 self._subprocess = proc
404 self._subprocess = proc
406
405
407 # And we hook up our "doublepipe" wrapper to allow querying
406 # And we hook up our "doublepipe" wrapper to allow querying
408 # stderr any time we perform I/O.
407 # stderr any time we perform I/O.
409 if autoreadstderr:
408 if autoreadstderr:
410 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
409 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
411 stdin = doublepipe(ui, stdin, stderr)
410 stdin = doublepipe(ui, stdin, stderr)
412
411
413 self._pipeo = stdin
412 self._pipeo = stdin
414 self._pipei = stdout
413 self._pipei = stdout
415 self._pipee = stderr
414 self._pipee = stderr
416 self._caps = caps
415 self._caps = caps
417 self._autoreadstderr = autoreadstderr
416 self._autoreadstderr = autoreadstderr
418 self._initstack = b''.join(util.getstackframes(1))
417 self._initstack = b''.join(util.getstackframes(1))
418 self._remotehidden = remotehidden
419
419
420 # Commands that have a "framed" response where the first line of the
420 # Commands that have a "framed" response where the first line of the
421 # response contains the length of that response.
421 # response contains the length of that response.
422 _FRAMED_COMMANDS = {
422 _FRAMED_COMMANDS = {
423 b'batch',
423 b'batch',
424 }
424 }
425
425
426 # Begin of ipeerconnection interface.
426 # Begin of ipeerconnection interface.
427
427
428 def url(self):
428 def url(self):
429 return self.path.loc
429 return self.path.loc
430
430
431 def local(self):
431 def local(self):
432 return None
432 return None
433
433
434 def canpush(self):
434 def canpush(self):
435 return True
435 return True
436
436
437 def close(self):
437 def close(self):
438 self._cleanup()
438 self._cleanup()
439
439
440 # End of ipeerconnection interface.
440 # End of ipeerconnection interface.
441
441
442 # Begin of ipeercommands interface.
442 # Begin of ipeercommands interface.
443
443
444 def capabilities(self):
444 def capabilities(self):
445 return self._caps
445 return self._caps
446
446
447 # End of ipeercommands interface.
447 # End of ipeercommands interface.
448
448
449 def _readerr(self):
449 def _readerr(self):
450 _forwardoutput(self.ui, self._pipee)
450 _forwardoutput(self.ui, self._pipee)
451
451
452 def _abort(self, exception):
452 def _abort(self, exception):
453 self._cleanup()
453 self._cleanup()
454 raise exception
454 raise exception
455
455
456 def _cleanup(self, warn=None):
456 def _cleanup(self, warn=None):
457 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee, warn=warn)
457 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee, warn=warn)
458
458
459 def __del__(self):
459 def __del__(self):
460 self._cleanup(warn=self._initstack)
460 self._cleanup(warn=self._initstack)
461
461
462 def _sendrequest(self, cmd, args, framed=False):
462 def _sendrequest(self, cmd, args, framed=False):
463 if self.ui.debugflag and self.ui.configbool(
463 if self.ui.debugflag and self.ui.configbool(
464 b'devel', b'debug.peer-request'
464 b'devel', b'debug.peer-request'
465 ):
465 ):
466 dbg = self.ui.debug
466 dbg = self.ui.debug
467 line = b'devel-peer-request: %s\n'
467 line = b'devel-peer-request: %s\n'
468 dbg(line % cmd)
468 dbg(line % cmd)
469 for key, value in sorted(args.items()):
469 for key, value in sorted(args.items()):
470 if not isinstance(value, dict):
470 if not isinstance(value, dict):
471 dbg(line % b' %s: %d bytes' % (key, len(value)))
471 dbg(line % b' %s: %d bytes' % (key, len(value)))
472 else:
472 else:
473 for dk, dv in sorted(value.items()):
473 for dk, dv in sorted(value.items()):
474 dbg(line % b' %s-%s: %d' % (key, dk, len(dv)))
474 dbg(line % b' %s-%s: %d' % (key, dk, len(dv)))
475 self.ui.debug(b"sending %s command\n" % cmd)
475 self.ui.debug(b"sending %s command\n" % cmd)
476 self._pipeo.write(b"%s\n" % cmd)
476 self._pipeo.write(b"%s\n" % cmd)
477 _func, names = wireprotov1server.commands[cmd]
477 _func, names = wireprotov1server.commands[cmd]
478 keys = names.split()
478 keys = names.split()
479 wireargs = {}
479 wireargs = {}
480 for k in keys:
480 for k in keys:
481 if k == b'*':
481 if k == b'*':
482 wireargs[b'*'] = args
482 wireargs[b'*'] = args
483 break
483 break
484 else:
484 else:
485 wireargs[k] = args[k]
485 wireargs[k] = args[k]
486 del args[k]
486 del args[k]
487 for k, v in sorted(wireargs.items()):
487 for k, v in sorted(wireargs.items()):
488 self._pipeo.write(b"%s %d\n" % (k, len(v)))
488 self._pipeo.write(b"%s %d\n" % (k, len(v)))
489 if isinstance(v, dict):
489 if isinstance(v, dict):
490 for dk, dv in v.items():
490 for dk, dv in v.items():
491 self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
491 self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
492 self._pipeo.write(dv)
492 self._pipeo.write(dv)
493 else:
493 else:
494 self._pipeo.write(v)
494 self._pipeo.write(v)
495 self._pipeo.flush()
495 self._pipeo.flush()
496
496
497 # We know exactly how many bytes are in the response. So return a proxy
497 # We know exactly how many bytes are in the response. So return a proxy
498 # around the raw output stream that allows reading exactly this many
498 # around the raw output stream that allows reading exactly this many
499 # bytes. Callers then can read() without fear of overrunning the
499 # bytes. Callers then can read() without fear of overrunning the
500 # response.
500 # response.
501 if framed:
501 if framed:
502 amount = self._getamount()
502 amount = self._getamount()
503 return util.cappedreader(self._pipei, amount)
503 return util.cappedreader(self._pipei, amount)
504
504
505 return self._pipei
505 return self._pipei
506
506
507 def _callstream(self, cmd, **args):
507 def _callstream(self, cmd, **args):
508 args = pycompat.byteskwargs(args)
508 args = pycompat.byteskwargs(args)
509 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
509 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
510
510
511 def _callcompressable(self, cmd, **args):
511 def _callcompressable(self, cmd, **args):
512 args = pycompat.byteskwargs(args)
512 args = pycompat.byteskwargs(args)
513 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
513 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
514
514
515 def _call(self, cmd, **args):
515 def _call(self, cmd, **args):
516 args = pycompat.byteskwargs(args)
516 args = pycompat.byteskwargs(args)
517 return self._sendrequest(cmd, args, framed=True).read()
517 return self._sendrequest(cmd, args, framed=True).read()
518
518
519 def _callpush(self, cmd, fp, **args):
519 def _callpush(self, cmd, fp, **args):
520 # The server responds with an empty frame if the client should
520 # The server responds with an empty frame if the client should
521 # continue submitting the payload.
521 # continue submitting the payload.
522 r = self._call(cmd, **args)
522 r = self._call(cmd, **args)
523 if r:
523 if r:
524 return b'', r
524 return b'', r
525
525
526 # The payload consists of frames with content followed by an empty
526 # The payload consists of frames with content followed by an empty
527 # frame.
527 # frame.
528 for d in iter(lambda: fp.read(4096), b''):
528 for d in iter(lambda: fp.read(4096), b''):
529 self._writeframed(d)
529 self._writeframed(d)
530 self._writeframed(b"", flush=True)
530 self._writeframed(b"", flush=True)
531
531
532 # In case of success, there is an empty frame and a frame containing
532 # In case of success, there is an empty frame and a frame containing
533 # the integer result (as a string).
533 # the integer result (as a string).
534 # In case of error, there is a non-empty frame containing the error.
534 # In case of error, there is a non-empty frame containing the error.
535 r = self._readframed()
535 r = self._readframed()
536 if r:
536 if r:
537 return b'', r
537 return b'', r
538 return self._readframed(), b''
538 return self._readframed(), b''
539
539
540 def _calltwowaystream(self, cmd, fp, **args):
540 def _calltwowaystream(self, cmd, fp, **args):
541 # The server responds with an empty frame if the client should
541 # The server responds with an empty frame if the client should
542 # continue submitting the payload.
542 # continue submitting the payload.
543 r = self._call(cmd, **args)
543 r = self._call(cmd, **args)
544 if r:
544 if r:
545 # XXX needs to be made better
545 # XXX needs to be made better
546 raise error.Abort(_(b'unexpected remote reply: %s') % r)
546 raise error.Abort(_(b'unexpected remote reply: %s') % r)
547
547
548 # The payload consists of frames with content followed by an empty
548 # The payload consists of frames with content followed by an empty
549 # frame.
549 # frame.
550 for d in iter(lambda: fp.read(4096), b''):
550 for d in iter(lambda: fp.read(4096), b''):
551 self._writeframed(d)
551 self._writeframed(d)
552 self._writeframed(b"", flush=True)
552 self._writeframed(b"", flush=True)
553
553
554 return self._pipei
554 return self._pipei
555
555
556 def _getamount(self):
556 def _getamount(self):
557 l = self._pipei.readline()
557 l = self._pipei.readline()
558 if l == b'\n':
558 if l == b'\n':
559 if self._autoreadstderr:
559 if self._autoreadstderr:
560 self._readerr()
560 self._readerr()
561 msg = _(b'check previous remote output')
561 msg = _(b'check previous remote output')
562 self._abort(error.OutOfBandError(hint=msg))
562 self._abort(error.OutOfBandError(hint=msg))
563 if self._autoreadstderr:
563 if self._autoreadstderr:
564 self._readerr()
564 self._readerr()
565 try:
565 try:
566 return int(l)
566 return int(l)
567 except ValueError:
567 except ValueError:
568 self._abort(error.ResponseError(_(b"unexpected response:"), l))
568 self._abort(error.ResponseError(_(b"unexpected response:"), l))
569
569
570 def _readframed(self):
570 def _readframed(self):
571 size = self._getamount()
571 size = self._getamount()
572 if not size:
572 if not size:
573 return b''
573 return b''
574
574
575 return self._pipei.read(size)
575 return self._pipei.read(size)
576
576
577 def _writeframed(self, data, flush=False):
577 def _writeframed(self, data, flush=False):
578 self._pipeo.write(b"%d\n" % len(data))
578 self._pipeo.write(b"%d\n" % len(data))
579 if data:
579 if data:
580 self._pipeo.write(data)
580 self._pipeo.write(data)
581 if flush:
581 if flush:
582 self._pipeo.flush()
582 self._pipeo.flush()
583 if self._autoreadstderr:
583 if self._autoreadstderr:
584 self._readerr()
584 self._readerr()
585
585
586
586
587 def _make_peer(
587 def _make_peer(
588 ui,
588 ui,
589 path,
589 path,
590 proc,
590 proc,
591 stdin,
591 stdin,
592 stdout,
592 stdout,
593 stderr,
593 stderr,
594 autoreadstderr=True,
594 autoreadstderr=True,
595 remotehidden=False,
595 remotehidden=False,
596 ):
596 ):
597 """Make a peer instance from existing pipes.
597 """Make a peer instance from existing pipes.
598
598
599 ``path`` and ``proc`` are stored on the eventual peer instance and may
599 ``path`` and ``proc`` are stored on the eventual peer instance and may
600 not be used for anything meaningful.
600 not be used for anything meaningful.
601
601
602 ``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
602 ``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
603 SSH server's stdio handles.
603 SSH server's stdio handles.
604
604
605 This function is factored out to allow creating peers that don't
605 This function is factored out to allow creating peers that don't
606 actually spawn a new process. It is useful for starting SSH protocol
606 actually spawn a new process. It is useful for starting SSH protocol
607 servers and clients via non-standard means, which can be useful for
607 servers and clients via non-standard means, which can be useful for
608 testing.
608 testing.
609 """
609 """
610 try:
610 try:
611 protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
611 protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
612 except Exception:
612 except Exception:
613 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
613 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
614 raise
614 raise
615
615
616 if protoname == wireprototypes.SSHV1:
616 if protoname == wireprototypes.SSHV1:
617 return sshv1peer(
617 return sshv1peer(
618 ui,
618 ui,
619 path,
619 path,
620 proc,
620 proc,
621 stdin,
621 stdin,
622 stdout,
622 stdout,
623 stderr,
623 stderr,
624 caps,
624 caps,
625 autoreadstderr=autoreadstderr,
625 autoreadstderr=autoreadstderr,
626 remotehidden=remotehidden,
626 remotehidden=remotehidden,
627 )
627 )
628 else:
628 else:
629 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
629 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
630 raise error.RepoError(
630 raise error.RepoError(
631 _(b'unknown version of SSH protocol: %s') % protoname
631 _(b'unknown version of SSH protocol: %s') % protoname
632 )
632 )
633
633
634
634
635 def make_peer(
635 def make_peer(
636 ui, path, create, intents=None, createopts=None, remotehidden=False
636 ui, path, create, intents=None, createopts=None, remotehidden=False
637 ):
637 ):
638 """Create an SSH peer.
638 """Create an SSH peer.
639
639
640 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
640 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
641 """
641 """
642 u = urlutil.url(path.loc, parsequery=False, parsefragment=False)
642 u = urlutil.url(path.loc, parsequery=False, parsefragment=False)
643 if u.scheme != b'ssh' or not u.host or u.path is None:
643 if u.scheme != b'ssh' or not u.host or u.path is None:
644 raise error.RepoError(_(b"couldn't parse location %s") % path)
644 raise error.RepoError(_(b"couldn't parse location %s") % path)
645
645
646 urlutil.checksafessh(path.loc)
646 urlutil.checksafessh(path.loc)
647
647
648 if u.passwd is not None:
648 if u.passwd is not None:
649 raise error.RepoError(_(b'password in URL not supported'))
649 raise error.RepoError(_(b'password in URL not supported'))
650
650
651 sshcmd = ui.config(b'ui', b'ssh')
651 sshcmd = ui.config(b'ui', b'ssh')
652 remotecmd = ui.config(b'ui', b'remotecmd')
652 remotecmd = ui.config(b'ui', b'remotecmd')
653 sshaddenv = dict(ui.configitems(b'sshenv'))
653 sshaddenv = dict(ui.configitems(b'sshenv'))
654 sshenv = procutil.shellenviron(sshaddenv)
654 sshenv = procutil.shellenviron(sshaddenv)
655 remotepath = u.path or b'.'
655 remotepath = u.path or b'.'
656
656
657 args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
657 args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
658
658
659 if create:
659 if create:
660 # We /could/ do this, but only if the remote init command knows how to
660 # We /could/ do this, but only if the remote init command knows how to
661 # handle them. We don't yet make any assumptions about that. And without
661 # handle them. We don't yet make any assumptions about that. And without
662 # querying the remote, there's no way of knowing if the remote even
662 # querying the remote, there's no way of knowing if the remote even
663 # supports said requested feature.
663 # supports said requested feature.
664 if createopts:
664 if createopts:
665 raise error.RepoError(
665 raise error.RepoError(
666 _(
666 _(
667 b'cannot create remote SSH repositories '
667 b'cannot create remote SSH repositories '
668 b'with extra options'
668 b'with extra options'
669 )
669 )
670 )
670 )
671
671
672 cmd = b'%s %s %s' % (
672 cmd = b'%s %s %s' % (
673 sshcmd,
673 sshcmd,
674 args,
674 args,
675 procutil.shellquote(
675 procutil.shellquote(
676 b'%s init %s'
676 b'%s init %s'
677 % (_serverquote(remotecmd), _serverquote(remotepath))
677 % (_serverquote(remotecmd), _serverquote(remotepath))
678 ),
678 ),
679 )
679 )
680 ui.debug(b'running %s\n' % cmd)
680 ui.debug(b'running %s\n' % cmd)
681 res = ui.system(cmd, blockedtag=b'sshpeer', environ=sshenv)
681 res = ui.system(cmd, blockedtag=b'sshpeer', environ=sshenv)
682 if res != 0:
682 if res != 0:
683 raise error.RepoError(_(b'could not create remote repo'))
683 raise error.RepoError(_(b'could not create remote repo'))
684
684
685 proc, stdin, stdout, stderr = _makeconnection(
685 proc, stdin, stdout, stderr = _makeconnection(
686 ui, sshcmd, args, remotecmd, remotepath, sshenv
686 ui,
687 sshcmd,
688 args,
689 remotecmd,
690 remotepath,
691 sshenv,
692 remotehidden=remotehidden,
687 )
693 )
688
694
689 peer = _make_peer(
695 peer = _make_peer(
690 ui, path, proc, stdin, stdout, stderr, remotehidden=remotehidden
696 ui, path, proc, stdin, stdout, stderr, remotehidden=remotehidden
691 )
697 )
692
698
693 # Finally, if supported by the server, notify it about our own
699 # Finally, if supported by the server, notify it about our own
694 # capabilities.
700 # capabilities.
695 if b'protocaps' in peer.capabilities():
701 if b'protocaps' in peer.capabilities():
696 try:
702 try:
697 peer._call(
703 peer._call(
698 b"protocaps", caps=b' '.join(sorted(_clientcapabilities()))
704 b"protocaps", caps=b' '.join(sorted(_clientcapabilities()))
699 )
705 )
700 except IOError:
706 except IOError:
701 peer._cleanup()
707 peer._cleanup()
702 raise error.RepoError(_(b'capability exchange failed'))
708 raise error.RepoError(_(b'capability exchange failed'))
703
709
704 return peer
710 return peer
@@ -1,545 +1,550 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
2 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7
7
8 import contextlib
8 import contextlib
9 import struct
9 import struct
10 import threading
10 import threading
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 encoding,
14 encoding,
15 error,
15 error,
16 pycompat,
16 pycompat,
17 util,
17 util,
18 wireprototypes,
18 wireprototypes,
19 wireprotov1server,
19 wireprotov1server,
20 )
20 )
21 from .interfaces import util as interfaceutil
21 from .interfaces import util as interfaceutil
22 from .utils import (
22 from .utils import (
23 compression,
23 compression,
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27 stringio = util.stringio
27 stringio = util.stringio
28
28
29 urlerr = util.urlerr
29 urlerr = util.urlerr
30 urlreq = util.urlreq
30 urlreq = util.urlreq
31
31
32 HTTP_OK = 200
32 HTTP_OK = 200
33
33
34 HGTYPE = b'application/mercurial-0.1'
34 HGTYPE = b'application/mercurial-0.1'
35 HGTYPE2 = b'application/mercurial-0.2'
35 HGTYPE2 = b'application/mercurial-0.2'
36 HGERRTYPE = b'application/hg-error'
36 HGERRTYPE = b'application/hg-error'
37
37
38 SSHV1 = wireprototypes.SSHV1
38 SSHV1 = wireprototypes.SSHV1
39
39
40
40
41 def decodevaluefromheaders(req, headerprefix):
41 def decodevaluefromheaders(req, headerprefix):
42 """Decode a long value from multiple HTTP request headers.
42 """Decode a long value from multiple HTTP request headers.
43
43
44 Returns the value as a bytes, not a str.
44 Returns the value as a bytes, not a str.
45 """
45 """
46 chunks = []
46 chunks = []
47 i = 1
47 i = 1
48 while True:
48 while True:
49 v = req.headers.get(b'%s-%d' % (headerprefix, i))
49 v = req.headers.get(b'%s-%d' % (headerprefix, i))
50 if v is None:
50 if v is None:
51 break
51 break
52 chunks.append(pycompat.bytesurl(v))
52 chunks.append(pycompat.bytesurl(v))
53 i += 1
53 i += 1
54
54
55 return b''.join(chunks)
55 return b''.join(chunks)
56
56
57
57
58 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
58 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
59 class httpv1protocolhandler:
59 class httpv1protocolhandler:
60 def __init__(self, req, ui, checkperm):
60 def __init__(self, req, ui, checkperm):
61 self._req = req
61 self._req = req
62 self._ui = ui
62 self._ui = ui
63 self._checkperm = checkperm
63 self._checkperm = checkperm
64 self._protocaps = None
64 self._protocaps = None
65
65
66 @property
66 @property
67 def name(self):
67 def name(self):
68 return b'http-v1'
68 return b'http-v1'
69
69
70 def getargs(self, args):
70 def getargs(self, args):
71 knownargs = self._args()
71 knownargs = self._args()
72 data = {}
72 data = {}
73 keys = args.split()
73 keys = args.split()
74 for k in keys:
74 for k in keys:
75 if k == b'*':
75 if k == b'*':
76 star = {}
76 star = {}
77 for key in knownargs.keys():
77 for key in knownargs.keys():
78 if key != b'cmd' and key not in keys:
78 if key != b'cmd' and key not in keys:
79 star[key] = knownargs[key][0]
79 star[key] = knownargs[key][0]
80 data[b'*'] = star
80 data[b'*'] = star
81 else:
81 else:
82 data[k] = knownargs[k][0]
82 data[k] = knownargs[k][0]
83 return [data[k] for k in keys]
83 return [data[k] for k in keys]
84
84
85 def _args(self):
85 def _args(self):
86 args = self._req.qsparams.asdictoflists()
86 args = self._req.qsparams.asdictoflists()
87 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
87 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
88 if postlen:
88 if postlen:
89 args.update(
89 args.update(
90 urlreq.parseqs(
90 urlreq.parseqs(
91 self._req.bodyfh.read(postlen), keep_blank_values=True
91 self._req.bodyfh.read(postlen), keep_blank_values=True
92 )
92 )
93 )
93 )
94 return args
94 return args
95
95
96 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
96 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
97 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
97 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
98 return args
98 return args
99
99
100 def getprotocaps(self):
100 def getprotocaps(self):
101 if self._protocaps is None:
101 if self._protocaps is None:
102 value = decodevaluefromheaders(self._req, b'X-HgProto')
102 value = decodevaluefromheaders(self._req, b'X-HgProto')
103 self._protocaps = set(value.split(b' '))
103 self._protocaps = set(value.split(b' '))
104 return self._protocaps
104 return self._protocaps
105
105
106 def getpayload(self):
106 def getpayload(self):
107 # Existing clients *always* send Content-Length.
107 # Existing clients *always* send Content-Length.
108 length = int(self._req.headers[b'Content-Length'])
108 length = int(self._req.headers[b'Content-Length'])
109
109
110 # If httppostargs is used, we need to read Content-Length
110 # If httppostargs is used, we need to read Content-Length
111 # minus the amount that was consumed by args.
111 # minus the amount that was consumed by args.
112 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
112 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
113 return util.filechunkiter(self._req.bodyfh, limit=length)
113 return util.filechunkiter(self._req.bodyfh, limit=length)
114
114
115 @contextlib.contextmanager
115 @contextlib.contextmanager
116 def mayberedirectstdio(self):
116 def mayberedirectstdio(self):
117 oldout = self._ui.fout
117 oldout = self._ui.fout
118 olderr = self._ui.ferr
118 olderr = self._ui.ferr
119
119
120 out = util.stringio()
120 out = util.stringio()
121
121
122 try:
122 try:
123 self._ui.fout = out
123 self._ui.fout = out
124 self._ui.ferr = out
124 self._ui.ferr = out
125 yield out
125 yield out
126 finally:
126 finally:
127 self._ui.fout = oldout
127 self._ui.fout = oldout
128 self._ui.ferr = olderr
128 self._ui.ferr = olderr
129
129
130 def client(self):
130 def client(self):
131 return b'remote:%s:%s:%s' % (
131 return b'remote:%s:%s:%s' % (
132 self._req.urlscheme,
132 self._req.urlscheme,
133 urlreq.quote(self._req.remotehost or b''),
133 urlreq.quote(self._req.remotehost or b''),
134 urlreq.quote(self._req.remoteuser or b''),
134 urlreq.quote(self._req.remoteuser or b''),
135 )
135 )
136
136
137 def addcapabilities(self, repo, caps):
137 def addcapabilities(self, repo, caps):
138 caps.append(b'batch')
138 caps.append(b'batch')
139
139
140 caps.append(
140 caps.append(
141 b'httpheader=%d' % repo.ui.configint(b'server', b'maxhttpheaderlen')
141 b'httpheader=%d' % repo.ui.configint(b'server', b'maxhttpheaderlen')
142 )
142 )
143 if repo.ui.configbool(b'experimental', b'httppostargs'):
143 if repo.ui.configbool(b'experimental', b'httppostargs'):
144 caps.append(b'httppostargs')
144 caps.append(b'httppostargs')
145
145
146 # FUTURE advertise 0.2rx once support is implemented
146 # FUTURE advertise 0.2rx once support is implemented
147 # FUTURE advertise minrx and mintx after consulting config option
147 # FUTURE advertise minrx and mintx after consulting config option
148 caps.append(b'httpmediatype=0.1rx,0.1tx,0.2tx')
148 caps.append(b'httpmediatype=0.1rx,0.1tx,0.2tx')
149
149
150 compengines = wireprototypes.supportedcompengines(
150 compengines = wireprototypes.supportedcompengines(
151 repo.ui, compression.SERVERROLE
151 repo.ui, compression.SERVERROLE
152 )
152 )
153 if compengines:
153 if compengines:
154 comptypes = b','.join(
154 comptypes = b','.join(
155 urlreq.quote(e.wireprotosupport().name) for e in compengines
155 urlreq.quote(e.wireprotosupport().name) for e in compengines
156 )
156 )
157 caps.append(b'compression=%s' % comptypes)
157 caps.append(b'compression=%s' % comptypes)
158
158
159 return caps
159 return caps
160
160
161 def checkperm(self, perm):
161 def checkperm(self, perm):
162 return self._checkperm(perm)
162 return self._checkperm(perm)
163
163
164
164
165 # This method exists mostly so that extensions like remotefilelog can
165 # This method exists mostly so that extensions like remotefilelog can
166 # disable a kludgey legacy method only over http. As of early 2018,
166 # disable a kludgey legacy method only over http. As of early 2018,
167 # there are no other known users, so with any luck we can discard this
167 # there are no other known users, so with any luck we can discard this
168 # hook if remotefilelog becomes a first-party extension.
168 # hook if remotefilelog becomes a first-party extension.
169 def iscmd(cmd):
169 def iscmd(cmd):
170 return cmd in wireprotov1server.commands
170 return cmd in wireprotov1server.commands
171
171
172
172
173 def handlewsgirequest(rctx, req, res, checkperm):
173 def handlewsgirequest(rctx, req, res, checkperm):
174 """Possibly process a wire protocol request.
174 """Possibly process a wire protocol request.
175
175
176 If the current request is a wire protocol request, the request is
176 If the current request is a wire protocol request, the request is
177 processed by this function.
177 processed by this function.
178
178
179 ``req`` is a ``parsedrequest`` instance.
179 ``req`` is a ``parsedrequest`` instance.
180 ``res`` is a ``wsgiresponse`` instance.
180 ``res`` is a ``wsgiresponse`` instance.
181
181
182 Returns a bool indicating if the request was serviced. If set, the caller
182 Returns a bool indicating if the request was serviced. If set, the caller
183 should stop processing the request, as a response has already been issued.
183 should stop processing the request, as a response has already been issued.
184 """
184 """
185 # Avoid cycle involving hg module.
185 # Avoid cycle involving hg module.
186 from .hgweb import common as hgwebcommon
186 from .hgweb import common as hgwebcommon
187
187
188 repo = rctx.repo
188 repo = rctx.repo
189
189
190 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
190 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
191 # string parameter. If it isn't present, this isn't a wire protocol
191 # string parameter. If it isn't present, this isn't a wire protocol
192 # request.
192 # request.
193 if b'cmd' not in req.qsparams:
193 if b'cmd' not in req.qsparams:
194 return False
194 return False
195
195
196 cmd = req.qsparams[b'cmd']
196 cmd = req.qsparams[b'cmd']
197
197
198 # The "cmd" request parameter is used by both the wire protocol and hgweb.
198 # The "cmd" request parameter is used by both the wire protocol and hgweb.
199 # While not all wire protocol commands are available for all transports,
199 # While not all wire protocol commands are available for all transports,
200 # if we see a "cmd" value that resembles a known wire protocol command, we
200 # if we see a "cmd" value that resembles a known wire protocol command, we
201 # route it to a protocol handler. This is better than routing possible
201 # route it to a protocol handler. This is better than routing possible
202 # wire protocol requests to hgweb because it prevents hgweb from using
202 # wire protocol requests to hgweb because it prevents hgweb from using
203 # known wire protocol commands and it is less confusing for machine
203 # known wire protocol commands and it is less confusing for machine
204 # clients.
204 # clients.
205 if not iscmd(cmd):
205 if not iscmd(cmd):
206 return False
206 return False
207
207
208 # The "cmd" query string argument is only valid on the root path of the
208 # The "cmd" query string argument is only valid on the root path of the
209 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
209 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
210 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
210 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
211 # in this case. We send an HTTP 404 for backwards compatibility reasons.
211 # in this case. We send an HTTP 404 for backwards compatibility reasons.
212 if req.dispatchpath:
212 if req.dispatchpath:
213 res.status = hgwebcommon.statusmessage(404)
213 res.status = hgwebcommon.statusmessage(404)
214 res.headers[b'Content-Type'] = HGTYPE
214 res.headers[b'Content-Type'] = HGTYPE
215 # TODO This is not a good response to issue for this request. This
215 # TODO This is not a good response to issue for this request. This
216 # is mostly for BC for now.
216 # is mostly for BC for now.
217 res.setbodybytes(b'0\n%s\n' % b'Not Found')
217 res.setbodybytes(b'0\n%s\n' % b'Not Found')
218 return True
218 return True
219
219
220 proto = httpv1protocolhandler(
220 proto = httpv1protocolhandler(
221 req, repo.ui, lambda perm: checkperm(rctx, req, perm)
221 req, repo.ui, lambda perm: checkperm(rctx, req, perm)
222 )
222 )
223
223
224 # The permissions checker should be the only thing that can raise an
224 # The permissions checker should be the only thing that can raise an
225 # ErrorResponse. It is kind of a layer violation to catch an hgweb
225 # ErrorResponse. It is kind of a layer violation to catch an hgweb
226 # exception here. So consider refactoring into a exception type that
226 # exception here. So consider refactoring into a exception type that
227 # is associated with the wire protocol.
227 # is associated with the wire protocol.
228 try:
228 try:
229 _callhttp(repo, req, res, proto, cmd)
229 _callhttp(repo, req, res, proto, cmd)
230 except hgwebcommon.ErrorResponse as e:
230 except hgwebcommon.ErrorResponse as e:
231 for k, v in e.headers:
231 for k, v in e.headers:
232 res.headers[k] = v
232 res.headers[k] = v
233 res.status = hgwebcommon.statusmessage(
233 res.status = hgwebcommon.statusmessage(
234 e.code, stringutil.forcebytestr(e)
234 e.code, stringutil.forcebytestr(e)
235 )
235 )
236 # TODO This response body assumes the failed command was
236 # TODO This response body assumes the failed command was
237 # "unbundle." That assumption is not always valid.
237 # "unbundle." That assumption is not always valid.
238 res.setbodybytes(b'0\n%s\n' % stringutil.forcebytestr(e))
238 res.setbodybytes(b'0\n%s\n' % stringutil.forcebytestr(e))
239
239
240 return True
240 return True
241
241
242
242
243 def _httpresponsetype(ui, proto, prefer_uncompressed):
243 def _httpresponsetype(ui, proto, prefer_uncompressed):
244 """Determine the appropriate response type and compression settings.
244 """Determine the appropriate response type and compression settings.
245
245
246 Returns a tuple of (mediatype, compengine, engineopts).
246 Returns a tuple of (mediatype, compengine, engineopts).
247 """
247 """
248 # Determine the response media type and compression engine based
248 # Determine the response media type and compression engine based
249 # on the request parameters.
249 # on the request parameters.
250
250
251 if b'0.2' in proto.getprotocaps():
251 if b'0.2' in proto.getprotocaps():
252 # All clients are expected to support uncompressed data.
252 # All clients are expected to support uncompressed data.
253 if prefer_uncompressed:
253 if prefer_uncompressed:
254 return HGTYPE2, compression._noopengine(), {}
254 return HGTYPE2, compression._noopengine(), {}
255
255
256 # Now find an agreed upon compression format.
256 # Now find an agreed upon compression format.
257 compformats = wireprotov1server.clientcompressionsupport(proto)
257 compformats = wireprotov1server.clientcompressionsupport(proto)
258 for engine in wireprototypes.supportedcompengines(
258 for engine in wireprototypes.supportedcompengines(
259 ui, compression.SERVERROLE
259 ui, compression.SERVERROLE
260 ):
260 ):
261 if engine.wireprotosupport().name in compformats:
261 if engine.wireprotosupport().name in compformats:
262 opts = {}
262 opts = {}
263 level = ui.configint(b'server', b'%slevel' % engine.name())
263 level = ui.configint(b'server', b'%slevel' % engine.name())
264 if level is not None:
264 if level is not None:
265 opts[b'level'] = level
265 opts[b'level'] = level
266
266
267 return HGTYPE2, engine, opts
267 return HGTYPE2, engine, opts
268
268
269 # No mutually supported compression format. Fall back to the
269 # No mutually supported compression format. Fall back to the
270 # legacy protocol.
270 # legacy protocol.
271
271
272 # Don't allow untrusted settings because disabling compression or
272 # Don't allow untrusted settings because disabling compression or
273 # setting a very high compression level could lead to flooding
273 # setting a very high compression level could lead to flooding
274 # the server's network or CPU.
274 # the server's network or CPU.
275 opts = {b'level': ui.configint(b'server', b'zliblevel')}
275 opts = {b'level': ui.configint(b'server', b'zliblevel')}
276 return HGTYPE, util.compengines[b'zlib'], opts
276 return HGTYPE, util.compengines[b'zlib'], opts
277
277
278
278
279 def _callhttp(repo, req, res, proto, cmd):
279 def _callhttp(repo, req, res, proto, cmd):
280 # Avoid cycle involving hg module.
280 # Avoid cycle involving hg module.
281 from .hgweb import common as hgwebcommon
281 from .hgweb import common as hgwebcommon
282
282
283 def genversion2(gen, engine, engineopts):
283 def genversion2(gen, engine, engineopts):
284 # application/mercurial-0.2 always sends a payload header
284 # application/mercurial-0.2 always sends a payload header
285 # identifying the compression engine.
285 # identifying the compression engine.
286 name = engine.wireprotosupport().name
286 name = engine.wireprotosupport().name
287 assert 0 < len(name) < 256
287 assert 0 < len(name) < 256
288 yield struct.pack(b'B', len(name))
288 yield struct.pack(b'B', len(name))
289 yield name
289 yield name
290
290
291 for chunk in gen:
291 for chunk in gen:
292 yield chunk
292 yield chunk
293
293
294 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
294 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
295 if code == HTTP_OK:
295 if code == HTTP_OK:
296 res.status = b'200 Script output follows'
296 res.status = b'200 Script output follows'
297 else:
297 else:
298 res.status = hgwebcommon.statusmessage(code)
298 res.status = hgwebcommon.statusmessage(code)
299
299
300 res.headers[b'Content-Type'] = contenttype
300 res.headers[b'Content-Type'] = contenttype
301
301
302 if bodybytes is not None:
302 if bodybytes is not None:
303 res.setbodybytes(bodybytes)
303 res.setbodybytes(bodybytes)
304 if bodygen is not None:
304 if bodygen is not None:
305 res.setbodygen(bodygen)
305 res.setbodygen(bodygen)
306
306
307 if not wireprotov1server.commands.commandavailable(cmd, proto):
307 if not wireprotov1server.commands.commandavailable(cmd, proto):
308 setresponse(
308 setresponse(
309 HTTP_OK,
309 HTTP_OK,
310 HGERRTYPE,
310 HGERRTYPE,
311 _(
311 _(
312 b'requested wire protocol command is not available over '
312 b'requested wire protocol command is not available over '
313 b'HTTP'
313 b'HTTP'
314 ),
314 ),
315 )
315 )
316 return
316 return
317
317
318 proto.checkperm(wireprotov1server.commands[cmd].permission)
318 proto.checkperm(wireprotov1server.commands[cmd].permission)
319
319
320 accesshidden = hgwebcommon.hashiddenaccess(repo, req)
320 accesshidden = hgwebcommon.hashiddenaccess(repo, req)
321 rsp = wireprotov1server.dispatch(repo, proto, cmd, accesshidden)
321 rsp = wireprotov1server.dispatch(repo, proto, cmd, accesshidden)
322
322
323 if isinstance(rsp, bytes):
323 if isinstance(rsp, bytes):
324 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
324 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
325 elif isinstance(rsp, wireprototypes.bytesresponse):
325 elif isinstance(rsp, wireprototypes.bytesresponse):
326 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
326 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
327 elif isinstance(rsp, wireprototypes.streamreslegacy):
327 elif isinstance(rsp, wireprototypes.streamreslegacy):
328 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
328 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
329 elif isinstance(rsp, wireprototypes.streamres):
329 elif isinstance(rsp, wireprototypes.streamres):
330 gen = rsp.gen
330 gen = rsp.gen
331
331
332 # This code for compression should not be streamres specific. It
332 # This code for compression should not be streamres specific. It
333 # is here because we only compress streamres at the moment.
333 # is here because we only compress streamres at the moment.
334 mediatype, engine, engineopts = _httpresponsetype(
334 mediatype, engine, engineopts = _httpresponsetype(
335 repo.ui, proto, rsp.prefer_uncompressed
335 repo.ui, proto, rsp.prefer_uncompressed
336 )
336 )
337 gen = engine.compressstream(gen, engineopts)
337 gen = engine.compressstream(gen, engineopts)
338
338
339 if mediatype == HGTYPE2:
339 if mediatype == HGTYPE2:
340 gen = genversion2(gen, engine, engineopts)
340 gen = genversion2(gen, engine, engineopts)
341
341
342 setresponse(HTTP_OK, mediatype, bodygen=gen)
342 setresponse(HTTP_OK, mediatype, bodygen=gen)
343 elif isinstance(rsp, wireprototypes.pushres):
343 elif isinstance(rsp, wireprototypes.pushres):
344 rsp = b'%d\n%s' % (rsp.res, rsp.output)
344 rsp = b'%d\n%s' % (rsp.res, rsp.output)
345 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
345 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
346 elif isinstance(rsp, wireprototypes.pusherr):
346 elif isinstance(rsp, wireprototypes.pusherr):
347 rsp = b'0\n%s\n' % rsp.res
347 rsp = b'0\n%s\n' % rsp.res
348 res.drain = True
348 res.drain = True
349 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
349 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
350 elif isinstance(rsp, wireprototypes.ooberror):
350 elif isinstance(rsp, wireprototypes.ooberror):
351 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
351 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
352 else:
352 else:
353 raise error.ProgrammingError(b'hgweb.protocol internal failure', rsp)
353 raise error.ProgrammingError(b'hgweb.protocol internal failure', rsp)
354
354
355
355
356 def _sshv1respondbytes(fout, value):
356 def _sshv1respondbytes(fout, value):
357 """Send a bytes response for protocol version 1."""
357 """Send a bytes response for protocol version 1."""
358 fout.write(b'%d\n' % len(value))
358 fout.write(b'%d\n' % len(value))
359 fout.write(value)
359 fout.write(value)
360 fout.flush()
360 fout.flush()
361
361
362
362
363 def _sshv1respondstream(fout, source):
363 def _sshv1respondstream(fout, source):
364 write = fout.write
364 write = fout.write
365 for chunk in source.gen:
365 for chunk in source.gen:
366 write(chunk)
366 write(chunk)
367 fout.flush()
367 fout.flush()
368
368
369
369
370 def _sshv1respondooberror(fout, ferr, rsp):
370 def _sshv1respondooberror(fout, ferr, rsp):
371 ferr.write(b'%s\n-\n' % rsp)
371 ferr.write(b'%s\n-\n' % rsp)
372 ferr.flush()
372 ferr.flush()
373 fout.write(b'\n')
373 fout.write(b'\n')
374 fout.flush()
374 fout.flush()
375
375
376
376
377 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
377 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
378 class sshv1protocolhandler:
378 class sshv1protocolhandler:
379 """Handler for requests services via version 1 of SSH protocol."""
379 """Handler for requests services via version 1 of SSH protocol."""
380
380
381 def __init__(self, ui, fin, fout):
381 def __init__(self, ui, fin, fout):
382 self._ui = ui
382 self._ui = ui
383 self._fin = fin
383 self._fin = fin
384 self._fout = fout
384 self._fout = fout
385 self._protocaps = set()
385 self._protocaps = set()
386
386
387 @property
387 @property
388 def name(self):
388 def name(self):
389 return wireprototypes.SSHV1
389 return wireprototypes.SSHV1
390
390
391 def getargs(self, args):
391 def getargs(self, args):
392 data = {}
392 data = {}
393 keys = args.split()
393 keys = args.split()
394 for n in range(len(keys)):
394 for n in range(len(keys)):
395 argline = self._fin.readline()[:-1]
395 argline = self._fin.readline()[:-1]
396 arg, l = argline.split()
396 arg, l = argline.split()
397 if arg not in keys:
397 if arg not in keys:
398 raise error.Abort(_(b"unexpected parameter %r") % arg)
398 raise error.Abort(_(b"unexpected parameter %r") % arg)
399 if arg == b'*':
399 if arg == b'*':
400 star = {}
400 star = {}
401 for k in range(int(l)):
401 for k in range(int(l)):
402 argline = self._fin.readline()[:-1]
402 argline = self._fin.readline()[:-1]
403 arg, l = argline.split()
403 arg, l = argline.split()
404 val = self._fin.read(int(l))
404 val = self._fin.read(int(l))
405 star[arg] = val
405 star[arg] = val
406 data[b'*'] = star
406 data[b'*'] = star
407 else:
407 else:
408 val = self._fin.read(int(l))
408 val = self._fin.read(int(l))
409 data[arg] = val
409 data[arg] = val
410 return [data[k] for k in keys]
410 return [data[k] for k in keys]
411
411
412 def getprotocaps(self):
412 def getprotocaps(self):
413 return self._protocaps
413 return self._protocaps
414
414
415 def getpayload(self):
415 def getpayload(self):
416 # We initially send an empty response. This tells the client it is
416 # We initially send an empty response. This tells the client it is
417 # OK to start sending data. If a client sees any other response, it
417 # OK to start sending data. If a client sees any other response, it
418 # interprets it as an error.
418 # interprets it as an error.
419 _sshv1respondbytes(self._fout, b'')
419 _sshv1respondbytes(self._fout, b'')
420
420
421 # The file is in the form:
421 # The file is in the form:
422 #
422 #
423 # <chunk size>\n<chunk>
423 # <chunk size>\n<chunk>
424 # ...
424 # ...
425 # 0\n
425 # 0\n
426 count = int(self._fin.readline())
426 count = int(self._fin.readline())
427 while count:
427 while count:
428 yield self._fin.read(count)
428 yield self._fin.read(count)
429 count = int(self._fin.readline())
429 count = int(self._fin.readline())
430
430
431 @contextlib.contextmanager
431 @contextlib.contextmanager
432 def mayberedirectstdio(self):
432 def mayberedirectstdio(self):
433 yield None
433 yield None
434
434
435 def client(self):
435 def client(self):
436 client = encoding.environ.get(b'SSH_CLIENT', b'').split(b' ', 1)[0]
436 client = encoding.environ.get(b'SSH_CLIENT', b'').split(b' ', 1)[0]
437 return b'remote:ssh:' + client
437 return b'remote:ssh:' + client
438
438
439 def addcapabilities(self, repo, caps):
439 def addcapabilities(self, repo, caps):
440 if self.name == wireprototypes.SSHV1:
440 if self.name == wireprototypes.SSHV1:
441 caps.append(b'protocaps')
441 caps.append(b'protocaps')
442 caps.append(b'batch')
442 caps.append(b'batch')
443 return caps
443 return caps
444
444
445 def checkperm(self, perm):
445 def checkperm(self, perm):
446 pass
446 pass
447
447
448
448
449 def _runsshserver(ui, repo, fin, fout, ev):
449 def _runsshserver(ui, repo, fin, fout, ev, accesshidden=False):
450 # This function operates like a state machine of sorts. The following
450 # This function operates like a state machine of sorts. The following
451 # states are defined:
451 # states are defined:
452 #
452 #
453 # protov1-serving
453 # protov1-serving
454 # Server is in protocol version 1 serving mode. Commands arrive on
454 # Server is in protocol version 1 serving mode. Commands arrive on
455 # new lines. These commands are processed in this state, one command
455 # new lines. These commands are processed in this state, one command
456 # after the other.
456 # after the other.
457 #
457 #
458 # shutdown
458 # shutdown
459 # The server is shutting down, possibly in reaction to a client event.
459 # The server is shutting down, possibly in reaction to a client event.
460 #
460 #
461 # And here are their transitions:
461 # And here are their transitions:
462 #
462 #
463 # protov1-serving -> shutdown
463 # protov1-serving -> shutdown
464 # When server receives an empty request or encounters another
464 # When server receives an empty request or encounters another
465 # error.
465 # error.
466
466
467 state = b'protov1-serving'
467 state = b'protov1-serving'
468 proto = sshv1protocolhandler(ui, fin, fout)
468 proto = sshv1protocolhandler(ui, fin, fout)
469
469
470 while not ev.is_set():
470 while not ev.is_set():
471 if state == b'protov1-serving':
471 if state == b'protov1-serving':
472 # Commands are issued on new lines.
472 # Commands are issued on new lines.
473 request = fin.readline()[:-1]
473 request = fin.readline()[:-1]
474
474
475 # Empty lines signal to terminate the connection.
475 # Empty lines signal to terminate the connection.
476 if not request:
476 if not request:
477 state = b'shutdown'
477 state = b'shutdown'
478 continue
478 continue
479
479
480 available = wireprotov1server.commands.commandavailable(
480 available = wireprotov1server.commands.commandavailable(
481 request, proto
481 request, proto
482 )
482 )
483
483
484 # This command isn't available. Send an empty response and go
484 # This command isn't available. Send an empty response and go
485 # back to waiting for a new command.
485 # back to waiting for a new command.
486 if not available:
486 if not available:
487 _sshv1respondbytes(fout, b'')
487 _sshv1respondbytes(fout, b'')
488 continue
488 continue
489
489
490 rsp = wireprotov1server.dispatch(repo, proto, request)
490 rsp = wireprotov1server.dispatch(
491 repo, proto, request, accesshidden=accesshidden
492 )
491 repo.ui.fout.flush()
493 repo.ui.fout.flush()
492 repo.ui.ferr.flush()
494 repo.ui.ferr.flush()
493
495
494 if isinstance(rsp, bytes):
496 if isinstance(rsp, bytes):
495 _sshv1respondbytes(fout, rsp)
497 _sshv1respondbytes(fout, rsp)
496 elif isinstance(rsp, wireprototypes.bytesresponse):
498 elif isinstance(rsp, wireprototypes.bytesresponse):
497 _sshv1respondbytes(fout, rsp.data)
499 _sshv1respondbytes(fout, rsp.data)
498 elif isinstance(rsp, wireprototypes.streamres):
500 elif isinstance(rsp, wireprototypes.streamres):
499 _sshv1respondstream(fout, rsp)
501 _sshv1respondstream(fout, rsp)
500 elif isinstance(rsp, wireprototypes.streamreslegacy):
502 elif isinstance(rsp, wireprototypes.streamreslegacy):
501 _sshv1respondstream(fout, rsp)
503 _sshv1respondstream(fout, rsp)
502 elif isinstance(rsp, wireprototypes.pushres):
504 elif isinstance(rsp, wireprototypes.pushres):
503 _sshv1respondbytes(fout, b'')
505 _sshv1respondbytes(fout, b'')
504 _sshv1respondbytes(fout, b'%d' % rsp.res)
506 _sshv1respondbytes(fout, b'%d' % rsp.res)
505 elif isinstance(rsp, wireprototypes.pusherr):
507 elif isinstance(rsp, wireprototypes.pusherr):
506 _sshv1respondbytes(fout, rsp.res)
508 _sshv1respondbytes(fout, rsp.res)
507 elif isinstance(rsp, wireprototypes.ooberror):
509 elif isinstance(rsp, wireprototypes.ooberror):
508 _sshv1respondooberror(fout, ui.ferr, rsp.message)
510 _sshv1respondooberror(fout, ui.ferr, rsp.message)
509 else:
511 else:
510 raise error.ProgrammingError(
512 raise error.ProgrammingError(
511 b'unhandled response type from '
513 b'unhandled response type from '
512 b'wire protocol command: %s' % rsp
514 b'wire protocol command: %s' % rsp
513 )
515 )
514
516
515 elif state == b'shutdown':
517 elif state == b'shutdown':
516 break
518 break
517
519
518 else:
520 else:
519 raise error.ProgrammingError(
521 raise error.ProgrammingError(
520 b'unhandled ssh server state: %s' % state
522 b'unhandled ssh server state: %s' % state
521 )
523 )
522
524
523
525
524 class sshserver:
526 class sshserver:
525 def __init__(self, ui, repo, logfh=None):
527 def __init__(self, ui, repo, logfh=None, accesshidden=False):
526 self._ui = ui
528 self._ui = ui
527 self._repo = repo
529 self._repo = repo
528 self._fin, self._fout = ui.protectfinout()
530 self._fin, self._fout = ui.protectfinout()
531 self._accesshidden = accesshidden
529
532
530 # Log write I/O to stdout and stderr if configured.
533 # Log write I/O to stdout and stderr if configured.
531 if logfh:
534 if logfh:
532 self._fout = util.makeloggingfileobject(
535 self._fout = util.makeloggingfileobject(
533 logfh, self._fout, b'o', logdata=True
536 logfh, self._fout, b'o', logdata=True
534 )
537 )
535 ui.ferr = util.makeloggingfileobject(
538 ui.ferr = util.makeloggingfileobject(
536 logfh, ui.ferr, b'e', logdata=True
539 logfh, ui.ferr, b'e', logdata=True
537 )
540 )
538
541
539 def serve_forever(self):
542 def serve_forever(self):
540 self.serveuntil(threading.Event())
543 self.serveuntil(threading.Event())
541 self._ui.restorefinout(self._fin, self._fout)
544 self._ui.restorefinout(self._fin, self._fout)
542
545
543 def serveuntil(self, ev):
546 def serveuntil(self, ev):
544 """Serve until a threading.Event is set."""
547 """Serve until a threading.Event is set."""
545 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
548 _runsshserver(
549 self._ui, self._repo, self._fin, self._fout, ev, self._accesshidden
550 )
@@ -1,312 +1,406 b''
1 ========================================================
1 ========================================================
2 Test the ability to access a hidden revision on a server
2 Test the ability to access a hidden revision on a server
3 ========================================================
3 ========================================================
4
4
5 #require serve
5 #require serve
6
6
7 $ . $TESTDIR/testlib/obsmarker-common.sh
7 $ . $TESTDIR/testlib/obsmarker-common.sh
8 $ cat >> $HGRCPATH << EOF
8 $ cat >> $HGRCPATH << EOF
9 > [ui]
10 > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
9 > [phases]
11 > [phases]
10 > # public changeset are not obsolete
12 > # public changeset are not obsolete
11 > publish=false
13 > publish=false
12 > [experimental]
14 > [experimental]
13 > evolution=all
15 > evolution=all
14 > [ui]
16 > [ui]
15 > logtemplate='{rev}:{node|short} {desc} [{phase}]\n'
17 > logtemplate='{rev}:{node|short} {desc} [{phase}]\n'
16 > EOF
18 > EOF
17
19
18 Setup a simple repository with some hidden revisions
20 Setup a simple repository with some hidden revisions
19 ----------------------------------------------------
21 ----------------------------------------------------
20
22
21 Testing the `served.hidden` view
23 Testing the `served.hidden` view
22
24
23 $ hg init repo-with-hidden
25 $ hg init repo-with-hidden
24 $ cd repo-with-hidden
26 $ cd repo-with-hidden
25
27
26 $ echo 0 > a
28 $ echo 0 > a
27 $ hg ci -qAm "c_Public"
29 $ hg ci -qAm "c_Public"
28 $ hg phase --public
30 $ hg phase --public
29 $ echo 1 > a
31 $ echo 1 > a
30 $ hg ci -m "c_Amend_Old"
32 $ hg ci -m "c_Amend_Old"
31 $ echo 2 > a
33 $ echo 2 > a
32 $ hg ci -m "c_Amend_New" --amend
34 $ hg ci -m "c_Amend_New" --amend
33 $ hg up ".^"
35 $ hg up ".^"
34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 $ echo 3 > a
37 $ echo 3 > a
36 $ hg ci -m "c_Pruned"
38 $ hg ci -m "c_Pruned"
37 created new head
39 created new head
38 $ hg debugobsolete --record-parents `getid 'desc("c_Pruned")'` -d '0 0'
40 $ hg debugobsolete --record-parents `getid 'desc("c_Pruned")'` -d '0 0'
39 1 new obsolescence markers
41 1 new obsolescence markers
40 obsoleted 1 changesets
42 obsoleted 1 changesets
41 $ hg up ".^"
43 $ hg up ".^"
42 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 $ echo 4 > a
45 $ echo 4 > a
44 $ hg ci -m "c_Secret" --secret
46 $ hg ci -m "c_Secret" --secret
45 created new head
47 created new head
46 $ echo 5 > a
48 $ echo 5 > a
47 $ hg ci -m "c_Secret_Pruned" --secret
49 $ hg ci -m "c_Secret_Pruned" --secret
48 $ hg debugobsolete --record-parents `getid 'desc("c_Secret_Pruned")'` -d '0 0'
50 $ hg debugobsolete --record-parents `getid 'desc("c_Secret_Pruned")'` -d '0 0'
49 1 new obsolescence markers
51 1 new obsolescence markers
50 obsoleted 1 changesets
52 obsoleted 1 changesets
51 $ hg up null
53 $ hg up null
52 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
54 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
53
55
54 $ hg log -G -T '{rev}:{node|short} {desc} [{phase}]\n' --hidden
56 $ hg log -G -T '{rev}:{node|short} {desc} [{phase}]\n' --hidden
55 x 5:8d28cbe335f3 c_Secret_Pruned [secret]
57 x 5:8d28cbe335f3 c_Secret_Pruned [secret]
56 |
58 |
57 o 4:1c6afd79eb66 c_Secret [secret]
59 o 4:1c6afd79eb66 c_Secret [secret]
58 |
60 |
59 | x 3:5d1575e42c25 c_Pruned [draft]
61 | x 3:5d1575e42c25 c_Pruned [draft]
60 |/
62 |/
61 | o 2:c33affeb3f6b c_Amend_New [draft]
63 | o 2:c33affeb3f6b c_Amend_New [draft]
62 |/
64 |/
63 | x 1:be215fbb8c50 c_Amend_Old [draft]
65 | x 1:be215fbb8c50 c_Amend_Old [draft]
64 |/
66 |/
65 o 0:5f354f46e585 c_Public [public]
67 o 0:5f354f46e585 c_Public [public]
66
68
67 $ hg debugobsolete
69 $ hg debugobsolete
68 be215fbb8c5090028b00154c1fe877ad1b376c61 c33affeb3f6b4e9621d1839d6175ddc07708807c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'amend', 'user': 'test'}
70 be215fbb8c5090028b00154c1fe877ad1b376c61 c33affeb3f6b4e9621d1839d6175ddc07708807c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'amend', 'user': 'test'}
69 5d1575e42c25b7f2db75cd4e0b881b1c35158fae 0 {5f354f46e5853535841ec7a128423e991ca4d59b} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
71 5d1575e42c25b7f2db75cd4e0b881b1c35158fae 0 {5f354f46e5853535841ec7a128423e991ca4d59b} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
70 8d28cbe335f311bc89332d7bbe8a07889b6914a0 0 {1c6afd79eb6663275bbe30097e162b1c24ced0f0} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
72 8d28cbe335f311bc89332d7bbe8a07889b6914a0 0 {1c6afd79eb6663275bbe30097e162b1c24ced0f0} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
71
73
72 $ cd ..
74 $ cd ..
73
75
74 Test the feature
76 Test the feature
75 ================
77 ================
76
78
77 Check cache pre-warm
79 Check cache pre-warm
78 --------------------
80 --------------------
79
81
80 $ ls -1 repo-with-hidden/.hg/cache
82 $ ls -1 repo-with-hidden/.hg/cache
81 branch2
83 branch2
82 branch2-base
84 branch2-base
83 branch2-served
85 branch2-served
84 branch2-served.hidden
86 branch2-served.hidden
85 branch2-visible
87 branch2-visible
86 rbc-names-v1
88 rbc-names-v1
87 rbc-revs-v1
89 rbc-revs-v1
88 tags2
90 tags2
89 tags2-visible
91 tags2-visible
90
92
91 Check that the `served.hidden` repoview
93 Check that the `served.hidden` repoview
92 ---------------------------------------
94 ---------------------------------------
93
95
94 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config web.view=served.hidden
96 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config web.view=served.hidden
95 $ cat hg.pid >> $DAEMON_PIDS
97 $ cat hg.pid >> $DAEMON_PIDS
96
98
97 changesets in secret and higher phases are not visible through hgweb
99 changesets in secret and higher phases are not visible through hgweb
98
100
99 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())"
101 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())"
100 revision: 2
102 revision: 2
101 revision: 0
103 revision: 0
102 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())" --hidden
104 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())" --hidden
103 revision: 3
105 revision: 3
104 revision: 2
106 revision: 2
105 revision: 1
107 revision: 1
106 revision: 0
108 revision: 0
107 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
109 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
108 revision: 3
110 revision: 3
109 revision: 2
111 revision: 2
110 revision: 1
112 revision: 1
111 revision: 0
113 revision: 0
112
114
113 $ killdaemons.py
115 $ killdaemons.py
114
116
115 Test --remote-hidden for local peer
117 Test --remote-hidden for local peer
116 -----------------------------------
118 -----------------------------------
117
119
118 $ hg clone --pull repo-with-hidden client
120 $ hg clone --pull repo-with-hidden client
119 requesting all changes
121 requesting all changes
120 adding changesets
122 adding changesets
121 adding manifests
123 adding manifests
122 adding file changes
124 adding file changes
123 added 2 changesets with 2 changes to 1 files
125 added 2 changesets with 2 changes to 1 files
124 2 new obsolescence markers
126 2 new obsolescence markers
125 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
127 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
126 updating to branch default
128 updating to branch default
127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 $ hg -R client log -G --hidden -v
130 $ hg -R client log -G --hidden -v
129 @ 1:c33affeb3f6b c_Amend_New [draft]
131 @ 1:c33affeb3f6b c_Amend_New [draft]
130 |
132 |
131 o 0:5f354f46e585 c_Public [public]
133 o 0:5f354f46e585 c_Public [public]
132
134
133
135
134 pulling an hidden changeset should fail:
136 pulling an hidden changeset should fail:
135
137
136 $ hg -R client pull -r be215fbb8c50
138 $ hg -R client pull -r be215fbb8c50
137 pulling from $TESTTMP/repo-with-hidden
139 pulling from $TESTTMP/repo-with-hidden
138 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
140 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
139 [10]
141 [10]
140
142
141 pulling an hidden changeset with --remote-hidden should succeed:
143 pulling an hidden changeset with --remote-hidden should succeed:
142
144
143 $ hg -R client pull --remote-hidden --traceback -r be215fbb8c50
145 $ hg -R client pull --remote-hidden --traceback -r be215fbb8c50
144 pulling from $TESTTMP/repo-with-hidden
146 pulling from $TESTTMP/repo-with-hidden
145 searching for changes
147 searching for changes
146 adding changesets
148 adding changesets
147 adding manifests
149 adding manifests
148 adding file changes
150 adding file changes
149 added 1 changesets with 1 changes to 1 files (+1 heads)
151 added 1 changesets with 1 changes to 1 files (+1 heads)
150 (1 other changesets obsolete on arrival)
152 (1 other changesets obsolete on arrival)
151 (run 'hg heads' to see heads)
153 (run 'hg heads' to see heads)
152 $ hg -R client log -G --hidden -v
154 $ hg -R client log -G --hidden -v
153 x 2:be215fbb8c50 c_Amend_Old [draft]
155 x 2:be215fbb8c50 c_Amend_Old [draft]
154 |
156 |
155 | @ 1:c33affeb3f6b c_Amend_New [draft]
157 | @ 1:c33affeb3f6b c_Amend_New [draft]
156 |/
158 |/
157 o 0:5f354f46e585 c_Public [public]
159 o 0:5f354f46e585 c_Public [public]
158
160
159
161
160 Pulling a secret changeset is still forbidden:
162 Pulling a secret changeset is still forbidden:
161
163
162 secret visible:
164 secret visible:
163
165
164 $ hg -R client pull --remote-hidden -r 8d28cbe335f3
166 $ hg -R client pull --remote-hidden -r 8d28cbe335f3
165 pulling from $TESTTMP/repo-with-hidden
167 pulling from $TESTTMP/repo-with-hidden
166 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
168 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
167 [10]
169 [10]
168
170
169 secret hidden:
171 secret hidden:
170
172
171 $ hg -R client pull --remote-hidden -r 1c6afd79eb66
173 $ hg -R client pull --remote-hidden -r 1c6afd79eb66
172 pulling from $TESTTMP/repo-with-hidden
174 pulling from $TESTTMP/repo-with-hidden
173 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
175 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
174 [10]
176 [10]
175
177
176 Test accessing hidden changeset through hgweb
178 Test accessing hidden changeset through hgweb
177 ---------------------------------------------
179 ---------------------------------------------
178
180
179 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config "experimental.server.allow-hidden-access=*" -E error.log --accesslog access.log
181 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config "experimental.server.allow-hidden-access=*" -E error.log --accesslog access.log
180 $ cat hg.pid >> $DAEMON_PIDS
182 $ cat hg.pid >> $DAEMON_PIDS
181
183
182 Hidden changeset are hidden by default:
184 Hidden changeset are hidden by default:
183
185
184 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
186 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
185 revision: 2
187 revision: 2
186 revision: 0
188 revision: 0
187
189
188 Hidden changeset are visible when requested:
190 Hidden changeset are visible when requested:
189
191
190 $ get-with-headers.py localhost:$HGPORT 'log?style=raw&access-hidden=1' | grep revision:
192 $ get-with-headers.py localhost:$HGPORT 'log?style=raw&access-hidden=1' | grep revision:
191 revision: 3
193 revision: 3
192 revision: 2
194 revision: 2
193 revision: 1
195 revision: 1
194 revision: 0
196 revision: 0
195
197
196 Same check on a server that do not allow hidden access:
198 Same check on a server that do not allow hidden access:
197 ```````````````````````````````````````````````````````
199 ```````````````````````````````````````````````````````
198
200
199 $ hg -R repo-with-hidden serve -p $HGPORT1 -d --pid-file hg2.pid --config "experimental.server.allow-hidden-access=" -E error.log --accesslog access.log
201 $ hg -R repo-with-hidden serve -p $HGPORT1 -d --pid-file hg2.pid --config "experimental.server.allow-hidden-access=" -E error.log --accesslog access.log
200 $ cat hg2.pid >> $DAEMON_PIDS
202 $ cat hg2.pid >> $DAEMON_PIDS
201
203
202 Hidden changeset are hidden by default:
204 Hidden changeset are hidden by default:
203
205
204 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw' | grep revision:
206 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw' | grep revision:
205 revision: 2
207 revision: 2
206 revision: 0
208 revision: 0
207
209
208 Hidden changeset are still hidden despite being the hidden access request:
210 Hidden changeset are still hidden despite being the hidden access request:
209
211
210 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw&access-hidden=1' | grep revision:
212 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw&access-hidden=1' | grep revision:
211 revision: 2
213 revision: 2
212 revision: 0
214 revision: 0
213
215
214 Test --remote-hidden for http peer
216 Test --remote-hidden for http peer
215 ----------------------------------
217 ----------------------------------
216
218
217 $ hg clone --pull http://localhost:$HGPORT client-http
219 $ hg clone --pull http://localhost:$HGPORT client-http
218 requesting all changes
220 requesting all changes
219 adding changesets
221 adding changesets
220 adding manifests
222 adding manifests
221 adding file changes
223 adding file changes
222 added 2 changesets with 2 changes to 1 files
224 added 2 changesets with 2 changes to 1 files
223 2 new obsolescence markers
225 2 new obsolescence markers
224 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
226 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
225 updating to branch default
227 updating to branch default
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 $ hg -R client-http log -G --hidden -v
229 $ hg -R client-http log -G --hidden -v
228 @ 1:c33affeb3f6b c_Amend_New [draft]
230 @ 1:c33affeb3f6b c_Amend_New [draft]
229 |
231 |
230 o 0:5f354f46e585 c_Public [public]
232 o 0:5f354f46e585 c_Public [public]
231
233
232
234
233 pulling an hidden changeset should fail:
235 pulling an hidden changeset should fail:
234
236
235 $ hg -R client-http pull -r be215fbb8c50
237 $ hg -R client-http pull -r be215fbb8c50
236 pulling from http://localhost:$HGPORT/
238 pulling from http://localhost:$HGPORT/
237 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
239 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
238 [255]
240 [255]
239
241
240 pulling an hidden changeset with --remote-hidden should succeed:
242 pulling an hidden changeset with --remote-hidden should succeed:
241
243
242 $ hg -R client-http pull --remote-hidden -r be215fbb8c50
244 $ hg -R client-http pull --remote-hidden -r be215fbb8c50
243 pulling from http://localhost:$HGPORT/
245 pulling from http://localhost:$HGPORT/
244 searching for changes
246 searching for changes
245 adding changesets
247 adding changesets
246 adding manifests
248 adding manifests
247 adding file changes
249 adding file changes
248 added 1 changesets with 1 changes to 1 files (+1 heads)
250 added 1 changesets with 1 changes to 1 files (+1 heads)
249 (1 other changesets obsolete on arrival)
251 (1 other changesets obsolete on arrival)
250 (run 'hg heads' to see heads)
252 (run 'hg heads' to see heads)
251 $ hg -R client-http log -G --hidden -v
253 $ hg -R client-http log -G --hidden -v
252 x 2:be215fbb8c50 c_Amend_Old [draft]
254 x 2:be215fbb8c50 c_Amend_Old [draft]
253 |
255 |
254 | @ 1:c33affeb3f6b c_Amend_New [draft]
256 | @ 1:c33affeb3f6b c_Amend_New [draft]
255 |/
257 |/
256 o 0:5f354f46e585 c_Public [public]
258 o 0:5f354f46e585 c_Public [public]
257
259
258
260
259 Pulling a secret changeset is still forbidden:
261 Pulling a secret changeset is still forbidden:
260
262
261 secret visible:
263 secret visible:
262
264
263 $ hg -R client-http pull --remote-hidden -r 8d28cbe335f3
265 $ hg -R client-http pull --remote-hidden -r 8d28cbe335f3
264 pulling from http://localhost:$HGPORT/
266 pulling from http://localhost:$HGPORT/
265 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
267 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
266 [255]
268 [255]
267
269
268 secret hidden:
270 secret hidden:
269
271
270 $ hg -R client-http pull --remote-hidden -r 1c6afd79eb66
272 $ hg -R client-http pull --remote-hidden -r 1c6afd79eb66
271 pulling from http://localhost:$HGPORT/
273 pulling from http://localhost:$HGPORT/
272 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
274 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
273 [255]
275 [255]
274
276
275 Same check on a server that do not allow hidden access:
277 Same check on a server that do not allow hidden access:
276 ```````````````````````````````````````````````````````
278 ```````````````````````````````````````````````````````
277
279
278 $ hg clone --pull http://localhost:$HGPORT1 client-http2
280 $ hg clone --pull http://localhost:$HGPORT1 client-http2
279 requesting all changes
281 requesting all changes
280 adding changesets
282 adding changesets
281 adding manifests
283 adding manifests
282 adding file changes
284 adding file changes
283 added 2 changesets with 2 changes to 1 files
285 added 2 changesets with 2 changes to 1 files
284 2 new obsolescence markers
286 2 new obsolescence markers
285 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
287 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
286 updating to branch default
288 updating to branch default
287 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 $ hg -R client-http2 log -G --hidden -v
290 $ hg -R client-http2 log -G --hidden -v
289 @ 1:c33affeb3f6b c_Amend_New [draft]
291 @ 1:c33affeb3f6b c_Amend_New [draft]
290 |
292 |
291 o 0:5f354f46e585 c_Public [public]
293 o 0:5f354f46e585 c_Public [public]
292
294
293
295
294 pulling an hidden changeset should fail:
296 pulling an hidden changeset should fail:
295
297
296 $ hg -R client-http2 pull -r be215fbb8c50
298 $ hg -R client-http2 pull -r be215fbb8c50
297 pulling from http://localhost:$HGPORT1/
299 pulling from http://localhost:$HGPORT1/
298 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
300 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
299 [255]
301 [255]
300
302
301 pulling an hidden changeset with --remote-hidden should fail too:
303 pulling an hidden changeset with --remote-hidden should fail too:
302
304
303 $ hg -R client-http2 pull --remote-hidden -r be215fbb8c50
305 $ hg -R client-http2 pull --remote-hidden -r be215fbb8c50
304 pulling from http://localhost:$HGPORT1/
306 pulling from http://localhost:$HGPORT1/
305 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
307 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
306 [255]
308 [255]
307
309
310 Test --remote-hidden for ssh peer
311 ----------------------------------
312
313 $ hg clone --pull ssh://user@dummy/repo-with-hidden client-ssh
314 requesting all changes
315 adding changesets
316 adding manifests
317 adding file changes
318 added 2 changesets with 2 changes to 1 files
319 2 new obsolescence markers
320 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
321 updating to branch default
322 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
323 $ hg -R client-ssh log -G --hidden -v
324 @ 1:c33affeb3f6b c_Amend_New [draft]
325 |
326 o 0:5f354f46e585 c_Public [public]
327
328
329 Check on a server that do not allow hidden access:
330 ``````````````````````````````````````````````````
331
332 pulling an hidden changeset should fail:
333
334 $ hg -R client-ssh pull -r be215fbb8c50
335 pulling from ssh://user@dummy/repo-with-hidden
336 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
337 [255]
338
339 pulling an hidden changeset with --remote-hidden should succeed:
340
341 $ hg -R client-ssh pull --remote-hidden -r be215fbb8c50
342 pulling from ssh://user@dummy/repo-with-hidden
343 remote: ignoring request to access hidden changeset by unauthorized user: * (glob)
344 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
345 [255]
346 $ hg -R client-ssh log -G --hidden -v
347 @ 1:c33affeb3f6b c_Amend_New [draft]
348 |
349 o 0:5f354f46e585 c_Public [public]
350
351
352 Check on a server that do allow hidden access:
353 ``````````````````````````````````````````````
354
355 $ cat << EOF >> repo-with-hidden/.hg/hgrc
356 > [experimental]
357 > server.allow-hidden-access=*
358 > EOF
359
360 pulling an hidden changeset should fail:
361
362 $ hg -R client-ssh pull -r be215fbb8c50
363 pulling from ssh://user@dummy/repo-with-hidden
364 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
365 [255]
366
367 pulling an hidden changeset with --remote-hidden should succeed:
368
369 $ hg -R client-ssh pull --remote-hidden -r be215fbb8c50
370 pulling from ssh://user@dummy/repo-with-hidden
371 searching for changes
372 adding changesets
373 adding manifests
374 adding file changes
375 added 1 changesets with 1 changes to 1 files (+1 heads)
376 (1 other changesets obsolete on arrival)
377 (run 'hg heads' to see heads)
378 $ hg -R client-ssh log -G --hidden -v
379 x 2:be215fbb8c50 c_Amend_Old [draft]
380 |
381 | @ 1:c33affeb3f6b c_Amend_New [draft]
382 |/
383 o 0:5f354f46e585 c_Public [public]
384
385
386 Pulling a secret changeset is still forbidden:
387
388 secret visible:
389
390 $ hg -R client-ssh pull --remote-hidden -r 8d28cbe335f3
391 pulling from ssh://user@dummy/repo-with-hidden
392 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
393 [255]
394
395 secret hidden:
396
397 $ hg -R client-ssh pull --remote-hidden -r 1c6afd79eb66
398 pulling from ssh://user@dummy/repo-with-hidden
399 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
400 [255]
401
308 =============
402 =============
309 Final cleanup
403 Final cleanup
310 =============
404 =============
311
405
312 $ killdaemons.py
406 $ killdaemons.py
General Comments 0
You need to be logged in to leave comments. Login now