##// END OF EJS Templates
merge: add a back_out() function to encapsulate update()...
Martin von Zweigbergk -
r46117:2b339c6c default
parent child Browse files
Show More
@@ -1,7814 +1,7808 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from .pycompat import open
25 from .pycompat import open
26 from . import (
26 from . import (
27 archival,
27 archival,
28 bookmarks,
28 bookmarks,
29 bundle2,
29 bundle2,
30 changegroup,
30 changegroup,
31 cmdutil,
31 cmdutil,
32 copies,
32 copies,
33 debugcommands as debugcommandsmod,
33 debugcommands as debugcommandsmod,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filemerge,
41 filemerge,
42 formatter,
42 formatter,
43 graphmod,
43 graphmod,
44 hbisect,
44 hbisect,
45 help,
45 help,
46 hg,
46 hg,
47 logcmdutil,
47 logcmdutil,
48 merge as mergemod,
48 merge as mergemod,
49 mergestate as mergestatemod,
49 mergestate as mergestatemod,
50 narrowspec,
50 narrowspec,
51 obsolete,
51 obsolete,
52 obsutil,
52 obsutil,
53 patch,
53 patch,
54 phases,
54 phases,
55 pycompat,
55 pycompat,
56 rcutil,
56 rcutil,
57 registrar,
57 registrar,
58 requirements,
58 requirements,
59 revsetlang,
59 revsetlang,
60 rewriteutil,
60 rewriteutil,
61 scmutil,
61 scmutil,
62 server,
62 server,
63 shelve as shelvemod,
63 shelve as shelvemod,
64 state as statemod,
64 state as statemod,
65 streamclone,
65 streamclone,
66 tags as tagsmod,
66 tags as tagsmod,
67 ui as uimod,
67 ui as uimod,
68 util,
68 util,
69 verify as verifymod,
69 verify as verifymod,
70 vfs as vfsmod,
70 vfs as vfsmod,
71 wireprotoserver,
71 wireprotoserver,
72 )
72 )
73 from .utils import (
73 from .utils import (
74 dateutil,
74 dateutil,
75 stringutil,
75 stringutil,
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.Abort(_(b'no operation in progress'))
189 raise error.Abort(_(b'no operation in progress'))
190 if not abortstate.abortfunc:
190 if not abortstate.abortfunc:
191 raise error.Abort(
191 raise error.Abort(
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.Abort(_(b'at least one filename or pattern is required'))
420 raise error.Abort(_(b'at least one filename or pattern is required'))
421
421
422 if opts.get(b'follow'):
422 if opts.get(b'follow'):
423 # --follow is deprecated and now just an alias for -f/--file
423 # --follow is deprecated and now just an alias for -f/--file
424 # to mimic the behavior of Mercurial before version 1.5
424 # to mimic the behavior of Mercurial before version 1.5
425 opts[b'file'] = True
425 opts[b'file'] = True
426
426
427 if (
427 if (
428 not opts.get(b'user')
428 not opts.get(b'user')
429 and not opts.get(b'changeset')
429 and not opts.get(b'changeset')
430 and not opts.get(b'date')
430 and not opts.get(b'date')
431 and not opts.get(b'file')
431 and not opts.get(b'file')
432 ):
432 ):
433 opts[b'number'] = True
433 opts[b'number'] = True
434
434
435 linenumber = opts.get(b'line_number') is not None
435 linenumber = opts.get(b'line_number') is not None
436 if (
436 if (
437 linenumber
437 linenumber
438 and (not opts.get(b'changeset'))
438 and (not opts.get(b'changeset'))
439 and (not opts.get(b'number'))
439 and (not opts.get(b'number'))
440 ):
440 ):
441 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
441 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
442
442
443 rev = opts.get(b'rev')
443 rev = opts.get(b'rev')
444 if rev:
444 if rev:
445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
446 ctx = scmutil.revsingle(repo, rev)
446 ctx = scmutil.revsingle(repo, rev)
447
447
448 ui.pager(b'annotate')
448 ui.pager(b'annotate')
449 rootfm = ui.formatter(b'annotate', opts)
449 rootfm = ui.formatter(b'annotate', opts)
450 if ui.debugflag:
450 if ui.debugflag:
451 shorthex = pycompat.identity
451 shorthex = pycompat.identity
452 else:
452 else:
453
453
454 def shorthex(h):
454 def shorthex(h):
455 return h[:12]
455 return h[:12]
456
456
457 if ui.quiet:
457 if ui.quiet:
458 datefunc = dateutil.shortdate
458 datefunc = dateutil.shortdate
459 else:
459 else:
460 datefunc = dateutil.datestr
460 datefunc = dateutil.datestr
461 if ctx.rev() is None:
461 if ctx.rev() is None:
462 if opts.get(b'changeset'):
462 if opts.get(b'changeset'):
463 # omit "+" suffix which is appended to node hex
463 # omit "+" suffix which is appended to node hex
464 def formatrev(rev):
464 def formatrev(rev):
465 if rev == wdirrev:
465 if rev == wdirrev:
466 return b'%d' % ctx.p1().rev()
466 return b'%d' % ctx.p1().rev()
467 else:
467 else:
468 return b'%d' % rev
468 return b'%d' % rev
469
469
470 else:
470 else:
471
471
472 def formatrev(rev):
472 def formatrev(rev):
473 if rev == wdirrev:
473 if rev == wdirrev:
474 return b'%d+' % ctx.p1().rev()
474 return b'%d+' % ctx.p1().rev()
475 else:
475 else:
476 return b'%d ' % rev
476 return b'%d ' % rev
477
477
478 def formathex(h):
478 def formathex(h):
479 if h == wdirhex:
479 if h == wdirhex:
480 return b'%s+' % shorthex(hex(ctx.p1().node()))
480 return b'%s+' % shorthex(hex(ctx.p1().node()))
481 else:
481 else:
482 return b'%s ' % shorthex(h)
482 return b'%s ' % shorthex(h)
483
483
484 else:
484 else:
485 formatrev = b'%d'.__mod__
485 formatrev = b'%d'.__mod__
486 formathex = shorthex
486 formathex = shorthex
487
487
488 opmap = [
488 opmap = [
489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
495 ]
495 ]
496 opnamemap = {
496 opnamemap = {
497 b'rev': b'number',
497 b'rev': b'number',
498 b'node': b'changeset',
498 b'node': b'changeset',
499 b'path': b'file',
499 b'path': b'file',
500 b'lineno': b'line_number',
500 b'lineno': b'line_number',
501 }
501 }
502
502
503 if rootfm.isplain():
503 if rootfm.isplain():
504
504
505 def makefunc(get, fmt):
505 def makefunc(get, fmt):
506 return lambda x: fmt(get(x))
506 return lambda x: fmt(get(x))
507
507
508 else:
508 else:
509
509
510 def makefunc(get, fmt):
510 def makefunc(get, fmt):
511 return get
511 return get
512
512
513 datahint = rootfm.datahint()
513 datahint = rootfm.datahint()
514 funcmap = [
514 funcmap = [
515 (makefunc(get, fmt), sep)
515 (makefunc(get, fmt), sep)
516 for fn, sep, get, fmt in opmap
516 for fn, sep, get, fmt in opmap
517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
518 ]
518 ]
519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
520 fields = b' '.join(
520 fields = b' '.join(
521 fn
521 fn
522 for fn, sep, get, fmt in opmap
522 for fn, sep, get, fmt in opmap
523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
524 )
524 )
525
525
526 def bad(x, y):
526 def bad(x, y):
527 raise error.Abort(b"%s: %s" % (x, y))
527 raise error.Abort(b"%s: %s" % (x, y))
528
528
529 m = scmutil.match(ctx, pats, opts, badfn=bad)
529 m = scmutil.match(ctx, pats, opts, badfn=bad)
530
530
531 follow = not opts.get(b'no_follow')
531 follow = not opts.get(b'no_follow')
532 diffopts = patch.difffeatureopts(
532 diffopts = patch.difffeatureopts(
533 ui, opts, section=b'annotate', whitespace=True
533 ui, opts, section=b'annotate', whitespace=True
534 )
534 )
535 skiprevs = opts.get(b'skip')
535 skiprevs = opts.get(b'skip')
536 if skiprevs:
536 if skiprevs:
537 skiprevs = scmutil.revrange(repo, skiprevs)
537 skiprevs = scmutil.revrange(repo, skiprevs)
538
538
539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
540 for abs in ctx.walk(m):
540 for abs in ctx.walk(m):
541 fctx = ctx[abs]
541 fctx = ctx[abs]
542 rootfm.startitem()
542 rootfm.startitem()
543 rootfm.data(path=abs)
543 rootfm.data(path=abs)
544 if not opts.get(b'text') and fctx.isbinary():
544 if not opts.get(b'text') and fctx.isbinary():
545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
546 continue
546 continue
547
547
548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
549 lines = fctx.annotate(
549 lines = fctx.annotate(
550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
551 )
551 )
552 if not lines:
552 if not lines:
553 fm.end()
553 fm.end()
554 continue
554 continue
555 formats = []
555 formats = []
556 pieces = []
556 pieces = []
557
557
558 for f, sep in funcmap:
558 for f, sep in funcmap:
559 l = [f(n) for n in lines]
559 l = [f(n) for n in lines]
560 if fm.isplain():
560 if fm.isplain():
561 sizes = [encoding.colwidth(x) for x in l]
561 sizes = [encoding.colwidth(x) for x in l]
562 ml = max(sizes)
562 ml = max(sizes)
563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
564 else:
564 else:
565 formats.append([b'%s'] * len(l))
565 formats.append([b'%s'] * len(l))
566 pieces.append(l)
566 pieces.append(l)
567
567
568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
569 fm.startitem()
569 fm.startitem()
570 fm.context(fctx=n.fctx)
570 fm.context(fctx=n.fctx)
571 fm.write(fields, b"".join(f), *p)
571 fm.write(fields, b"".join(f), *p)
572 if n.skip:
572 if n.skip:
573 fmt = b"* %s"
573 fmt = b"* %s"
574 else:
574 else:
575 fmt = b": %s"
575 fmt = b": %s"
576 fm.write(b'line', fmt, n.text)
576 fm.write(b'line', fmt, n.text)
577
577
578 if not lines[-1].text.endswith(b'\n'):
578 if not lines[-1].text.endswith(b'\n'):
579 fm.plain(b'\n')
579 fm.plain(b'\n')
580 fm.end()
580 fm.end()
581
581
582 rootfm.end()
582 rootfm.end()
583
583
584
584
585 @command(
585 @command(
586 b'archive',
586 b'archive',
587 [
587 [
588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
589 (
589 (
590 b'p',
590 b'p',
591 b'prefix',
591 b'prefix',
592 b'',
592 b'',
593 _(b'directory prefix for files in archive'),
593 _(b'directory prefix for files in archive'),
594 _(b'PREFIX'),
594 _(b'PREFIX'),
595 ),
595 ),
596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
598 ]
598 ]
599 + subrepoopts
599 + subrepoopts
600 + walkopts,
600 + walkopts,
601 _(b'[OPTION]... DEST'),
601 _(b'[OPTION]... DEST'),
602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
603 )
603 )
604 def archive(ui, repo, dest, **opts):
604 def archive(ui, repo, dest, **opts):
605 '''create an unversioned archive of a repository revision
605 '''create an unversioned archive of a repository revision
606
606
607 By default, the revision used is the parent of the working
607 By default, the revision used is the parent of the working
608 directory; use -r/--rev to specify a different revision.
608 directory; use -r/--rev to specify a different revision.
609
609
610 The archive type is automatically detected based on file
610 The archive type is automatically detected based on file
611 extension (to override, use -t/--type).
611 extension (to override, use -t/--type).
612
612
613 .. container:: verbose
613 .. container:: verbose
614
614
615 Examples:
615 Examples:
616
616
617 - create a zip file containing the 1.0 release::
617 - create a zip file containing the 1.0 release::
618
618
619 hg archive -r 1.0 project-1.0.zip
619 hg archive -r 1.0 project-1.0.zip
620
620
621 - create a tarball excluding .hg files::
621 - create a tarball excluding .hg files::
622
622
623 hg archive project.tar.gz -X ".hg*"
623 hg archive project.tar.gz -X ".hg*"
624
624
625 Valid types are:
625 Valid types are:
626
626
627 :``files``: a directory full of files (default)
627 :``files``: a directory full of files (default)
628 :``tar``: tar archive, uncompressed
628 :``tar``: tar archive, uncompressed
629 :``tbz2``: tar archive, compressed using bzip2
629 :``tbz2``: tar archive, compressed using bzip2
630 :``tgz``: tar archive, compressed using gzip
630 :``tgz``: tar archive, compressed using gzip
631 :``txz``: tar archive, compressed using lzma (only in Python 3)
631 :``txz``: tar archive, compressed using lzma (only in Python 3)
632 :``uzip``: zip archive, uncompressed
632 :``uzip``: zip archive, uncompressed
633 :``zip``: zip archive, compressed using deflate
633 :``zip``: zip archive, compressed using deflate
634
634
635 The exact name of the destination archive or directory is given
635 The exact name of the destination archive or directory is given
636 using a format string; see :hg:`help export` for details.
636 using a format string; see :hg:`help export` for details.
637
637
638 Each member added to an archive file has a directory prefix
638 Each member added to an archive file has a directory prefix
639 prepended. Use -p/--prefix to specify a format string for the
639 prepended. Use -p/--prefix to specify a format string for the
640 prefix. The default is the basename of the archive, with suffixes
640 prefix. The default is the basename of the archive, with suffixes
641 removed.
641 removed.
642
642
643 Returns 0 on success.
643 Returns 0 on success.
644 '''
644 '''
645
645
646 opts = pycompat.byteskwargs(opts)
646 opts = pycompat.byteskwargs(opts)
647 rev = opts.get(b'rev')
647 rev = opts.get(b'rev')
648 if rev:
648 if rev:
649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
650 ctx = scmutil.revsingle(repo, rev)
650 ctx = scmutil.revsingle(repo, rev)
651 if not ctx:
651 if not ctx:
652 raise error.Abort(_(b'no working directory: please specify a revision'))
652 raise error.Abort(_(b'no working directory: please specify a revision'))
653 node = ctx.node()
653 node = ctx.node()
654 dest = cmdutil.makefilename(ctx, dest)
654 dest = cmdutil.makefilename(ctx, dest)
655 if os.path.realpath(dest) == repo.root:
655 if os.path.realpath(dest) == repo.root:
656 raise error.Abort(_(b'repository root cannot be destination'))
656 raise error.Abort(_(b'repository root cannot be destination'))
657
657
658 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
658 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
659 prefix = opts.get(b'prefix')
659 prefix = opts.get(b'prefix')
660
660
661 if dest == b'-':
661 if dest == b'-':
662 if kind == b'files':
662 if kind == b'files':
663 raise error.Abort(_(b'cannot archive plain files to stdout'))
663 raise error.Abort(_(b'cannot archive plain files to stdout'))
664 dest = cmdutil.makefileobj(ctx, dest)
664 dest = cmdutil.makefileobj(ctx, dest)
665 if not prefix:
665 if not prefix:
666 prefix = os.path.basename(repo.root) + b'-%h'
666 prefix = os.path.basename(repo.root) + b'-%h'
667
667
668 prefix = cmdutil.makefilename(ctx, prefix)
668 prefix = cmdutil.makefilename(ctx, prefix)
669 match = scmutil.match(ctx, [], opts)
669 match = scmutil.match(ctx, [], opts)
670 archival.archive(
670 archival.archive(
671 repo,
671 repo,
672 dest,
672 dest,
673 node,
673 node,
674 kind,
674 kind,
675 not opts.get(b'no_decode'),
675 not opts.get(b'no_decode'),
676 match,
676 match,
677 prefix,
677 prefix,
678 subrepos=opts.get(b'subrepos'),
678 subrepos=opts.get(b'subrepos'),
679 )
679 )
680
680
681
681
682 @command(
682 @command(
683 b'backout',
683 b'backout',
684 [
684 [
685 (
685 (
686 b'',
686 b'',
687 b'merge',
687 b'merge',
688 None,
688 None,
689 _(b'merge with old dirstate parent after backout'),
689 _(b'merge with old dirstate parent after backout'),
690 ),
690 ),
691 (
691 (
692 b'',
692 b'',
693 b'commit',
693 b'commit',
694 None,
694 None,
695 _(b'commit if no conflicts were encountered (DEPRECATED)'),
695 _(b'commit if no conflicts were encountered (DEPRECATED)'),
696 ),
696 ),
697 (b'', b'no-commit', None, _(b'do not commit')),
697 (b'', b'no-commit', None, _(b'do not commit')),
698 (
698 (
699 b'',
699 b'',
700 b'parent',
700 b'parent',
701 b'',
701 b'',
702 _(b'parent to choose when backing out merge (DEPRECATED)'),
702 _(b'parent to choose when backing out merge (DEPRECATED)'),
703 _(b'REV'),
703 _(b'REV'),
704 ),
704 ),
705 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
705 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
706 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
706 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
707 ]
707 ]
708 + mergetoolopts
708 + mergetoolopts
709 + walkopts
709 + walkopts
710 + commitopts
710 + commitopts
711 + commitopts2,
711 + commitopts2,
712 _(b'[OPTION]... [-r] REV'),
712 _(b'[OPTION]... [-r] REV'),
713 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
713 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
714 )
714 )
715 def backout(ui, repo, node=None, rev=None, **opts):
715 def backout(ui, repo, node=None, rev=None, **opts):
716 '''reverse effect of earlier changeset
716 '''reverse effect of earlier changeset
717
717
718 Prepare a new changeset with the effect of REV undone in the
718 Prepare a new changeset with the effect of REV undone in the
719 current working directory. If no conflicts were encountered,
719 current working directory. If no conflicts were encountered,
720 it will be committed immediately.
720 it will be committed immediately.
721
721
722 If REV is the parent of the working directory, then this new changeset
722 If REV is the parent of the working directory, then this new changeset
723 is committed automatically (unless --no-commit is specified).
723 is committed automatically (unless --no-commit is specified).
724
724
725 .. note::
725 .. note::
726
726
727 :hg:`backout` cannot be used to fix either an unwanted or
727 :hg:`backout` cannot be used to fix either an unwanted or
728 incorrect merge.
728 incorrect merge.
729
729
730 .. container:: verbose
730 .. container:: verbose
731
731
732 Examples:
732 Examples:
733
733
734 - Reverse the effect of the parent of the working directory.
734 - Reverse the effect of the parent of the working directory.
735 This backout will be committed immediately::
735 This backout will be committed immediately::
736
736
737 hg backout -r .
737 hg backout -r .
738
738
739 - Reverse the effect of previous bad revision 23::
739 - Reverse the effect of previous bad revision 23::
740
740
741 hg backout -r 23
741 hg backout -r 23
742
742
743 - Reverse the effect of previous bad revision 23 and
743 - Reverse the effect of previous bad revision 23 and
744 leave changes uncommitted::
744 leave changes uncommitted::
745
745
746 hg backout -r 23 --no-commit
746 hg backout -r 23 --no-commit
747 hg commit -m "Backout revision 23"
747 hg commit -m "Backout revision 23"
748
748
749 By default, the pending changeset will have one parent,
749 By default, the pending changeset will have one parent,
750 maintaining a linear history. With --merge, the pending
750 maintaining a linear history. With --merge, the pending
751 changeset will instead have two parents: the old parent of the
751 changeset will instead have two parents: the old parent of the
752 working directory and a new child of REV that simply undoes REV.
752 working directory and a new child of REV that simply undoes REV.
753
753
754 Before version 1.7, the behavior without --merge was equivalent
754 Before version 1.7, the behavior without --merge was equivalent
755 to specifying --merge followed by :hg:`update --clean .` to
755 to specifying --merge followed by :hg:`update --clean .` to
756 cancel the merge and leave the child of REV as a head to be
756 cancel the merge and leave the child of REV as a head to be
757 merged separately.
757 merged separately.
758
758
759 See :hg:`help dates` for a list of formats valid for -d/--date.
759 See :hg:`help dates` for a list of formats valid for -d/--date.
760
760
761 See :hg:`help revert` for a way to restore files to the state
761 See :hg:`help revert` for a way to restore files to the state
762 of another revision.
762 of another revision.
763
763
764 Returns 0 on success, 1 if nothing to backout or there are unresolved
764 Returns 0 on success, 1 if nothing to backout or there are unresolved
765 files.
765 files.
766 '''
766 '''
767 with repo.wlock(), repo.lock():
767 with repo.wlock(), repo.lock():
768 return _dobackout(ui, repo, node, rev, **opts)
768 return _dobackout(ui, repo, node, rev, **opts)
769
769
770
770
771 def _dobackout(ui, repo, node=None, rev=None, **opts):
771 def _dobackout(ui, repo, node=None, rev=None, **opts):
772 opts = pycompat.byteskwargs(opts)
772 opts = pycompat.byteskwargs(opts)
773 if opts.get(b'commit') and opts.get(b'no_commit'):
773 if opts.get(b'commit') and opts.get(b'no_commit'):
774 raise error.Abort(_(b"cannot use --commit with --no-commit"))
774 raise error.Abort(_(b"cannot use --commit with --no-commit"))
775 if opts.get(b'merge') and opts.get(b'no_commit'):
775 if opts.get(b'merge') and opts.get(b'no_commit'):
776 raise error.Abort(_(b"cannot use --merge with --no-commit"))
776 raise error.Abort(_(b"cannot use --merge with --no-commit"))
777
777
778 if rev and node:
778 if rev and node:
779 raise error.Abort(_(b"please specify just one revision"))
779 raise error.Abort(_(b"please specify just one revision"))
780
780
781 if not rev:
781 if not rev:
782 rev = node
782 rev = node
783
783
784 if not rev:
784 if not rev:
785 raise error.Abort(_(b"please specify a revision to backout"))
785 raise error.Abort(_(b"please specify a revision to backout"))
786
786
787 date = opts.get(b'date')
787 date = opts.get(b'date')
788 if date:
788 if date:
789 opts[b'date'] = dateutil.parsedate(date)
789 opts[b'date'] = dateutil.parsedate(date)
790
790
791 cmdutil.checkunfinished(repo)
791 cmdutil.checkunfinished(repo)
792 cmdutil.bailifchanged(repo)
792 cmdutil.bailifchanged(repo)
793 node = scmutil.revsingle(repo, rev).node()
793 ctx = scmutil.revsingle(repo, rev)
794 node = ctx.node()
794
795
795 op1, op2 = repo.dirstate.parents()
796 op1, op2 = repo.dirstate.parents()
796 if not repo.changelog.isancestor(node, op1):
797 if not repo.changelog.isancestor(node, op1):
797 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
798 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
798
799
799 p1, p2 = repo.changelog.parents(node)
800 p1, p2 = repo.changelog.parents(node)
800 if p1 == nullid:
801 if p1 == nullid:
801 raise error.Abort(_(b'cannot backout a change with no parents'))
802 raise error.Abort(_(b'cannot backout a change with no parents'))
802 if p2 != nullid:
803 if p2 != nullid:
803 if not opts.get(b'parent'):
804 if not opts.get(b'parent'):
804 raise error.Abort(_(b'cannot backout a merge changeset'))
805 raise error.Abort(_(b'cannot backout a merge changeset'))
805 p = repo.lookup(opts[b'parent'])
806 p = repo.lookup(opts[b'parent'])
806 if p not in (p1, p2):
807 if p not in (p1, p2):
807 raise error.Abort(
808 raise error.Abort(
808 _(b'%s is not a parent of %s') % (short(p), short(node))
809 _(b'%s is not a parent of %s') % (short(p), short(node))
809 )
810 )
810 parent = p
811 parent = p
811 else:
812 else:
812 if opts.get(b'parent'):
813 if opts.get(b'parent'):
813 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
814 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
814 parent = p1
815 parent = p1
815
816
816 # the backout should appear on the same branch
817 # the backout should appear on the same branch
817 branch = repo.dirstate.branch()
818 branch = repo.dirstate.branch()
818 bheads = repo.branchheads(branch)
819 bheads = repo.branchheads(branch)
819 rctx = scmutil.revsingle(repo, hex(parent))
820 rctx = scmutil.revsingle(repo, hex(parent))
820 if not opts.get(b'merge') and op1 != node:
821 if not opts.get(b'merge') and op1 != node:
821 with dirstateguard.dirstateguard(repo, b'backout'):
822 with dirstateguard.dirstateguard(repo, b'backout'):
822 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
823 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
823 with ui.configoverride(overrides, b'backout'):
824 with ui.configoverride(overrides, b'backout'):
824 stats = mergemod.update(
825 stats = mergemod.back_out(ctx, parent=repo[parent])
825 repo,
826 parent,
827 branchmerge=True,
828 force=True,
829 ancestor=node,
830 mergeancestor=False,
831 )
832 repo.setparents(op1, op2)
826 repo.setparents(op1, op2)
833 hg._showstats(repo, stats)
827 hg._showstats(repo, stats)
834 if stats.unresolvedcount:
828 if stats.unresolvedcount:
835 repo.ui.status(
829 repo.ui.status(
836 _(b"use 'hg resolve' to retry unresolved file merges\n")
830 _(b"use 'hg resolve' to retry unresolved file merges\n")
837 )
831 )
838 return 1
832 return 1
839 else:
833 else:
840 hg.clean(repo, node, show_stats=False)
834 hg.clean(repo, node, show_stats=False)
841 repo.dirstate.setbranch(branch)
835 repo.dirstate.setbranch(branch)
842 cmdutil.revert(ui, repo, rctx)
836 cmdutil.revert(ui, repo, rctx)
843
837
844 if opts.get(b'no_commit'):
838 if opts.get(b'no_commit'):
845 msg = _(b"changeset %s backed out, don't forget to commit.\n")
839 msg = _(b"changeset %s backed out, don't forget to commit.\n")
846 ui.status(msg % short(node))
840 ui.status(msg % short(node))
847 return 0
841 return 0
848
842
849 def commitfunc(ui, repo, message, match, opts):
843 def commitfunc(ui, repo, message, match, opts):
850 editform = b'backout'
844 editform = b'backout'
851 e = cmdutil.getcommiteditor(
845 e = cmdutil.getcommiteditor(
852 editform=editform, **pycompat.strkwargs(opts)
846 editform=editform, **pycompat.strkwargs(opts)
853 )
847 )
854 if not message:
848 if not message:
855 # we don't translate commit messages
849 # we don't translate commit messages
856 message = b"Backed out changeset %s" % short(node)
850 message = b"Backed out changeset %s" % short(node)
857 e = cmdutil.getcommiteditor(edit=True, editform=editform)
851 e = cmdutil.getcommiteditor(edit=True, editform=editform)
858 return repo.commit(
852 return repo.commit(
859 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
853 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
860 )
854 )
861
855
862 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
856 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
863 if not newnode:
857 if not newnode:
864 ui.status(_(b"nothing changed\n"))
858 ui.status(_(b"nothing changed\n"))
865 return 1
859 return 1
866 cmdutil.commitstatus(repo, newnode, branch, bheads)
860 cmdutil.commitstatus(repo, newnode, branch, bheads)
867
861
868 def nice(node):
862 def nice(node):
869 return b'%d:%s' % (repo.changelog.rev(node), short(node))
863 return b'%d:%s' % (repo.changelog.rev(node), short(node))
870
864
871 ui.status(
865 ui.status(
872 _(b'changeset %s backs out changeset %s\n')
866 _(b'changeset %s backs out changeset %s\n')
873 % (nice(repo.changelog.tip()), nice(node))
867 % (nice(repo.changelog.tip()), nice(node))
874 )
868 )
875 if opts.get(b'merge') and op1 != node:
869 if opts.get(b'merge') and op1 != node:
876 hg.clean(repo, op1, show_stats=False)
870 hg.clean(repo, op1, show_stats=False)
877 ui.status(
871 ui.status(
878 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
872 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
879 )
873 )
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
874 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 with ui.configoverride(overrides, b'backout'):
875 with ui.configoverride(overrides, b'backout'):
882 return hg.merge(repo[b'tip'])
876 return hg.merge(repo[b'tip'])
883 return 0
877 return 0
884
878
885
879
886 @command(
880 @command(
887 b'bisect',
881 b'bisect',
888 [
882 [
889 (b'r', b'reset', False, _(b'reset bisect state')),
883 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'g', b'good', False, _(b'mark changeset good')),
884 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
885 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
886 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
887 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (
888 (
895 b'c',
889 b'c',
896 b'command',
890 b'command',
897 b'',
891 b'',
898 _(b'use command to check changeset state'),
892 _(b'use command to check changeset state'),
899 _(b'CMD'),
893 _(b'CMD'),
900 ),
894 ),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
895 (b'U', b'noupdate', False, _(b'do not update to target')),
902 ],
896 ],
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
897 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
898 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 )
899 )
906 def bisect(
900 def bisect(
907 ui,
901 ui,
908 repo,
902 repo,
909 rev=None,
903 rev=None,
910 extra=None,
904 extra=None,
911 command=None,
905 command=None,
912 reset=None,
906 reset=None,
913 good=None,
907 good=None,
914 bad=None,
908 bad=None,
915 skip=None,
909 skip=None,
916 extend=None,
910 extend=None,
917 noupdate=None,
911 noupdate=None,
918 ):
912 ):
919 """subdivision search of changesets
913 """subdivision search of changesets
920
914
921 This command helps to find changesets which introduce problems. To
915 This command helps to find changesets which introduce problems. To
922 use, mark the earliest changeset you know exhibits the problem as
916 use, mark the earliest changeset you know exhibits the problem as
923 bad, then mark the latest changeset which is free from the problem
917 bad, then mark the latest changeset which is free from the problem
924 as good. Bisect will update your working directory to a revision
918 as good. Bisect will update your working directory to a revision
925 for testing (unless the -U/--noupdate option is specified). Once
919 for testing (unless the -U/--noupdate option is specified). Once
926 you have performed tests, mark the working directory as good or
920 you have performed tests, mark the working directory as good or
927 bad, and bisect will either update to another candidate changeset
921 bad, and bisect will either update to another candidate changeset
928 or announce that it has found the bad revision.
922 or announce that it has found the bad revision.
929
923
930 As a shortcut, you can also use the revision argument to mark a
924 As a shortcut, you can also use the revision argument to mark a
931 revision as good or bad without checking it out first.
925 revision as good or bad without checking it out first.
932
926
933 If you supply a command, it will be used for automatic bisection.
927 If you supply a command, it will be used for automatic bisection.
934 The environment variable HG_NODE will contain the ID of the
928 The environment variable HG_NODE will contain the ID of the
935 changeset being tested. The exit status of the command will be
929 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
930 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
931 means to skip the revision, 127 (command not found) will abort the
938 bisection, and any other non-zero exit status means the revision
932 bisection, and any other non-zero exit status means the revision
939 is bad.
933 is bad.
940
934
941 .. container:: verbose
935 .. container:: verbose
942
936
943 Some examples:
937 Some examples:
944
938
945 - start a bisection with known bad revision 34, and good revision 12::
939 - start a bisection with known bad revision 34, and good revision 12::
946
940
947 hg bisect --bad 34
941 hg bisect --bad 34
948 hg bisect --good 12
942 hg bisect --good 12
949
943
950 - advance the current bisection by marking current revision as good or
944 - advance the current bisection by marking current revision as good or
951 bad::
945 bad::
952
946
953 hg bisect --good
947 hg bisect --good
954 hg bisect --bad
948 hg bisect --bad
955
949
956 - mark the current revision, or a known revision, to be skipped (e.g. if
950 - mark the current revision, or a known revision, to be skipped (e.g. if
957 that revision is not usable because of another issue)::
951 that revision is not usable because of another issue)::
958
952
959 hg bisect --skip
953 hg bisect --skip
960 hg bisect --skip 23
954 hg bisect --skip 23
961
955
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
956 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963
957
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
958 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965
959
966 - forget the current bisection::
960 - forget the current bisection::
967
961
968 hg bisect --reset
962 hg bisect --reset
969
963
970 - use 'make && make tests' to automatically find the first broken
964 - use 'make && make tests' to automatically find the first broken
971 revision::
965 revision::
972
966
973 hg bisect --reset
967 hg bisect --reset
974 hg bisect --bad 34
968 hg bisect --bad 34
975 hg bisect --good 12
969 hg bisect --good 12
976 hg bisect --command "make && make tests"
970 hg bisect --command "make && make tests"
977
971
978 - see all changesets whose states are already known in the current
972 - see all changesets whose states are already known in the current
979 bisection::
973 bisection::
980
974
981 hg log -r "bisect(pruned)"
975 hg log -r "bisect(pruned)"
982
976
983 - see the changeset currently being bisected (especially useful
977 - see the changeset currently being bisected (especially useful
984 if running with -U/--noupdate)::
978 if running with -U/--noupdate)::
985
979
986 hg log -r "bisect(current)"
980 hg log -r "bisect(current)"
987
981
988 - see all changesets that took part in the current bisection::
982 - see all changesets that took part in the current bisection::
989
983
990 hg log -r "bisect(range)"
984 hg log -r "bisect(range)"
991
985
992 - you can even get a nice graph::
986 - you can even get a nice graph::
993
987
994 hg log --graph -r "bisect(range)"
988 hg log --graph -r "bisect(range)"
995
989
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
990 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997
991
998 Returns 0 on success.
992 Returns 0 on success.
999 """
993 """
1000 # backward compatibility
994 # backward compatibility
1001 if rev in b"good bad reset init".split():
995 if rev in b"good bad reset init".split():
1002 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
996 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1003 cmd, rev, extra = rev, extra, None
997 cmd, rev, extra = rev, extra, None
1004 if cmd == b"good":
998 if cmd == b"good":
1005 good = True
999 good = True
1006 elif cmd == b"bad":
1000 elif cmd == b"bad":
1007 bad = True
1001 bad = True
1008 else:
1002 else:
1009 reset = True
1003 reset = True
1010 elif extra:
1004 elif extra:
1011 raise error.Abort(_(b'incompatible arguments'))
1005 raise error.Abort(_(b'incompatible arguments'))
1012
1006
1013 incompatibles = {
1007 incompatibles = {
1014 b'--bad': bad,
1008 b'--bad': bad,
1015 b'--command': bool(command),
1009 b'--command': bool(command),
1016 b'--extend': extend,
1010 b'--extend': extend,
1017 b'--good': good,
1011 b'--good': good,
1018 b'--reset': reset,
1012 b'--reset': reset,
1019 b'--skip': skip,
1013 b'--skip': skip,
1020 }
1014 }
1021
1015
1022 enabled = [x for x in incompatibles if incompatibles[x]]
1016 enabled = [x for x in incompatibles if incompatibles[x]]
1023
1017
1024 if len(enabled) > 1:
1018 if len(enabled) > 1:
1025 raise error.Abort(
1019 raise error.Abort(
1026 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1020 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1027 )
1021 )
1028
1022
1029 if reset:
1023 if reset:
1030 hbisect.resetstate(repo)
1024 hbisect.resetstate(repo)
1031 return
1025 return
1032
1026
1033 state = hbisect.load_state(repo)
1027 state = hbisect.load_state(repo)
1034
1028
1035 # update state
1029 # update state
1036 if good or bad or skip:
1030 if good or bad or skip:
1037 if rev:
1031 if rev:
1038 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1032 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1039 else:
1033 else:
1040 nodes = [repo.lookup(b'.')]
1034 nodes = [repo.lookup(b'.')]
1041 if good:
1035 if good:
1042 state[b'good'] += nodes
1036 state[b'good'] += nodes
1043 elif bad:
1037 elif bad:
1044 state[b'bad'] += nodes
1038 state[b'bad'] += nodes
1045 elif skip:
1039 elif skip:
1046 state[b'skip'] += nodes
1040 state[b'skip'] += nodes
1047 hbisect.save_state(repo, state)
1041 hbisect.save_state(repo, state)
1048 if not (state[b'good'] and state[b'bad']):
1042 if not (state[b'good'] and state[b'bad']):
1049 return
1043 return
1050
1044
1051 def mayupdate(repo, node, show_stats=True):
1045 def mayupdate(repo, node, show_stats=True):
1052 """common used update sequence"""
1046 """common used update sequence"""
1053 if noupdate:
1047 if noupdate:
1054 return
1048 return
1055 cmdutil.checkunfinished(repo)
1049 cmdutil.checkunfinished(repo)
1056 cmdutil.bailifchanged(repo)
1050 cmdutil.bailifchanged(repo)
1057 return hg.clean(repo, node, show_stats=show_stats)
1051 return hg.clean(repo, node, show_stats=show_stats)
1058
1052
1059 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1053 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1060
1054
1061 if command:
1055 if command:
1062 changesets = 1
1056 changesets = 1
1063 if noupdate:
1057 if noupdate:
1064 try:
1058 try:
1065 node = state[b'current'][0]
1059 node = state[b'current'][0]
1066 except LookupError:
1060 except LookupError:
1067 raise error.Abort(
1061 raise error.Abort(
1068 _(
1062 _(
1069 b'current bisect revision is unknown - '
1063 b'current bisect revision is unknown - '
1070 b'start a new bisect to fix'
1064 b'start a new bisect to fix'
1071 )
1065 )
1072 )
1066 )
1073 else:
1067 else:
1074 node, p2 = repo.dirstate.parents()
1068 node, p2 = repo.dirstate.parents()
1075 if p2 != nullid:
1069 if p2 != nullid:
1076 raise error.Abort(_(b'current bisect revision is a merge'))
1070 raise error.Abort(_(b'current bisect revision is a merge'))
1077 if rev:
1071 if rev:
1078 node = repo[scmutil.revsingle(repo, rev, node)].node()
1072 node = repo[scmutil.revsingle(repo, rev, node)].node()
1079 with hbisect.restore_state(repo, state, node):
1073 with hbisect.restore_state(repo, state, node):
1080 while changesets:
1074 while changesets:
1081 # update state
1075 # update state
1082 state[b'current'] = [node]
1076 state[b'current'] = [node]
1083 hbisect.save_state(repo, state)
1077 hbisect.save_state(repo, state)
1084 status = ui.system(
1078 status = ui.system(
1085 command,
1079 command,
1086 environ={b'HG_NODE': hex(node)},
1080 environ={b'HG_NODE': hex(node)},
1087 blockedtag=b'bisect_check',
1081 blockedtag=b'bisect_check',
1088 )
1082 )
1089 if status == 125:
1083 if status == 125:
1090 transition = b"skip"
1084 transition = b"skip"
1091 elif status == 0:
1085 elif status == 0:
1092 transition = b"good"
1086 transition = b"good"
1093 # status < 0 means process was killed
1087 # status < 0 means process was killed
1094 elif status == 127:
1088 elif status == 127:
1095 raise error.Abort(_(b"failed to execute %s") % command)
1089 raise error.Abort(_(b"failed to execute %s") % command)
1096 elif status < 0:
1090 elif status < 0:
1097 raise error.Abort(_(b"%s killed") % command)
1091 raise error.Abort(_(b"%s killed") % command)
1098 else:
1092 else:
1099 transition = b"bad"
1093 transition = b"bad"
1100 state[transition].append(node)
1094 state[transition].append(node)
1101 ctx = repo[node]
1095 ctx = repo[node]
1102 ui.status(
1096 ui.status(
1103 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1097 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1104 )
1098 )
1105 hbisect.checkstate(state)
1099 hbisect.checkstate(state)
1106 # bisect
1100 # bisect
1107 nodes, changesets, bgood = hbisect.bisect(repo, state)
1101 nodes, changesets, bgood = hbisect.bisect(repo, state)
1108 # update to next check
1102 # update to next check
1109 node = nodes[0]
1103 node = nodes[0]
1110 mayupdate(repo, node, show_stats=False)
1104 mayupdate(repo, node, show_stats=False)
1111 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1105 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1112 return
1106 return
1113
1107
1114 hbisect.checkstate(state)
1108 hbisect.checkstate(state)
1115
1109
1116 # actually bisect
1110 # actually bisect
1117 nodes, changesets, good = hbisect.bisect(repo, state)
1111 nodes, changesets, good = hbisect.bisect(repo, state)
1118 if extend:
1112 if extend:
1119 if not changesets:
1113 if not changesets:
1120 extendnode = hbisect.extendrange(repo, state, nodes, good)
1114 extendnode = hbisect.extendrange(repo, state, nodes, good)
1121 if extendnode is not None:
1115 if extendnode is not None:
1122 ui.write(
1116 ui.write(
1123 _(b"Extending search to changeset %d:%s\n")
1117 _(b"Extending search to changeset %d:%s\n")
1124 % (extendnode.rev(), extendnode)
1118 % (extendnode.rev(), extendnode)
1125 )
1119 )
1126 state[b'current'] = [extendnode.node()]
1120 state[b'current'] = [extendnode.node()]
1127 hbisect.save_state(repo, state)
1121 hbisect.save_state(repo, state)
1128 return mayupdate(repo, extendnode.node())
1122 return mayupdate(repo, extendnode.node())
1129 raise error.Abort(_(b"nothing to extend"))
1123 raise error.Abort(_(b"nothing to extend"))
1130
1124
1131 if changesets == 0:
1125 if changesets == 0:
1132 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1126 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1133 else:
1127 else:
1134 assert len(nodes) == 1 # only a single node can be tested next
1128 assert len(nodes) == 1 # only a single node can be tested next
1135 node = nodes[0]
1129 node = nodes[0]
1136 # compute the approximate number of remaining tests
1130 # compute the approximate number of remaining tests
1137 tests, size = 0, 2
1131 tests, size = 0, 2
1138 while size <= changesets:
1132 while size <= changesets:
1139 tests, size = tests + 1, size * 2
1133 tests, size = tests + 1, size * 2
1140 rev = repo.changelog.rev(node)
1134 rev = repo.changelog.rev(node)
1141 ui.write(
1135 ui.write(
1142 _(
1136 _(
1143 b"Testing changeset %d:%s "
1137 b"Testing changeset %d:%s "
1144 b"(%d changesets remaining, ~%d tests)\n"
1138 b"(%d changesets remaining, ~%d tests)\n"
1145 )
1139 )
1146 % (rev, short(node), changesets, tests)
1140 % (rev, short(node), changesets, tests)
1147 )
1141 )
1148 state[b'current'] = [node]
1142 state[b'current'] = [node]
1149 hbisect.save_state(repo, state)
1143 hbisect.save_state(repo, state)
1150 return mayupdate(repo, node)
1144 return mayupdate(repo, node)
1151
1145
1152
1146
1153 @command(
1147 @command(
1154 b'bookmarks|bookmark',
1148 b'bookmarks|bookmark',
1155 [
1149 [
1156 (b'f', b'force', False, _(b'force')),
1150 (b'f', b'force', False, _(b'force')),
1157 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1151 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1158 (b'd', b'delete', False, _(b'delete a given bookmark')),
1152 (b'd', b'delete', False, _(b'delete a given bookmark')),
1159 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1153 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1160 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1154 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1161 (b'l', b'list', False, _(b'list existing bookmarks')),
1155 (b'l', b'list', False, _(b'list existing bookmarks')),
1162 ]
1156 ]
1163 + formatteropts,
1157 + formatteropts,
1164 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1158 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1165 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1159 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1166 )
1160 )
1167 def bookmark(ui, repo, *names, **opts):
1161 def bookmark(ui, repo, *names, **opts):
1168 '''create a new bookmark or list existing bookmarks
1162 '''create a new bookmark or list existing bookmarks
1169
1163
1170 Bookmarks are labels on changesets to help track lines of development.
1164 Bookmarks are labels on changesets to help track lines of development.
1171 Bookmarks are unversioned and can be moved, renamed and deleted.
1165 Bookmarks are unversioned and can be moved, renamed and deleted.
1172 Deleting or moving a bookmark has no effect on the associated changesets.
1166 Deleting or moving a bookmark has no effect on the associated changesets.
1173
1167
1174 Creating or updating to a bookmark causes it to be marked as 'active'.
1168 Creating or updating to a bookmark causes it to be marked as 'active'.
1175 The active bookmark is indicated with a '*'.
1169 The active bookmark is indicated with a '*'.
1176 When a commit is made, the active bookmark will advance to the new commit.
1170 When a commit is made, the active bookmark will advance to the new commit.
1177 A plain :hg:`update` will also advance an active bookmark, if possible.
1171 A plain :hg:`update` will also advance an active bookmark, if possible.
1178 Updating away from a bookmark will cause it to be deactivated.
1172 Updating away from a bookmark will cause it to be deactivated.
1179
1173
1180 Bookmarks can be pushed and pulled between repositories (see
1174 Bookmarks can be pushed and pulled between repositories (see
1181 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1175 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1182 diverged, a new 'divergent bookmark' of the form 'name@path' will
1176 diverged, a new 'divergent bookmark' of the form 'name@path' will
1183 be created. Using :hg:`merge` will resolve the divergence.
1177 be created. Using :hg:`merge` will resolve the divergence.
1184
1178
1185 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1179 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1186 the active bookmark's name.
1180 the active bookmark's name.
1187
1181
1188 A bookmark named '@' has the special property that :hg:`clone` will
1182 A bookmark named '@' has the special property that :hg:`clone` will
1189 check it out by default if it exists.
1183 check it out by default if it exists.
1190
1184
1191 .. container:: verbose
1185 .. container:: verbose
1192
1186
1193 Template:
1187 Template:
1194
1188
1195 The following keywords are supported in addition to the common template
1189 The following keywords are supported in addition to the common template
1196 keywords and functions such as ``{bookmark}``. See also
1190 keywords and functions such as ``{bookmark}``. See also
1197 :hg:`help templates`.
1191 :hg:`help templates`.
1198
1192
1199 :active: Boolean. True if the bookmark is active.
1193 :active: Boolean. True if the bookmark is active.
1200
1194
1201 Examples:
1195 Examples:
1202
1196
1203 - create an active bookmark for a new line of development::
1197 - create an active bookmark for a new line of development::
1204
1198
1205 hg book new-feature
1199 hg book new-feature
1206
1200
1207 - create an inactive bookmark as a place marker::
1201 - create an inactive bookmark as a place marker::
1208
1202
1209 hg book -i reviewed
1203 hg book -i reviewed
1210
1204
1211 - create an inactive bookmark on another changeset::
1205 - create an inactive bookmark on another changeset::
1212
1206
1213 hg book -r .^ tested
1207 hg book -r .^ tested
1214
1208
1215 - rename bookmark turkey to dinner::
1209 - rename bookmark turkey to dinner::
1216
1210
1217 hg book -m turkey dinner
1211 hg book -m turkey dinner
1218
1212
1219 - move the '@' bookmark from another branch::
1213 - move the '@' bookmark from another branch::
1220
1214
1221 hg book -f @
1215 hg book -f @
1222
1216
1223 - print only the active bookmark name::
1217 - print only the active bookmark name::
1224
1218
1225 hg book -ql .
1219 hg book -ql .
1226 '''
1220 '''
1227 opts = pycompat.byteskwargs(opts)
1221 opts = pycompat.byteskwargs(opts)
1228 force = opts.get(b'force')
1222 force = opts.get(b'force')
1229 rev = opts.get(b'rev')
1223 rev = opts.get(b'rev')
1230 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1224 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1231
1225
1232 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1226 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1233 if action:
1227 if action:
1234 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1228 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1235 elif names or rev:
1229 elif names or rev:
1236 action = b'add'
1230 action = b'add'
1237 elif inactive:
1231 elif inactive:
1238 action = b'inactive' # meaning deactivate
1232 action = b'inactive' # meaning deactivate
1239 else:
1233 else:
1240 action = b'list'
1234 action = b'list'
1241
1235
1242 cmdutil.check_incompatible_arguments(
1236 cmdutil.check_incompatible_arguments(
1243 opts, b'inactive', [b'delete', b'list']
1237 opts, b'inactive', [b'delete', b'list']
1244 )
1238 )
1245 if not names and action in {b'add', b'delete'}:
1239 if not names and action in {b'add', b'delete'}:
1246 raise error.Abort(_(b"bookmark name required"))
1240 raise error.Abort(_(b"bookmark name required"))
1247
1241
1248 if action in {b'add', b'delete', b'rename', b'inactive'}:
1242 if action in {b'add', b'delete', b'rename', b'inactive'}:
1249 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1243 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1250 if action == b'delete':
1244 if action == b'delete':
1251 names = pycompat.maplist(repo._bookmarks.expandname, names)
1245 names = pycompat.maplist(repo._bookmarks.expandname, names)
1252 bookmarks.delete(repo, tr, names)
1246 bookmarks.delete(repo, tr, names)
1253 elif action == b'rename':
1247 elif action == b'rename':
1254 if not names:
1248 if not names:
1255 raise error.Abort(_(b"new bookmark name required"))
1249 raise error.Abort(_(b"new bookmark name required"))
1256 elif len(names) > 1:
1250 elif len(names) > 1:
1257 raise error.Abort(_(b"only one new bookmark name allowed"))
1251 raise error.Abort(_(b"only one new bookmark name allowed"))
1258 oldname = repo._bookmarks.expandname(opts[b'rename'])
1252 oldname = repo._bookmarks.expandname(opts[b'rename'])
1259 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1253 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1260 elif action == b'add':
1254 elif action == b'add':
1261 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1255 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1262 elif action == b'inactive':
1256 elif action == b'inactive':
1263 if len(repo._bookmarks) == 0:
1257 if len(repo._bookmarks) == 0:
1264 ui.status(_(b"no bookmarks set\n"))
1258 ui.status(_(b"no bookmarks set\n"))
1265 elif not repo._activebookmark:
1259 elif not repo._activebookmark:
1266 ui.status(_(b"no active bookmark\n"))
1260 ui.status(_(b"no active bookmark\n"))
1267 else:
1261 else:
1268 bookmarks.deactivate(repo)
1262 bookmarks.deactivate(repo)
1269 elif action == b'list':
1263 elif action == b'list':
1270 names = pycompat.maplist(repo._bookmarks.expandname, names)
1264 names = pycompat.maplist(repo._bookmarks.expandname, names)
1271 with ui.formatter(b'bookmarks', opts) as fm:
1265 with ui.formatter(b'bookmarks', opts) as fm:
1272 bookmarks.printbookmarks(ui, repo, fm, names)
1266 bookmarks.printbookmarks(ui, repo, fm, names)
1273 else:
1267 else:
1274 raise error.ProgrammingError(b'invalid action: %s' % action)
1268 raise error.ProgrammingError(b'invalid action: %s' % action)
1275
1269
1276
1270
1277 @command(
1271 @command(
1278 b'branch',
1272 b'branch',
1279 [
1273 [
1280 (
1274 (
1281 b'f',
1275 b'f',
1282 b'force',
1276 b'force',
1283 None,
1277 None,
1284 _(b'set branch name even if it shadows an existing branch'),
1278 _(b'set branch name even if it shadows an existing branch'),
1285 ),
1279 ),
1286 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1280 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1287 (
1281 (
1288 b'r',
1282 b'r',
1289 b'rev',
1283 b'rev',
1290 [],
1284 [],
1291 _(b'change branches of the given revs (EXPERIMENTAL)'),
1285 _(b'change branches of the given revs (EXPERIMENTAL)'),
1292 ),
1286 ),
1293 ],
1287 ],
1294 _(b'[-fC] [NAME]'),
1288 _(b'[-fC] [NAME]'),
1295 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1289 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1296 )
1290 )
1297 def branch(ui, repo, label=None, **opts):
1291 def branch(ui, repo, label=None, **opts):
1298 """set or show the current branch name
1292 """set or show the current branch name
1299
1293
1300 .. note::
1294 .. note::
1301
1295
1302 Branch names are permanent and global. Use :hg:`bookmark` to create a
1296 Branch names are permanent and global. Use :hg:`bookmark` to create a
1303 light-weight bookmark instead. See :hg:`help glossary` for more
1297 light-weight bookmark instead. See :hg:`help glossary` for more
1304 information about named branches and bookmarks.
1298 information about named branches and bookmarks.
1305
1299
1306 With no argument, show the current branch name. With one argument,
1300 With no argument, show the current branch name. With one argument,
1307 set the working directory branch name (the branch will not exist
1301 set the working directory branch name (the branch will not exist
1308 in the repository until the next commit). Standard practice
1302 in the repository until the next commit). Standard practice
1309 recommends that primary development take place on the 'default'
1303 recommends that primary development take place on the 'default'
1310 branch.
1304 branch.
1311
1305
1312 Unless -f/--force is specified, branch will not let you set a
1306 Unless -f/--force is specified, branch will not let you set a
1313 branch name that already exists.
1307 branch name that already exists.
1314
1308
1315 Use -C/--clean to reset the working directory branch to that of
1309 Use -C/--clean to reset the working directory branch to that of
1316 the parent of the working directory, negating a previous branch
1310 the parent of the working directory, negating a previous branch
1317 change.
1311 change.
1318
1312
1319 Use the command :hg:`update` to switch to an existing branch. Use
1313 Use the command :hg:`update` to switch to an existing branch. Use
1320 :hg:`commit --close-branch` to mark this branch head as closed.
1314 :hg:`commit --close-branch` to mark this branch head as closed.
1321 When all heads of a branch are closed, the branch will be
1315 When all heads of a branch are closed, the branch will be
1322 considered closed.
1316 considered closed.
1323
1317
1324 Returns 0 on success.
1318 Returns 0 on success.
1325 """
1319 """
1326 opts = pycompat.byteskwargs(opts)
1320 opts = pycompat.byteskwargs(opts)
1327 revs = opts.get(b'rev')
1321 revs = opts.get(b'rev')
1328 if label:
1322 if label:
1329 label = label.strip()
1323 label = label.strip()
1330
1324
1331 if not opts.get(b'clean') and not label:
1325 if not opts.get(b'clean') and not label:
1332 if revs:
1326 if revs:
1333 raise error.Abort(_(b"no branch name specified for the revisions"))
1327 raise error.Abort(_(b"no branch name specified for the revisions"))
1334 ui.write(b"%s\n" % repo.dirstate.branch())
1328 ui.write(b"%s\n" % repo.dirstate.branch())
1335 return
1329 return
1336
1330
1337 with repo.wlock():
1331 with repo.wlock():
1338 if opts.get(b'clean'):
1332 if opts.get(b'clean'):
1339 label = repo[b'.'].branch()
1333 label = repo[b'.'].branch()
1340 repo.dirstate.setbranch(label)
1334 repo.dirstate.setbranch(label)
1341 ui.status(_(b'reset working directory to branch %s\n') % label)
1335 ui.status(_(b'reset working directory to branch %s\n') % label)
1342 elif label:
1336 elif label:
1343
1337
1344 scmutil.checknewlabel(repo, label, b'branch')
1338 scmutil.checknewlabel(repo, label, b'branch')
1345 if revs:
1339 if revs:
1346 return cmdutil.changebranch(ui, repo, revs, label, opts)
1340 return cmdutil.changebranch(ui, repo, revs, label, opts)
1347
1341
1348 if not opts.get(b'force') and label in repo.branchmap():
1342 if not opts.get(b'force') and label in repo.branchmap():
1349 if label not in [p.branch() for p in repo[None].parents()]:
1343 if label not in [p.branch() for p in repo[None].parents()]:
1350 raise error.Abort(
1344 raise error.Abort(
1351 _(b'a branch of the same name already exists'),
1345 _(b'a branch of the same name already exists'),
1352 # i18n: "it" refers to an existing branch
1346 # i18n: "it" refers to an existing branch
1353 hint=_(b"use 'hg update' to switch to it"),
1347 hint=_(b"use 'hg update' to switch to it"),
1354 )
1348 )
1355
1349
1356 repo.dirstate.setbranch(label)
1350 repo.dirstate.setbranch(label)
1357 ui.status(_(b'marked working directory as branch %s\n') % label)
1351 ui.status(_(b'marked working directory as branch %s\n') % label)
1358
1352
1359 # find any open named branches aside from default
1353 # find any open named branches aside from default
1360 for n, h, t, c in repo.branchmap().iterbranches():
1354 for n, h, t, c in repo.branchmap().iterbranches():
1361 if n != b"default" and not c:
1355 if n != b"default" and not c:
1362 return 0
1356 return 0
1363 ui.status(
1357 ui.status(
1364 _(
1358 _(
1365 b'(branches are permanent and global, '
1359 b'(branches are permanent and global, '
1366 b'did you want a bookmark?)\n'
1360 b'did you want a bookmark?)\n'
1367 )
1361 )
1368 )
1362 )
1369
1363
1370
1364
1371 @command(
1365 @command(
1372 b'branches',
1366 b'branches',
1373 [
1367 [
1374 (
1368 (
1375 b'a',
1369 b'a',
1376 b'active',
1370 b'active',
1377 False,
1371 False,
1378 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1372 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1379 ),
1373 ),
1380 (b'c', b'closed', False, _(b'show normal and closed branches')),
1374 (b'c', b'closed', False, _(b'show normal and closed branches')),
1381 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1375 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1382 ]
1376 ]
1383 + formatteropts,
1377 + formatteropts,
1384 _(b'[-c]'),
1378 _(b'[-c]'),
1385 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1379 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1386 intents={INTENT_READONLY},
1380 intents={INTENT_READONLY},
1387 )
1381 )
1388 def branches(ui, repo, active=False, closed=False, **opts):
1382 def branches(ui, repo, active=False, closed=False, **opts):
1389 """list repository named branches
1383 """list repository named branches
1390
1384
1391 List the repository's named branches, indicating which ones are
1385 List the repository's named branches, indicating which ones are
1392 inactive. If -c/--closed is specified, also list branches which have
1386 inactive. If -c/--closed is specified, also list branches which have
1393 been marked closed (see :hg:`commit --close-branch`).
1387 been marked closed (see :hg:`commit --close-branch`).
1394
1388
1395 Use the command :hg:`update` to switch to an existing branch.
1389 Use the command :hg:`update` to switch to an existing branch.
1396
1390
1397 .. container:: verbose
1391 .. container:: verbose
1398
1392
1399 Template:
1393 Template:
1400
1394
1401 The following keywords are supported in addition to the common template
1395 The following keywords are supported in addition to the common template
1402 keywords and functions such as ``{branch}``. See also
1396 keywords and functions such as ``{branch}``. See also
1403 :hg:`help templates`.
1397 :hg:`help templates`.
1404
1398
1405 :active: Boolean. True if the branch is active.
1399 :active: Boolean. True if the branch is active.
1406 :closed: Boolean. True if the branch is closed.
1400 :closed: Boolean. True if the branch is closed.
1407 :current: Boolean. True if it is the current branch.
1401 :current: Boolean. True if it is the current branch.
1408
1402
1409 Returns 0.
1403 Returns 0.
1410 """
1404 """
1411
1405
1412 opts = pycompat.byteskwargs(opts)
1406 opts = pycompat.byteskwargs(opts)
1413 revs = opts.get(b'rev')
1407 revs = opts.get(b'rev')
1414 selectedbranches = None
1408 selectedbranches = None
1415 if revs:
1409 if revs:
1416 revs = scmutil.revrange(repo, revs)
1410 revs = scmutil.revrange(repo, revs)
1417 getbi = repo.revbranchcache().branchinfo
1411 getbi = repo.revbranchcache().branchinfo
1418 selectedbranches = {getbi(r)[0] for r in revs}
1412 selectedbranches = {getbi(r)[0] for r in revs}
1419
1413
1420 ui.pager(b'branches')
1414 ui.pager(b'branches')
1421 fm = ui.formatter(b'branches', opts)
1415 fm = ui.formatter(b'branches', opts)
1422 hexfunc = fm.hexfunc
1416 hexfunc = fm.hexfunc
1423
1417
1424 allheads = set(repo.heads())
1418 allheads = set(repo.heads())
1425 branches = []
1419 branches = []
1426 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1420 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1427 if selectedbranches is not None and tag not in selectedbranches:
1421 if selectedbranches is not None and tag not in selectedbranches:
1428 continue
1422 continue
1429 isactive = False
1423 isactive = False
1430 if not isclosed:
1424 if not isclosed:
1431 openheads = set(repo.branchmap().iteropen(heads))
1425 openheads = set(repo.branchmap().iteropen(heads))
1432 isactive = bool(openheads & allheads)
1426 isactive = bool(openheads & allheads)
1433 branches.append((tag, repo[tip], isactive, not isclosed))
1427 branches.append((tag, repo[tip], isactive, not isclosed))
1434 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1428 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1435
1429
1436 for tag, ctx, isactive, isopen in branches:
1430 for tag, ctx, isactive, isopen in branches:
1437 if active and not isactive:
1431 if active and not isactive:
1438 continue
1432 continue
1439 if isactive:
1433 if isactive:
1440 label = b'branches.active'
1434 label = b'branches.active'
1441 notice = b''
1435 notice = b''
1442 elif not isopen:
1436 elif not isopen:
1443 if not closed:
1437 if not closed:
1444 continue
1438 continue
1445 label = b'branches.closed'
1439 label = b'branches.closed'
1446 notice = _(b' (closed)')
1440 notice = _(b' (closed)')
1447 else:
1441 else:
1448 label = b'branches.inactive'
1442 label = b'branches.inactive'
1449 notice = _(b' (inactive)')
1443 notice = _(b' (inactive)')
1450 current = tag == repo.dirstate.branch()
1444 current = tag == repo.dirstate.branch()
1451 if current:
1445 if current:
1452 label = b'branches.current'
1446 label = b'branches.current'
1453
1447
1454 fm.startitem()
1448 fm.startitem()
1455 fm.write(b'branch', b'%s', tag, label=label)
1449 fm.write(b'branch', b'%s', tag, label=label)
1456 rev = ctx.rev()
1450 rev = ctx.rev()
1457 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1451 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1458 fmt = b' ' * padsize + b' %d:%s'
1452 fmt = b' ' * padsize + b' %d:%s'
1459 fm.condwrite(
1453 fm.condwrite(
1460 not ui.quiet,
1454 not ui.quiet,
1461 b'rev node',
1455 b'rev node',
1462 fmt,
1456 fmt,
1463 rev,
1457 rev,
1464 hexfunc(ctx.node()),
1458 hexfunc(ctx.node()),
1465 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1459 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1466 )
1460 )
1467 fm.context(ctx=ctx)
1461 fm.context(ctx=ctx)
1468 fm.data(active=isactive, closed=not isopen, current=current)
1462 fm.data(active=isactive, closed=not isopen, current=current)
1469 if not ui.quiet:
1463 if not ui.quiet:
1470 fm.plain(notice)
1464 fm.plain(notice)
1471 fm.plain(b'\n')
1465 fm.plain(b'\n')
1472 fm.end()
1466 fm.end()
1473
1467
1474
1468
1475 @command(
1469 @command(
1476 b'bundle',
1470 b'bundle',
1477 [
1471 [
1478 (
1472 (
1479 b'f',
1473 b'f',
1480 b'force',
1474 b'force',
1481 None,
1475 None,
1482 _(b'run even when the destination is unrelated'),
1476 _(b'run even when the destination is unrelated'),
1483 ),
1477 ),
1484 (
1478 (
1485 b'r',
1479 b'r',
1486 b'rev',
1480 b'rev',
1487 [],
1481 [],
1488 _(b'a changeset intended to be added to the destination'),
1482 _(b'a changeset intended to be added to the destination'),
1489 _(b'REV'),
1483 _(b'REV'),
1490 ),
1484 ),
1491 (
1485 (
1492 b'b',
1486 b'b',
1493 b'branch',
1487 b'branch',
1494 [],
1488 [],
1495 _(b'a specific branch you would like to bundle'),
1489 _(b'a specific branch you would like to bundle'),
1496 _(b'BRANCH'),
1490 _(b'BRANCH'),
1497 ),
1491 ),
1498 (
1492 (
1499 b'',
1493 b'',
1500 b'base',
1494 b'base',
1501 [],
1495 [],
1502 _(b'a base changeset assumed to be available at the destination'),
1496 _(b'a base changeset assumed to be available at the destination'),
1503 _(b'REV'),
1497 _(b'REV'),
1504 ),
1498 ),
1505 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1499 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1506 (
1500 (
1507 b't',
1501 b't',
1508 b'type',
1502 b'type',
1509 b'bzip2',
1503 b'bzip2',
1510 _(b'bundle compression type to use'),
1504 _(b'bundle compression type to use'),
1511 _(b'TYPE'),
1505 _(b'TYPE'),
1512 ),
1506 ),
1513 ]
1507 ]
1514 + remoteopts,
1508 + remoteopts,
1515 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1509 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1516 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1510 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1517 )
1511 )
1518 def bundle(ui, repo, fname, dest=None, **opts):
1512 def bundle(ui, repo, fname, dest=None, **opts):
1519 """create a bundle file
1513 """create a bundle file
1520
1514
1521 Generate a bundle file containing data to be transferred to another
1515 Generate a bundle file containing data to be transferred to another
1522 repository.
1516 repository.
1523
1517
1524 To create a bundle containing all changesets, use -a/--all
1518 To create a bundle containing all changesets, use -a/--all
1525 (or --base null). Otherwise, hg assumes the destination will have
1519 (or --base null). Otherwise, hg assumes the destination will have
1526 all the nodes you specify with --base parameters. Otherwise, hg
1520 all the nodes you specify with --base parameters. Otherwise, hg
1527 will assume the repository has all the nodes in destination, or
1521 will assume the repository has all the nodes in destination, or
1528 default-push/default if no destination is specified, where destination
1522 default-push/default if no destination is specified, where destination
1529 is the repository you provide through DEST option.
1523 is the repository you provide through DEST option.
1530
1524
1531 You can change bundle format with the -t/--type option. See
1525 You can change bundle format with the -t/--type option. See
1532 :hg:`help bundlespec` for documentation on this format. By default,
1526 :hg:`help bundlespec` for documentation on this format. By default,
1533 the most appropriate format is used and compression defaults to
1527 the most appropriate format is used and compression defaults to
1534 bzip2.
1528 bzip2.
1535
1529
1536 The bundle file can then be transferred using conventional means
1530 The bundle file can then be transferred using conventional means
1537 and applied to another repository with the unbundle or pull
1531 and applied to another repository with the unbundle or pull
1538 command. This is useful when direct push and pull are not
1532 command. This is useful when direct push and pull are not
1539 available or when exporting an entire repository is undesirable.
1533 available or when exporting an entire repository is undesirable.
1540
1534
1541 Applying bundles preserves all changeset contents including
1535 Applying bundles preserves all changeset contents including
1542 permissions, copy/rename information, and revision history.
1536 permissions, copy/rename information, and revision history.
1543
1537
1544 Returns 0 on success, 1 if no changes found.
1538 Returns 0 on success, 1 if no changes found.
1545 """
1539 """
1546 opts = pycompat.byteskwargs(opts)
1540 opts = pycompat.byteskwargs(opts)
1547 revs = None
1541 revs = None
1548 if b'rev' in opts:
1542 if b'rev' in opts:
1549 revstrings = opts[b'rev']
1543 revstrings = opts[b'rev']
1550 revs = scmutil.revrange(repo, revstrings)
1544 revs = scmutil.revrange(repo, revstrings)
1551 if revstrings and not revs:
1545 if revstrings and not revs:
1552 raise error.Abort(_(b'no commits to bundle'))
1546 raise error.Abort(_(b'no commits to bundle'))
1553
1547
1554 bundletype = opts.get(b'type', b'bzip2').lower()
1548 bundletype = opts.get(b'type', b'bzip2').lower()
1555 try:
1549 try:
1556 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1550 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1557 except error.UnsupportedBundleSpecification as e:
1551 except error.UnsupportedBundleSpecification as e:
1558 raise error.Abort(
1552 raise error.Abort(
1559 pycompat.bytestr(e),
1553 pycompat.bytestr(e),
1560 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1554 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1561 )
1555 )
1562 cgversion = bundlespec.contentopts[b"cg.version"]
1556 cgversion = bundlespec.contentopts[b"cg.version"]
1563
1557
1564 # Packed bundles are a pseudo bundle format for now.
1558 # Packed bundles are a pseudo bundle format for now.
1565 if cgversion == b's1':
1559 if cgversion == b's1':
1566 raise error.Abort(
1560 raise error.Abort(
1567 _(b'packed bundles cannot be produced by "hg bundle"'),
1561 _(b'packed bundles cannot be produced by "hg bundle"'),
1568 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1562 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1569 )
1563 )
1570
1564
1571 if opts.get(b'all'):
1565 if opts.get(b'all'):
1572 if dest:
1566 if dest:
1573 raise error.Abort(
1567 raise error.Abort(
1574 _(b"--all is incompatible with specifying a destination")
1568 _(b"--all is incompatible with specifying a destination")
1575 )
1569 )
1576 if opts.get(b'base'):
1570 if opts.get(b'base'):
1577 ui.warn(_(b"ignoring --base because --all was specified\n"))
1571 ui.warn(_(b"ignoring --base because --all was specified\n"))
1578 base = [nullrev]
1572 base = [nullrev]
1579 else:
1573 else:
1580 base = scmutil.revrange(repo, opts.get(b'base'))
1574 base = scmutil.revrange(repo, opts.get(b'base'))
1581 if cgversion not in changegroup.supportedoutgoingversions(repo):
1575 if cgversion not in changegroup.supportedoutgoingversions(repo):
1582 raise error.Abort(
1576 raise error.Abort(
1583 _(b"repository does not support bundle version %s") % cgversion
1577 _(b"repository does not support bundle version %s") % cgversion
1584 )
1578 )
1585
1579
1586 if base:
1580 if base:
1587 if dest:
1581 if dest:
1588 raise error.Abort(
1582 raise error.Abort(
1589 _(b"--base is incompatible with specifying a destination")
1583 _(b"--base is incompatible with specifying a destination")
1590 )
1584 )
1591 common = [repo[rev].node() for rev in base]
1585 common = [repo[rev].node() for rev in base]
1592 heads = [repo[r].node() for r in revs] if revs else None
1586 heads = [repo[r].node() for r in revs] if revs else None
1593 outgoing = discovery.outgoing(repo, common, heads)
1587 outgoing = discovery.outgoing(repo, common, heads)
1594 else:
1588 else:
1595 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1589 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1596 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1590 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1597 other = hg.peer(repo, opts, dest)
1591 other = hg.peer(repo, opts, dest)
1598 revs = [repo[r].hex() for r in revs]
1592 revs = [repo[r].hex() for r in revs]
1599 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1593 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1600 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1594 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1601 outgoing = discovery.findcommonoutgoing(
1595 outgoing = discovery.findcommonoutgoing(
1602 repo,
1596 repo,
1603 other,
1597 other,
1604 onlyheads=heads,
1598 onlyheads=heads,
1605 force=opts.get(b'force'),
1599 force=opts.get(b'force'),
1606 portable=True,
1600 portable=True,
1607 )
1601 )
1608
1602
1609 if not outgoing.missing:
1603 if not outgoing.missing:
1610 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1604 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1611 return 1
1605 return 1
1612
1606
1613 if cgversion == b'01': # bundle1
1607 if cgversion == b'01': # bundle1
1614 bversion = b'HG10' + bundlespec.wirecompression
1608 bversion = b'HG10' + bundlespec.wirecompression
1615 bcompression = None
1609 bcompression = None
1616 elif cgversion in (b'02', b'03'):
1610 elif cgversion in (b'02', b'03'):
1617 bversion = b'HG20'
1611 bversion = b'HG20'
1618 bcompression = bundlespec.wirecompression
1612 bcompression = bundlespec.wirecompression
1619 else:
1613 else:
1620 raise error.ProgrammingError(
1614 raise error.ProgrammingError(
1621 b'bundle: unexpected changegroup version %s' % cgversion
1615 b'bundle: unexpected changegroup version %s' % cgversion
1622 )
1616 )
1623
1617
1624 # TODO compression options should be derived from bundlespec parsing.
1618 # TODO compression options should be derived from bundlespec parsing.
1625 # This is a temporary hack to allow adjusting bundle compression
1619 # This is a temporary hack to allow adjusting bundle compression
1626 # level without a) formalizing the bundlespec changes to declare it
1620 # level without a) formalizing the bundlespec changes to declare it
1627 # b) introducing a command flag.
1621 # b) introducing a command flag.
1628 compopts = {}
1622 compopts = {}
1629 complevel = ui.configint(
1623 complevel = ui.configint(
1630 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1624 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1631 )
1625 )
1632 if complevel is None:
1626 if complevel is None:
1633 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1627 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1634 if complevel is not None:
1628 if complevel is not None:
1635 compopts[b'level'] = complevel
1629 compopts[b'level'] = complevel
1636
1630
1637 # Allow overriding the bundling of obsmarker in phases through
1631 # Allow overriding the bundling of obsmarker in phases through
1638 # configuration while we don't have a bundle version that include them
1632 # configuration while we don't have a bundle version that include them
1639 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1633 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1640 bundlespec.contentopts[b'obsolescence'] = True
1634 bundlespec.contentopts[b'obsolescence'] = True
1641 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1635 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1642 bundlespec.contentopts[b'phases'] = True
1636 bundlespec.contentopts[b'phases'] = True
1643
1637
1644 bundle2.writenewbundle(
1638 bundle2.writenewbundle(
1645 ui,
1639 ui,
1646 repo,
1640 repo,
1647 b'bundle',
1641 b'bundle',
1648 fname,
1642 fname,
1649 bversion,
1643 bversion,
1650 outgoing,
1644 outgoing,
1651 bundlespec.contentopts,
1645 bundlespec.contentopts,
1652 compression=bcompression,
1646 compression=bcompression,
1653 compopts=compopts,
1647 compopts=compopts,
1654 )
1648 )
1655
1649
1656
1650
1657 @command(
1651 @command(
1658 b'cat',
1652 b'cat',
1659 [
1653 [
1660 (
1654 (
1661 b'o',
1655 b'o',
1662 b'output',
1656 b'output',
1663 b'',
1657 b'',
1664 _(b'print output to file with formatted name'),
1658 _(b'print output to file with formatted name'),
1665 _(b'FORMAT'),
1659 _(b'FORMAT'),
1666 ),
1660 ),
1667 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1661 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1668 (b'', b'decode', None, _(b'apply any matching decode filter')),
1662 (b'', b'decode', None, _(b'apply any matching decode filter')),
1669 ]
1663 ]
1670 + walkopts
1664 + walkopts
1671 + formatteropts,
1665 + formatteropts,
1672 _(b'[OPTION]... FILE...'),
1666 _(b'[OPTION]... FILE...'),
1673 helpcategory=command.CATEGORY_FILE_CONTENTS,
1667 helpcategory=command.CATEGORY_FILE_CONTENTS,
1674 inferrepo=True,
1668 inferrepo=True,
1675 intents={INTENT_READONLY},
1669 intents={INTENT_READONLY},
1676 )
1670 )
1677 def cat(ui, repo, file1, *pats, **opts):
1671 def cat(ui, repo, file1, *pats, **opts):
1678 """output the current or given revision of files
1672 """output the current or given revision of files
1679
1673
1680 Print the specified files as they were at the given revision. If
1674 Print the specified files as they were at the given revision. If
1681 no revision is given, the parent of the working directory is used.
1675 no revision is given, the parent of the working directory is used.
1682
1676
1683 Output may be to a file, in which case the name of the file is
1677 Output may be to a file, in which case the name of the file is
1684 given using a template string. See :hg:`help templates`. In addition
1678 given using a template string. See :hg:`help templates`. In addition
1685 to the common template keywords, the following formatting rules are
1679 to the common template keywords, the following formatting rules are
1686 supported:
1680 supported:
1687
1681
1688 :``%%``: literal "%" character
1682 :``%%``: literal "%" character
1689 :``%s``: basename of file being printed
1683 :``%s``: basename of file being printed
1690 :``%d``: dirname of file being printed, or '.' if in repository root
1684 :``%d``: dirname of file being printed, or '.' if in repository root
1691 :``%p``: root-relative path name of file being printed
1685 :``%p``: root-relative path name of file being printed
1692 :``%H``: changeset hash (40 hexadecimal digits)
1686 :``%H``: changeset hash (40 hexadecimal digits)
1693 :``%R``: changeset revision number
1687 :``%R``: changeset revision number
1694 :``%h``: short-form changeset hash (12 hexadecimal digits)
1688 :``%h``: short-form changeset hash (12 hexadecimal digits)
1695 :``%r``: zero-padded changeset revision number
1689 :``%r``: zero-padded changeset revision number
1696 :``%b``: basename of the exporting repository
1690 :``%b``: basename of the exporting repository
1697 :``\\``: literal "\\" character
1691 :``\\``: literal "\\" character
1698
1692
1699 .. container:: verbose
1693 .. container:: verbose
1700
1694
1701 Template:
1695 Template:
1702
1696
1703 The following keywords are supported in addition to the common template
1697 The following keywords are supported in addition to the common template
1704 keywords and functions. See also :hg:`help templates`.
1698 keywords and functions. See also :hg:`help templates`.
1705
1699
1706 :data: String. File content.
1700 :data: String. File content.
1707 :path: String. Repository-absolute path of the file.
1701 :path: String. Repository-absolute path of the file.
1708
1702
1709 Returns 0 on success.
1703 Returns 0 on success.
1710 """
1704 """
1711 opts = pycompat.byteskwargs(opts)
1705 opts = pycompat.byteskwargs(opts)
1712 rev = opts.get(b'rev')
1706 rev = opts.get(b'rev')
1713 if rev:
1707 if rev:
1714 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1708 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1715 ctx = scmutil.revsingle(repo, rev)
1709 ctx = scmutil.revsingle(repo, rev)
1716 m = scmutil.match(ctx, (file1,) + pats, opts)
1710 m = scmutil.match(ctx, (file1,) + pats, opts)
1717 fntemplate = opts.pop(b'output', b'')
1711 fntemplate = opts.pop(b'output', b'')
1718 if cmdutil.isstdiofilename(fntemplate):
1712 if cmdutil.isstdiofilename(fntemplate):
1719 fntemplate = b''
1713 fntemplate = b''
1720
1714
1721 if fntemplate:
1715 if fntemplate:
1722 fm = formatter.nullformatter(ui, b'cat', opts)
1716 fm = formatter.nullformatter(ui, b'cat', opts)
1723 else:
1717 else:
1724 ui.pager(b'cat')
1718 ui.pager(b'cat')
1725 fm = ui.formatter(b'cat', opts)
1719 fm = ui.formatter(b'cat', opts)
1726 with fm:
1720 with fm:
1727 return cmdutil.cat(
1721 return cmdutil.cat(
1728 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1722 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1729 )
1723 )
1730
1724
1731
1725
1732 @command(
1726 @command(
1733 b'clone',
1727 b'clone',
1734 [
1728 [
1735 (
1729 (
1736 b'U',
1730 b'U',
1737 b'noupdate',
1731 b'noupdate',
1738 None,
1732 None,
1739 _(
1733 _(
1740 b'the clone will include an empty working '
1734 b'the clone will include an empty working '
1741 b'directory (only a repository)'
1735 b'directory (only a repository)'
1742 ),
1736 ),
1743 ),
1737 ),
1744 (
1738 (
1745 b'u',
1739 b'u',
1746 b'updaterev',
1740 b'updaterev',
1747 b'',
1741 b'',
1748 _(b'revision, tag, or branch to check out'),
1742 _(b'revision, tag, or branch to check out'),
1749 _(b'REV'),
1743 _(b'REV'),
1750 ),
1744 ),
1751 (
1745 (
1752 b'r',
1746 b'r',
1753 b'rev',
1747 b'rev',
1754 [],
1748 [],
1755 _(
1749 _(
1756 b'do not clone everything, but include this changeset'
1750 b'do not clone everything, but include this changeset'
1757 b' and its ancestors'
1751 b' and its ancestors'
1758 ),
1752 ),
1759 _(b'REV'),
1753 _(b'REV'),
1760 ),
1754 ),
1761 (
1755 (
1762 b'b',
1756 b'b',
1763 b'branch',
1757 b'branch',
1764 [],
1758 [],
1765 _(
1759 _(
1766 b'do not clone everything, but include this branch\'s'
1760 b'do not clone everything, but include this branch\'s'
1767 b' changesets and their ancestors'
1761 b' changesets and their ancestors'
1768 ),
1762 ),
1769 _(b'BRANCH'),
1763 _(b'BRANCH'),
1770 ),
1764 ),
1771 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1765 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1772 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1766 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1773 (b'', b'stream', None, _(b'clone with minimal data processing')),
1767 (b'', b'stream', None, _(b'clone with minimal data processing')),
1774 ]
1768 ]
1775 + remoteopts,
1769 + remoteopts,
1776 _(b'[OPTION]... SOURCE [DEST]'),
1770 _(b'[OPTION]... SOURCE [DEST]'),
1777 helpcategory=command.CATEGORY_REPO_CREATION,
1771 helpcategory=command.CATEGORY_REPO_CREATION,
1778 helpbasic=True,
1772 helpbasic=True,
1779 norepo=True,
1773 norepo=True,
1780 )
1774 )
1781 def clone(ui, source, dest=None, **opts):
1775 def clone(ui, source, dest=None, **opts):
1782 """make a copy of an existing repository
1776 """make a copy of an existing repository
1783
1777
1784 Create a copy of an existing repository in a new directory.
1778 Create a copy of an existing repository in a new directory.
1785
1779
1786 If no destination directory name is specified, it defaults to the
1780 If no destination directory name is specified, it defaults to the
1787 basename of the source.
1781 basename of the source.
1788
1782
1789 The location of the source is added to the new repository's
1783 The location of the source is added to the new repository's
1790 ``.hg/hgrc`` file, as the default to be used for future pulls.
1784 ``.hg/hgrc`` file, as the default to be used for future pulls.
1791
1785
1792 Only local paths and ``ssh://`` URLs are supported as
1786 Only local paths and ``ssh://`` URLs are supported as
1793 destinations. For ``ssh://`` destinations, no working directory or
1787 destinations. For ``ssh://`` destinations, no working directory or
1794 ``.hg/hgrc`` will be created on the remote side.
1788 ``.hg/hgrc`` will be created on the remote side.
1795
1789
1796 If the source repository has a bookmark called '@' set, that
1790 If the source repository has a bookmark called '@' set, that
1797 revision will be checked out in the new repository by default.
1791 revision will be checked out in the new repository by default.
1798
1792
1799 To check out a particular version, use -u/--update, or
1793 To check out a particular version, use -u/--update, or
1800 -U/--noupdate to create a clone with no working directory.
1794 -U/--noupdate to create a clone with no working directory.
1801
1795
1802 To pull only a subset of changesets, specify one or more revisions
1796 To pull only a subset of changesets, specify one or more revisions
1803 identifiers with -r/--rev or branches with -b/--branch. The
1797 identifiers with -r/--rev or branches with -b/--branch. The
1804 resulting clone will contain only the specified changesets and
1798 resulting clone will contain only the specified changesets and
1805 their ancestors. These options (or 'clone src#rev dest') imply
1799 their ancestors. These options (or 'clone src#rev dest') imply
1806 --pull, even for local source repositories.
1800 --pull, even for local source repositories.
1807
1801
1808 In normal clone mode, the remote normalizes repository data into a common
1802 In normal clone mode, the remote normalizes repository data into a common
1809 exchange format and the receiving end translates this data into its local
1803 exchange format and the receiving end translates this data into its local
1810 storage format. --stream activates a different clone mode that essentially
1804 storage format. --stream activates a different clone mode that essentially
1811 copies repository files from the remote with minimal data processing. This
1805 copies repository files from the remote with minimal data processing. This
1812 significantly reduces the CPU cost of a clone both remotely and locally.
1806 significantly reduces the CPU cost of a clone both remotely and locally.
1813 However, it often increases the transferred data size by 30-40%. This can
1807 However, it often increases the transferred data size by 30-40%. This can
1814 result in substantially faster clones where I/O throughput is plentiful,
1808 result in substantially faster clones where I/O throughput is plentiful,
1815 especially for larger repositories. A side-effect of --stream clones is
1809 especially for larger repositories. A side-effect of --stream clones is
1816 that storage settings and requirements on the remote are applied locally:
1810 that storage settings and requirements on the remote are applied locally:
1817 a modern client may inherit legacy or inefficient storage used by the
1811 a modern client may inherit legacy or inefficient storage used by the
1818 remote or a legacy Mercurial client may not be able to clone from a
1812 remote or a legacy Mercurial client may not be able to clone from a
1819 modern Mercurial remote.
1813 modern Mercurial remote.
1820
1814
1821 .. note::
1815 .. note::
1822
1816
1823 Specifying a tag will include the tagged changeset but not the
1817 Specifying a tag will include the tagged changeset but not the
1824 changeset containing the tag.
1818 changeset containing the tag.
1825
1819
1826 .. container:: verbose
1820 .. container:: verbose
1827
1821
1828 For efficiency, hardlinks are used for cloning whenever the
1822 For efficiency, hardlinks are used for cloning whenever the
1829 source and destination are on the same filesystem (note this
1823 source and destination are on the same filesystem (note this
1830 applies only to the repository data, not to the working
1824 applies only to the repository data, not to the working
1831 directory). Some filesystems, such as AFS, implement hardlinking
1825 directory). Some filesystems, such as AFS, implement hardlinking
1832 incorrectly, but do not report errors. In these cases, use the
1826 incorrectly, but do not report errors. In these cases, use the
1833 --pull option to avoid hardlinking.
1827 --pull option to avoid hardlinking.
1834
1828
1835 Mercurial will update the working directory to the first applicable
1829 Mercurial will update the working directory to the first applicable
1836 revision from this list:
1830 revision from this list:
1837
1831
1838 a) null if -U or the source repository has no changesets
1832 a) null if -U or the source repository has no changesets
1839 b) if -u . and the source repository is local, the first parent of
1833 b) if -u . and the source repository is local, the first parent of
1840 the source repository's working directory
1834 the source repository's working directory
1841 c) the changeset specified with -u (if a branch name, this means the
1835 c) the changeset specified with -u (if a branch name, this means the
1842 latest head of that branch)
1836 latest head of that branch)
1843 d) the changeset specified with -r
1837 d) the changeset specified with -r
1844 e) the tipmost head specified with -b
1838 e) the tipmost head specified with -b
1845 f) the tipmost head specified with the url#branch source syntax
1839 f) the tipmost head specified with the url#branch source syntax
1846 g) the revision marked with the '@' bookmark, if present
1840 g) the revision marked with the '@' bookmark, if present
1847 h) the tipmost head of the default branch
1841 h) the tipmost head of the default branch
1848 i) tip
1842 i) tip
1849
1843
1850 When cloning from servers that support it, Mercurial may fetch
1844 When cloning from servers that support it, Mercurial may fetch
1851 pre-generated data from a server-advertised URL or inline from the
1845 pre-generated data from a server-advertised URL or inline from the
1852 same stream. When this is done, hooks operating on incoming changesets
1846 same stream. When this is done, hooks operating on incoming changesets
1853 and changegroups may fire more than once, once for each pre-generated
1847 and changegroups may fire more than once, once for each pre-generated
1854 bundle and as well as for any additional remaining data. In addition,
1848 bundle and as well as for any additional remaining data. In addition,
1855 if an error occurs, the repository may be rolled back to a partial
1849 if an error occurs, the repository may be rolled back to a partial
1856 clone. This behavior may change in future releases.
1850 clone. This behavior may change in future releases.
1857 See :hg:`help -e clonebundles` for more.
1851 See :hg:`help -e clonebundles` for more.
1858
1852
1859 Examples:
1853 Examples:
1860
1854
1861 - clone a remote repository to a new directory named hg/::
1855 - clone a remote repository to a new directory named hg/::
1862
1856
1863 hg clone https://www.mercurial-scm.org/repo/hg/
1857 hg clone https://www.mercurial-scm.org/repo/hg/
1864
1858
1865 - create a lightweight local clone::
1859 - create a lightweight local clone::
1866
1860
1867 hg clone project/ project-feature/
1861 hg clone project/ project-feature/
1868
1862
1869 - clone from an absolute path on an ssh server (note double-slash)::
1863 - clone from an absolute path on an ssh server (note double-slash)::
1870
1864
1871 hg clone ssh://user@server//home/projects/alpha/
1865 hg clone ssh://user@server//home/projects/alpha/
1872
1866
1873 - do a streaming clone while checking out a specified version::
1867 - do a streaming clone while checking out a specified version::
1874
1868
1875 hg clone --stream http://server/repo -u 1.5
1869 hg clone --stream http://server/repo -u 1.5
1876
1870
1877 - create a repository without changesets after a particular revision::
1871 - create a repository without changesets after a particular revision::
1878
1872
1879 hg clone -r 04e544 experimental/ good/
1873 hg clone -r 04e544 experimental/ good/
1880
1874
1881 - clone (and track) a particular named branch::
1875 - clone (and track) a particular named branch::
1882
1876
1883 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1877 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1884
1878
1885 See :hg:`help urls` for details on specifying URLs.
1879 See :hg:`help urls` for details on specifying URLs.
1886
1880
1887 Returns 0 on success.
1881 Returns 0 on success.
1888 """
1882 """
1889 opts = pycompat.byteskwargs(opts)
1883 opts = pycompat.byteskwargs(opts)
1890 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1884 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1891
1885
1892 # --include/--exclude can come from narrow or sparse.
1886 # --include/--exclude can come from narrow or sparse.
1893 includepats, excludepats = None, None
1887 includepats, excludepats = None, None
1894
1888
1895 # hg.clone() differentiates between None and an empty set. So make sure
1889 # hg.clone() differentiates between None and an empty set. So make sure
1896 # patterns are sets if narrow is requested without patterns.
1890 # patterns are sets if narrow is requested without patterns.
1897 if opts.get(b'narrow'):
1891 if opts.get(b'narrow'):
1898 includepats = set()
1892 includepats = set()
1899 excludepats = set()
1893 excludepats = set()
1900
1894
1901 if opts.get(b'include'):
1895 if opts.get(b'include'):
1902 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1896 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1903 if opts.get(b'exclude'):
1897 if opts.get(b'exclude'):
1904 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1898 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1905
1899
1906 r = hg.clone(
1900 r = hg.clone(
1907 ui,
1901 ui,
1908 opts,
1902 opts,
1909 source,
1903 source,
1910 dest,
1904 dest,
1911 pull=opts.get(b'pull'),
1905 pull=opts.get(b'pull'),
1912 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1906 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1913 revs=opts.get(b'rev'),
1907 revs=opts.get(b'rev'),
1914 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1908 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1915 branch=opts.get(b'branch'),
1909 branch=opts.get(b'branch'),
1916 shareopts=opts.get(b'shareopts'),
1910 shareopts=opts.get(b'shareopts'),
1917 storeincludepats=includepats,
1911 storeincludepats=includepats,
1918 storeexcludepats=excludepats,
1912 storeexcludepats=excludepats,
1919 depth=opts.get(b'depth') or None,
1913 depth=opts.get(b'depth') or None,
1920 )
1914 )
1921
1915
1922 return r is None
1916 return r is None
1923
1917
1924
1918
1925 @command(
1919 @command(
1926 b'commit|ci',
1920 b'commit|ci',
1927 [
1921 [
1928 (
1922 (
1929 b'A',
1923 b'A',
1930 b'addremove',
1924 b'addremove',
1931 None,
1925 None,
1932 _(b'mark new/missing files as added/removed before committing'),
1926 _(b'mark new/missing files as added/removed before committing'),
1933 ),
1927 ),
1934 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1928 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1935 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1929 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1936 (b's', b'secret', None, _(b'use the secret phase for committing')),
1930 (b's', b'secret', None, _(b'use the secret phase for committing')),
1937 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1931 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1938 (
1932 (
1939 b'',
1933 b'',
1940 b'force-close-branch',
1934 b'force-close-branch',
1941 None,
1935 None,
1942 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1936 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1943 ),
1937 ),
1944 (b'i', b'interactive', None, _(b'use interactive mode')),
1938 (b'i', b'interactive', None, _(b'use interactive mode')),
1945 ]
1939 ]
1946 + walkopts
1940 + walkopts
1947 + commitopts
1941 + commitopts
1948 + commitopts2
1942 + commitopts2
1949 + subrepoopts,
1943 + subrepoopts,
1950 _(b'[OPTION]... [FILE]...'),
1944 _(b'[OPTION]... [FILE]...'),
1951 helpcategory=command.CATEGORY_COMMITTING,
1945 helpcategory=command.CATEGORY_COMMITTING,
1952 helpbasic=True,
1946 helpbasic=True,
1953 inferrepo=True,
1947 inferrepo=True,
1954 )
1948 )
1955 def commit(ui, repo, *pats, **opts):
1949 def commit(ui, repo, *pats, **opts):
1956 """commit the specified files or all outstanding changes
1950 """commit the specified files or all outstanding changes
1957
1951
1958 Commit changes to the given files into the repository. Unlike a
1952 Commit changes to the given files into the repository. Unlike a
1959 centralized SCM, this operation is a local operation. See
1953 centralized SCM, this operation is a local operation. See
1960 :hg:`push` for a way to actively distribute your changes.
1954 :hg:`push` for a way to actively distribute your changes.
1961
1955
1962 If a list of files is omitted, all changes reported by :hg:`status`
1956 If a list of files is omitted, all changes reported by :hg:`status`
1963 will be committed.
1957 will be committed.
1964
1958
1965 If you are committing the result of a merge, do not provide any
1959 If you are committing the result of a merge, do not provide any
1966 filenames or -I/-X filters.
1960 filenames or -I/-X filters.
1967
1961
1968 If no commit message is specified, Mercurial starts your
1962 If no commit message is specified, Mercurial starts your
1969 configured editor where you can enter a message. In case your
1963 configured editor where you can enter a message. In case your
1970 commit fails, you will find a backup of your message in
1964 commit fails, you will find a backup of your message in
1971 ``.hg/last-message.txt``.
1965 ``.hg/last-message.txt``.
1972
1966
1973 The --close-branch flag can be used to mark the current branch
1967 The --close-branch flag can be used to mark the current branch
1974 head closed. When all heads of a branch are closed, the branch
1968 head closed. When all heads of a branch are closed, the branch
1975 will be considered closed and no longer listed.
1969 will be considered closed and no longer listed.
1976
1970
1977 The --amend flag can be used to amend the parent of the
1971 The --amend flag can be used to amend the parent of the
1978 working directory with a new commit that contains the changes
1972 working directory with a new commit that contains the changes
1979 in the parent in addition to those currently reported by :hg:`status`,
1973 in the parent in addition to those currently reported by :hg:`status`,
1980 if there are any. The old commit is stored in a backup bundle in
1974 if there are any. The old commit is stored in a backup bundle in
1981 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1975 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1982 on how to restore it).
1976 on how to restore it).
1983
1977
1984 Message, user and date are taken from the amended commit unless
1978 Message, user and date are taken from the amended commit unless
1985 specified. When a message isn't specified on the command line,
1979 specified. When a message isn't specified on the command line,
1986 the editor will open with the message of the amended commit.
1980 the editor will open with the message of the amended commit.
1987
1981
1988 It is not possible to amend public changesets (see :hg:`help phases`)
1982 It is not possible to amend public changesets (see :hg:`help phases`)
1989 or changesets that have children.
1983 or changesets that have children.
1990
1984
1991 See :hg:`help dates` for a list of formats valid for -d/--date.
1985 See :hg:`help dates` for a list of formats valid for -d/--date.
1992
1986
1993 Returns 0 on success, 1 if nothing changed.
1987 Returns 0 on success, 1 if nothing changed.
1994
1988
1995 .. container:: verbose
1989 .. container:: verbose
1996
1990
1997 Examples:
1991 Examples:
1998
1992
1999 - commit all files ending in .py::
1993 - commit all files ending in .py::
2000
1994
2001 hg commit --include "set:**.py"
1995 hg commit --include "set:**.py"
2002
1996
2003 - commit all non-binary files::
1997 - commit all non-binary files::
2004
1998
2005 hg commit --exclude "set:binary()"
1999 hg commit --exclude "set:binary()"
2006
2000
2007 - amend the current commit and set the date to now::
2001 - amend the current commit and set the date to now::
2008
2002
2009 hg commit --amend --date now
2003 hg commit --amend --date now
2010 """
2004 """
2011 with repo.wlock(), repo.lock():
2005 with repo.wlock(), repo.lock():
2012 return _docommit(ui, repo, *pats, **opts)
2006 return _docommit(ui, repo, *pats, **opts)
2013
2007
2014
2008
2015 def _docommit(ui, repo, *pats, **opts):
2009 def _docommit(ui, repo, *pats, **opts):
2016 if opts.get('interactive'):
2010 if opts.get('interactive'):
2017 opts.pop('interactive')
2011 opts.pop('interactive')
2018 ret = cmdutil.dorecord(
2012 ret = cmdutil.dorecord(
2019 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2013 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2020 )
2014 )
2021 # ret can be 0 (no changes to record) or the value returned by
2015 # ret can be 0 (no changes to record) or the value returned by
2022 # commit(), 1 if nothing changed or None on success.
2016 # commit(), 1 if nothing changed or None on success.
2023 return 1 if ret == 0 else ret
2017 return 1 if ret == 0 else ret
2024
2018
2025 opts = pycompat.byteskwargs(opts)
2019 opts = pycompat.byteskwargs(opts)
2026 if opts.get(b'subrepos'):
2020 if opts.get(b'subrepos'):
2027 if opts.get(b'amend'):
2021 if opts.get(b'amend'):
2028 raise error.Abort(_(b'cannot amend with --subrepos'))
2022 raise error.Abort(_(b'cannot amend with --subrepos'))
2029 # Let --subrepos on the command line override config setting.
2023 # Let --subrepos on the command line override config setting.
2030 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2024 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2031
2025
2032 cmdutil.checkunfinished(repo, commit=True)
2026 cmdutil.checkunfinished(repo, commit=True)
2033
2027
2034 branch = repo[None].branch()
2028 branch = repo[None].branch()
2035 bheads = repo.branchheads(branch)
2029 bheads = repo.branchheads(branch)
2036
2030
2037 extra = {}
2031 extra = {}
2038 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2032 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2039 extra[b'close'] = b'1'
2033 extra[b'close'] = b'1'
2040
2034
2041 if repo[b'.'].closesbranch():
2035 if repo[b'.'].closesbranch():
2042 raise error.Abort(
2036 raise error.Abort(
2043 _(b'current revision is already a branch closing head')
2037 _(b'current revision is already a branch closing head')
2044 )
2038 )
2045 elif not bheads:
2039 elif not bheads:
2046 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2040 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2047 elif (
2041 elif (
2048 branch == repo[b'.'].branch()
2042 branch == repo[b'.'].branch()
2049 and repo[b'.'].node() not in bheads
2043 and repo[b'.'].node() not in bheads
2050 and not opts.get(b'force_close_branch')
2044 and not opts.get(b'force_close_branch')
2051 ):
2045 ):
2052 hint = _(
2046 hint = _(
2053 b'use --force-close-branch to close branch from a non-head'
2047 b'use --force-close-branch to close branch from a non-head'
2054 b' changeset'
2048 b' changeset'
2055 )
2049 )
2056 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2050 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2057 elif opts.get(b'amend'):
2051 elif opts.get(b'amend'):
2058 if (
2052 if (
2059 repo[b'.'].p1().branch() != branch
2053 repo[b'.'].p1().branch() != branch
2060 and repo[b'.'].p2().branch() != branch
2054 and repo[b'.'].p2().branch() != branch
2061 ):
2055 ):
2062 raise error.Abort(_(b'can only close branch heads'))
2056 raise error.Abort(_(b'can only close branch heads'))
2063
2057
2064 if opts.get(b'amend'):
2058 if opts.get(b'amend'):
2065 if ui.configbool(b'ui', b'commitsubrepos'):
2059 if ui.configbool(b'ui', b'commitsubrepos'):
2066 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2060 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2067
2061
2068 old = repo[b'.']
2062 old = repo[b'.']
2069 rewriteutil.precheck(repo, [old.rev()], b'amend')
2063 rewriteutil.precheck(repo, [old.rev()], b'amend')
2070
2064
2071 # Currently histedit gets confused if an amend happens while histedit
2065 # Currently histedit gets confused if an amend happens while histedit
2072 # is in progress. Since we have a checkunfinished command, we are
2066 # is in progress. Since we have a checkunfinished command, we are
2073 # temporarily honoring it.
2067 # temporarily honoring it.
2074 #
2068 #
2075 # Note: eventually this guard will be removed. Please do not expect
2069 # Note: eventually this guard will be removed. Please do not expect
2076 # this behavior to remain.
2070 # this behavior to remain.
2077 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2071 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2078 cmdutil.checkunfinished(repo)
2072 cmdutil.checkunfinished(repo)
2079
2073
2080 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2074 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2081 if node == old.node():
2075 if node == old.node():
2082 ui.status(_(b"nothing changed\n"))
2076 ui.status(_(b"nothing changed\n"))
2083 return 1
2077 return 1
2084 else:
2078 else:
2085
2079
2086 def commitfunc(ui, repo, message, match, opts):
2080 def commitfunc(ui, repo, message, match, opts):
2087 overrides = {}
2081 overrides = {}
2088 if opts.get(b'secret'):
2082 if opts.get(b'secret'):
2089 overrides[(b'phases', b'new-commit')] = b'secret'
2083 overrides[(b'phases', b'new-commit')] = b'secret'
2090
2084
2091 baseui = repo.baseui
2085 baseui = repo.baseui
2092 with baseui.configoverride(overrides, b'commit'):
2086 with baseui.configoverride(overrides, b'commit'):
2093 with ui.configoverride(overrides, b'commit'):
2087 with ui.configoverride(overrides, b'commit'):
2094 editform = cmdutil.mergeeditform(
2088 editform = cmdutil.mergeeditform(
2095 repo[None], b'commit.normal'
2089 repo[None], b'commit.normal'
2096 )
2090 )
2097 editor = cmdutil.getcommiteditor(
2091 editor = cmdutil.getcommiteditor(
2098 editform=editform, **pycompat.strkwargs(opts)
2092 editform=editform, **pycompat.strkwargs(opts)
2099 )
2093 )
2100 return repo.commit(
2094 return repo.commit(
2101 message,
2095 message,
2102 opts.get(b'user'),
2096 opts.get(b'user'),
2103 opts.get(b'date'),
2097 opts.get(b'date'),
2104 match,
2098 match,
2105 editor=editor,
2099 editor=editor,
2106 extra=extra,
2100 extra=extra,
2107 )
2101 )
2108
2102
2109 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2103 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2110
2104
2111 if not node:
2105 if not node:
2112 stat = cmdutil.postcommitstatus(repo, pats, opts)
2106 stat = cmdutil.postcommitstatus(repo, pats, opts)
2113 if stat.deleted:
2107 if stat.deleted:
2114 ui.status(
2108 ui.status(
2115 _(
2109 _(
2116 b"nothing changed (%d missing files, see "
2110 b"nothing changed (%d missing files, see "
2117 b"'hg status')\n"
2111 b"'hg status')\n"
2118 )
2112 )
2119 % len(stat.deleted)
2113 % len(stat.deleted)
2120 )
2114 )
2121 else:
2115 else:
2122 ui.status(_(b"nothing changed\n"))
2116 ui.status(_(b"nothing changed\n"))
2123 return 1
2117 return 1
2124
2118
2125 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2119 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2126
2120
2127 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2121 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2128 status(
2122 status(
2129 ui,
2123 ui,
2130 repo,
2124 repo,
2131 modified=True,
2125 modified=True,
2132 added=True,
2126 added=True,
2133 removed=True,
2127 removed=True,
2134 deleted=True,
2128 deleted=True,
2135 unknown=True,
2129 unknown=True,
2136 subrepos=opts.get(b'subrepos'),
2130 subrepos=opts.get(b'subrepos'),
2137 )
2131 )
2138
2132
2139
2133
2140 @command(
2134 @command(
2141 b'config|showconfig|debugconfig',
2135 b'config|showconfig|debugconfig',
2142 [
2136 [
2143 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2137 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2144 (b'e', b'edit', None, _(b'edit user config')),
2138 (b'e', b'edit', None, _(b'edit user config')),
2145 (b'l', b'local', None, _(b'edit repository config')),
2139 (b'l', b'local', None, _(b'edit repository config')),
2146 (
2140 (
2147 b'',
2141 b'',
2148 b'shared',
2142 b'shared',
2149 None,
2143 None,
2150 _(b'edit shared source repository config (EXPERIMENTAL)'),
2144 _(b'edit shared source repository config (EXPERIMENTAL)'),
2151 ),
2145 ),
2152 (b'g', b'global', None, _(b'edit global config')),
2146 (b'g', b'global', None, _(b'edit global config')),
2153 ]
2147 ]
2154 + formatteropts,
2148 + formatteropts,
2155 _(b'[-u] [NAME]...'),
2149 _(b'[-u] [NAME]...'),
2156 helpcategory=command.CATEGORY_HELP,
2150 helpcategory=command.CATEGORY_HELP,
2157 optionalrepo=True,
2151 optionalrepo=True,
2158 intents={INTENT_READONLY},
2152 intents={INTENT_READONLY},
2159 )
2153 )
2160 def config(ui, repo, *values, **opts):
2154 def config(ui, repo, *values, **opts):
2161 """show combined config settings from all hgrc files
2155 """show combined config settings from all hgrc files
2162
2156
2163 With no arguments, print names and values of all config items.
2157 With no arguments, print names and values of all config items.
2164
2158
2165 With one argument of the form section.name, print just the value
2159 With one argument of the form section.name, print just the value
2166 of that config item.
2160 of that config item.
2167
2161
2168 With multiple arguments, print names and values of all config
2162 With multiple arguments, print names and values of all config
2169 items with matching section names or section.names.
2163 items with matching section names or section.names.
2170
2164
2171 With --edit, start an editor on the user-level config file. With
2165 With --edit, start an editor on the user-level config file. With
2172 --global, edit the system-wide config file. With --local, edit the
2166 --global, edit the system-wide config file. With --local, edit the
2173 repository-level config file.
2167 repository-level config file.
2174
2168
2175 With --debug, the source (filename and line number) is printed
2169 With --debug, the source (filename and line number) is printed
2176 for each config item.
2170 for each config item.
2177
2171
2178 See :hg:`help config` for more information about config files.
2172 See :hg:`help config` for more information about config files.
2179
2173
2180 .. container:: verbose
2174 .. container:: verbose
2181
2175
2182 Template:
2176 Template:
2183
2177
2184 The following keywords are supported. See also :hg:`help templates`.
2178 The following keywords are supported. See also :hg:`help templates`.
2185
2179
2186 :name: String. Config name.
2180 :name: String. Config name.
2187 :source: String. Filename and line number where the item is defined.
2181 :source: String. Filename and line number where the item is defined.
2188 :value: String. Config value.
2182 :value: String. Config value.
2189
2183
2190 The --shared flag can be used to edit the config file of shared source
2184 The --shared flag can be used to edit the config file of shared source
2191 repository. It only works when you have shared using the experimental
2185 repository. It only works when you have shared using the experimental
2192 share safe feature.
2186 share safe feature.
2193
2187
2194 Returns 0 on success, 1 if NAME does not exist.
2188 Returns 0 on success, 1 if NAME does not exist.
2195
2189
2196 """
2190 """
2197
2191
2198 opts = pycompat.byteskwargs(opts)
2192 opts = pycompat.byteskwargs(opts)
2199 editopts = (b'edit', b'local', b'global', b'shared')
2193 editopts = (b'edit', b'local', b'global', b'shared')
2200 if any(opts.get(o) for o in editopts):
2194 if any(opts.get(o) for o in editopts):
2201 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2195 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2202 if opts.get(b'local'):
2196 if opts.get(b'local'):
2203 if not repo:
2197 if not repo:
2204 raise error.Abort(_(b"can't use --local outside a repository"))
2198 raise error.Abort(_(b"can't use --local outside a repository"))
2205 paths = [repo.vfs.join(b'hgrc')]
2199 paths = [repo.vfs.join(b'hgrc')]
2206 elif opts.get(b'global'):
2200 elif opts.get(b'global'):
2207 paths = rcutil.systemrcpath()
2201 paths = rcutil.systemrcpath()
2208 elif opts.get(b'shared'):
2202 elif opts.get(b'shared'):
2209 if not repo.shared():
2203 if not repo.shared():
2210 raise error.Abort(
2204 raise error.Abort(
2211 _(b"repository is not shared; can't use --shared")
2205 _(b"repository is not shared; can't use --shared")
2212 )
2206 )
2213 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2207 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2214 raise error.Abort(
2208 raise error.Abort(
2215 _(
2209 _(
2216 b"share safe feature not unabled; "
2210 b"share safe feature not unabled; "
2217 b"unable to edit shared source repository config"
2211 b"unable to edit shared source repository config"
2218 )
2212 )
2219 )
2213 )
2220 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2214 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2221 else:
2215 else:
2222 paths = rcutil.userrcpath()
2216 paths = rcutil.userrcpath()
2223
2217
2224 for f in paths:
2218 for f in paths:
2225 if os.path.exists(f):
2219 if os.path.exists(f):
2226 break
2220 break
2227 else:
2221 else:
2228 if opts.get(b'global'):
2222 if opts.get(b'global'):
2229 samplehgrc = uimod.samplehgrcs[b'global']
2223 samplehgrc = uimod.samplehgrcs[b'global']
2230 elif opts.get(b'local'):
2224 elif opts.get(b'local'):
2231 samplehgrc = uimod.samplehgrcs[b'local']
2225 samplehgrc = uimod.samplehgrcs[b'local']
2232 else:
2226 else:
2233 samplehgrc = uimod.samplehgrcs[b'user']
2227 samplehgrc = uimod.samplehgrcs[b'user']
2234
2228
2235 f = paths[0]
2229 f = paths[0]
2236 fp = open(f, b"wb")
2230 fp = open(f, b"wb")
2237 fp.write(util.tonativeeol(samplehgrc))
2231 fp.write(util.tonativeeol(samplehgrc))
2238 fp.close()
2232 fp.close()
2239
2233
2240 editor = ui.geteditor()
2234 editor = ui.geteditor()
2241 ui.system(
2235 ui.system(
2242 b"%s \"%s\"" % (editor, f),
2236 b"%s \"%s\"" % (editor, f),
2243 onerr=error.Abort,
2237 onerr=error.Abort,
2244 errprefix=_(b"edit failed"),
2238 errprefix=_(b"edit failed"),
2245 blockedtag=b'config_edit',
2239 blockedtag=b'config_edit',
2246 )
2240 )
2247 return
2241 return
2248 ui.pager(b'config')
2242 ui.pager(b'config')
2249 fm = ui.formatter(b'config', opts)
2243 fm = ui.formatter(b'config', opts)
2250 for t, f in rcutil.rccomponents():
2244 for t, f in rcutil.rccomponents():
2251 if t == b'path':
2245 if t == b'path':
2252 ui.debug(b'read config from: %s\n' % f)
2246 ui.debug(b'read config from: %s\n' % f)
2253 elif t == b'resource':
2247 elif t == b'resource':
2254 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2248 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2255 elif t == b'items':
2249 elif t == b'items':
2256 # Don't print anything for 'items'.
2250 # Don't print anything for 'items'.
2257 pass
2251 pass
2258 else:
2252 else:
2259 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2253 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2260 untrusted = bool(opts.get(b'untrusted'))
2254 untrusted = bool(opts.get(b'untrusted'))
2261
2255
2262 selsections = selentries = []
2256 selsections = selentries = []
2263 if values:
2257 if values:
2264 selsections = [v for v in values if b'.' not in v]
2258 selsections = [v for v in values if b'.' not in v]
2265 selentries = [v for v in values if b'.' in v]
2259 selentries = [v for v in values if b'.' in v]
2266 uniquesel = len(selentries) == 1 and not selsections
2260 uniquesel = len(selentries) == 1 and not selsections
2267 selsections = set(selsections)
2261 selsections = set(selsections)
2268 selentries = set(selentries)
2262 selentries = set(selentries)
2269
2263
2270 matched = False
2264 matched = False
2271 for section, name, value in ui.walkconfig(untrusted=untrusted):
2265 for section, name, value in ui.walkconfig(untrusted=untrusted):
2272 source = ui.configsource(section, name, untrusted)
2266 source = ui.configsource(section, name, untrusted)
2273 value = pycompat.bytestr(value)
2267 value = pycompat.bytestr(value)
2274 defaultvalue = ui.configdefault(section, name)
2268 defaultvalue = ui.configdefault(section, name)
2275 if fm.isplain():
2269 if fm.isplain():
2276 source = source or b'none'
2270 source = source or b'none'
2277 value = value.replace(b'\n', b'\\n')
2271 value = value.replace(b'\n', b'\\n')
2278 entryname = section + b'.' + name
2272 entryname = section + b'.' + name
2279 if values and not (section in selsections or entryname in selentries):
2273 if values and not (section in selsections or entryname in selentries):
2280 continue
2274 continue
2281 fm.startitem()
2275 fm.startitem()
2282 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2276 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2283 if uniquesel:
2277 if uniquesel:
2284 fm.data(name=entryname)
2278 fm.data(name=entryname)
2285 fm.write(b'value', b'%s\n', value)
2279 fm.write(b'value', b'%s\n', value)
2286 else:
2280 else:
2287 fm.write(b'name value', b'%s=%s\n', entryname, value)
2281 fm.write(b'name value', b'%s=%s\n', entryname, value)
2288 if formatter.isprintable(defaultvalue):
2282 if formatter.isprintable(defaultvalue):
2289 fm.data(defaultvalue=defaultvalue)
2283 fm.data(defaultvalue=defaultvalue)
2290 elif isinstance(defaultvalue, list) and all(
2284 elif isinstance(defaultvalue, list) and all(
2291 formatter.isprintable(e) for e in defaultvalue
2285 formatter.isprintable(e) for e in defaultvalue
2292 ):
2286 ):
2293 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2287 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2294 # TODO: no idea how to process unsupported defaultvalue types
2288 # TODO: no idea how to process unsupported defaultvalue types
2295 matched = True
2289 matched = True
2296 fm.end()
2290 fm.end()
2297 if matched:
2291 if matched:
2298 return 0
2292 return 0
2299 return 1
2293 return 1
2300
2294
2301
2295
2302 @command(
2296 @command(
2303 b'continue',
2297 b'continue',
2304 dryrunopts,
2298 dryrunopts,
2305 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2299 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2306 helpbasic=True,
2300 helpbasic=True,
2307 )
2301 )
2308 def continuecmd(ui, repo, **opts):
2302 def continuecmd(ui, repo, **opts):
2309 """resumes an interrupted operation (EXPERIMENTAL)
2303 """resumes an interrupted operation (EXPERIMENTAL)
2310
2304
2311 Finishes a multistep operation like graft, histedit, rebase, merge,
2305 Finishes a multistep operation like graft, histedit, rebase, merge,
2312 and unshelve if they are in an interrupted state.
2306 and unshelve if they are in an interrupted state.
2313
2307
2314 use --dry-run/-n to dry run the command.
2308 use --dry-run/-n to dry run the command.
2315 """
2309 """
2316 dryrun = opts.get('dry_run')
2310 dryrun = opts.get('dry_run')
2317 contstate = cmdutil.getunfinishedstate(repo)
2311 contstate = cmdutil.getunfinishedstate(repo)
2318 if not contstate:
2312 if not contstate:
2319 raise error.Abort(_(b'no operation in progress'))
2313 raise error.Abort(_(b'no operation in progress'))
2320 if not contstate.continuefunc:
2314 if not contstate.continuefunc:
2321 raise error.Abort(
2315 raise error.Abort(
2322 (
2316 (
2323 _(b"%s in progress but does not support 'hg continue'")
2317 _(b"%s in progress but does not support 'hg continue'")
2324 % (contstate._opname)
2318 % (contstate._opname)
2325 ),
2319 ),
2326 hint=contstate.continuemsg(),
2320 hint=contstate.continuemsg(),
2327 )
2321 )
2328 if dryrun:
2322 if dryrun:
2329 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2323 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2330 return
2324 return
2331 return contstate.continuefunc(ui, repo)
2325 return contstate.continuefunc(ui, repo)
2332
2326
2333
2327
2334 @command(
2328 @command(
2335 b'copy|cp',
2329 b'copy|cp',
2336 [
2330 [
2337 (b'', b'forget', None, _(b'unmark a file as copied')),
2331 (b'', b'forget', None, _(b'unmark a file as copied')),
2338 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2332 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2339 (
2333 (
2340 b'',
2334 b'',
2341 b'at-rev',
2335 b'at-rev',
2342 b'',
2336 b'',
2343 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2337 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2344 _(b'REV'),
2338 _(b'REV'),
2345 ),
2339 ),
2346 (
2340 (
2347 b'f',
2341 b'f',
2348 b'force',
2342 b'force',
2349 None,
2343 None,
2350 _(b'forcibly copy over an existing managed file'),
2344 _(b'forcibly copy over an existing managed file'),
2351 ),
2345 ),
2352 ]
2346 ]
2353 + walkopts
2347 + walkopts
2354 + dryrunopts,
2348 + dryrunopts,
2355 _(b'[OPTION]... SOURCE... DEST'),
2349 _(b'[OPTION]... SOURCE... DEST'),
2356 helpcategory=command.CATEGORY_FILE_CONTENTS,
2350 helpcategory=command.CATEGORY_FILE_CONTENTS,
2357 )
2351 )
2358 def copy(ui, repo, *pats, **opts):
2352 def copy(ui, repo, *pats, **opts):
2359 """mark files as copied for the next commit
2353 """mark files as copied for the next commit
2360
2354
2361 Mark dest as having copies of source files. If dest is a
2355 Mark dest as having copies of source files. If dest is a
2362 directory, copies are put in that directory. If dest is a file,
2356 directory, copies are put in that directory. If dest is a file,
2363 the source must be a single file.
2357 the source must be a single file.
2364
2358
2365 By default, this command copies the contents of files as they
2359 By default, this command copies the contents of files as they
2366 exist in the working directory. If invoked with -A/--after, the
2360 exist in the working directory. If invoked with -A/--after, the
2367 operation is recorded, but no copying is performed.
2361 operation is recorded, but no copying is performed.
2368
2362
2369 To undo marking a file as copied, use --forget. With that option,
2363 To undo marking a file as copied, use --forget. With that option,
2370 all given (positional) arguments are unmarked as copies. The destination
2364 all given (positional) arguments are unmarked as copies. The destination
2371 file(s) will be left in place (still tracked).
2365 file(s) will be left in place (still tracked).
2372
2366
2373 This command takes effect with the next commit by default.
2367 This command takes effect with the next commit by default.
2374
2368
2375 Returns 0 on success, 1 if errors are encountered.
2369 Returns 0 on success, 1 if errors are encountered.
2376 """
2370 """
2377 opts = pycompat.byteskwargs(opts)
2371 opts = pycompat.byteskwargs(opts)
2378 with repo.wlock():
2372 with repo.wlock():
2379 return cmdutil.copy(ui, repo, pats, opts)
2373 return cmdutil.copy(ui, repo, pats, opts)
2380
2374
2381
2375
2382 @command(
2376 @command(
2383 b'debugcommands',
2377 b'debugcommands',
2384 [],
2378 [],
2385 _(b'[COMMAND]'),
2379 _(b'[COMMAND]'),
2386 helpcategory=command.CATEGORY_HELP,
2380 helpcategory=command.CATEGORY_HELP,
2387 norepo=True,
2381 norepo=True,
2388 )
2382 )
2389 def debugcommands(ui, cmd=b'', *args):
2383 def debugcommands(ui, cmd=b'', *args):
2390 """list all available commands and options"""
2384 """list all available commands and options"""
2391 for cmd, vals in sorted(pycompat.iteritems(table)):
2385 for cmd, vals in sorted(pycompat.iteritems(table)):
2392 cmd = cmd.split(b'|')[0]
2386 cmd = cmd.split(b'|')[0]
2393 opts = b', '.join([i[1] for i in vals[1]])
2387 opts = b', '.join([i[1] for i in vals[1]])
2394 ui.write(b'%s: %s\n' % (cmd, opts))
2388 ui.write(b'%s: %s\n' % (cmd, opts))
2395
2389
2396
2390
2397 @command(
2391 @command(
2398 b'debugcomplete',
2392 b'debugcomplete',
2399 [(b'o', b'options', None, _(b'show the command options'))],
2393 [(b'o', b'options', None, _(b'show the command options'))],
2400 _(b'[-o] CMD'),
2394 _(b'[-o] CMD'),
2401 helpcategory=command.CATEGORY_HELP,
2395 helpcategory=command.CATEGORY_HELP,
2402 norepo=True,
2396 norepo=True,
2403 )
2397 )
2404 def debugcomplete(ui, cmd=b'', **opts):
2398 def debugcomplete(ui, cmd=b'', **opts):
2405 """returns the completion list associated with the given command"""
2399 """returns the completion list associated with the given command"""
2406
2400
2407 if opts.get('options'):
2401 if opts.get('options'):
2408 options = []
2402 options = []
2409 otables = [globalopts]
2403 otables = [globalopts]
2410 if cmd:
2404 if cmd:
2411 aliases, entry = cmdutil.findcmd(cmd, table, False)
2405 aliases, entry = cmdutil.findcmd(cmd, table, False)
2412 otables.append(entry[1])
2406 otables.append(entry[1])
2413 for t in otables:
2407 for t in otables:
2414 for o in t:
2408 for o in t:
2415 if b"(DEPRECATED)" in o[3]:
2409 if b"(DEPRECATED)" in o[3]:
2416 continue
2410 continue
2417 if o[0]:
2411 if o[0]:
2418 options.append(b'-%s' % o[0])
2412 options.append(b'-%s' % o[0])
2419 options.append(b'--%s' % o[1])
2413 options.append(b'--%s' % o[1])
2420 ui.write(b"%s\n" % b"\n".join(options))
2414 ui.write(b"%s\n" % b"\n".join(options))
2421 return
2415 return
2422
2416
2423 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2417 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2424 if ui.verbose:
2418 if ui.verbose:
2425 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2419 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2426 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2420 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2427
2421
2428
2422
2429 @command(
2423 @command(
2430 b'diff',
2424 b'diff',
2431 [
2425 [
2432 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2426 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2433 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2427 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2434 ]
2428 ]
2435 + diffopts
2429 + diffopts
2436 + diffopts2
2430 + diffopts2
2437 + walkopts
2431 + walkopts
2438 + subrepoopts,
2432 + subrepoopts,
2439 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2433 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2440 helpcategory=command.CATEGORY_FILE_CONTENTS,
2434 helpcategory=command.CATEGORY_FILE_CONTENTS,
2441 helpbasic=True,
2435 helpbasic=True,
2442 inferrepo=True,
2436 inferrepo=True,
2443 intents={INTENT_READONLY},
2437 intents={INTENT_READONLY},
2444 )
2438 )
2445 def diff(ui, repo, *pats, **opts):
2439 def diff(ui, repo, *pats, **opts):
2446 """diff repository (or selected files)
2440 """diff repository (or selected files)
2447
2441
2448 Show differences between revisions for the specified files.
2442 Show differences between revisions for the specified files.
2449
2443
2450 Differences between files are shown using the unified diff format.
2444 Differences between files are shown using the unified diff format.
2451
2445
2452 .. note::
2446 .. note::
2453
2447
2454 :hg:`diff` may generate unexpected results for merges, as it will
2448 :hg:`diff` may generate unexpected results for merges, as it will
2455 default to comparing against the working directory's first
2449 default to comparing against the working directory's first
2456 parent changeset if no revisions are specified.
2450 parent changeset if no revisions are specified.
2457
2451
2458 When two revision arguments are given, then changes are shown
2452 When two revision arguments are given, then changes are shown
2459 between those revisions. If only one revision is specified then
2453 between those revisions. If only one revision is specified then
2460 that revision is compared to the working directory, and, when no
2454 that revision is compared to the working directory, and, when no
2461 revisions are specified, the working directory files are compared
2455 revisions are specified, the working directory files are compared
2462 to its first parent.
2456 to its first parent.
2463
2457
2464 Alternatively you can specify -c/--change with a revision to see
2458 Alternatively you can specify -c/--change with a revision to see
2465 the changes in that changeset relative to its first parent.
2459 the changes in that changeset relative to its first parent.
2466
2460
2467 Without the -a/--text option, diff will avoid generating diffs of
2461 Without the -a/--text option, diff will avoid generating diffs of
2468 files it detects as binary. With -a, diff will generate a diff
2462 files it detects as binary. With -a, diff will generate a diff
2469 anyway, probably with undesirable results.
2463 anyway, probably with undesirable results.
2470
2464
2471 Use the -g/--git option to generate diffs in the git extended diff
2465 Use the -g/--git option to generate diffs in the git extended diff
2472 format. For more information, read :hg:`help diffs`.
2466 format. For more information, read :hg:`help diffs`.
2473
2467
2474 .. container:: verbose
2468 .. container:: verbose
2475
2469
2476 Examples:
2470 Examples:
2477
2471
2478 - compare a file in the current working directory to its parent::
2472 - compare a file in the current working directory to its parent::
2479
2473
2480 hg diff foo.c
2474 hg diff foo.c
2481
2475
2482 - compare two historical versions of a directory, with rename info::
2476 - compare two historical versions of a directory, with rename info::
2483
2477
2484 hg diff --git -r 1.0:1.2 lib/
2478 hg diff --git -r 1.0:1.2 lib/
2485
2479
2486 - get change stats relative to the last change on some date::
2480 - get change stats relative to the last change on some date::
2487
2481
2488 hg diff --stat -r "date('may 2')"
2482 hg diff --stat -r "date('may 2')"
2489
2483
2490 - diff all newly-added files that contain a keyword::
2484 - diff all newly-added files that contain a keyword::
2491
2485
2492 hg diff "set:added() and grep(GNU)"
2486 hg diff "set:added() and grep(GNU)"
2493
2487
2494 - compare a revision and its parents::
2488 - compare a revision and its parents::
2495
2489
2496 hg diff -c 9353 # compare against first parent
2490 hg diff -c 9353 # compare against first parent
2497 hg diff -r 9353^:9353 # same using revset syntax
2491 hg diff -r 9353^:9353 # same using revset syntax
2498 hg diff -r 9353^2:9353 # compare against the second parent
2492 hg diff -r 9353^2:9353 # compare against the second parent
2499
2493
2500 Returns 0 on success.
2494 Returns 0 on success.
2501 """
2495 """
2502
2496
2503 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2497 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2504 opts = pycompat.byteskwargs(opts)
2498 opts = pycompat.byteskwargs(opts)
2505 revs = opts.get(b'rev')
2499 revs = opts.get(b'rev')
2506 change = opts.get(b'change')
2500 change = opts.get(b'change')
2507 stat = opts.get(b'stat')
2501 stat = opts.get(b'stat')
2508 reverse = opts.get(b'reverse')
2502 reverse = opts.get(b'reverse')
2509
2503
2510 if change:
2504 if change:
2511 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2505 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2512 ctx2 = scmutil.revsingle(repo, change, None)
2506 ctx2 = scmutil.revsingle(repo, change, None)
2513 ctx1 = ctx2.p1()
2507 ctx1 = ctx2.p1()
2514 else:
2508 else:
2515 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2509 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2516 ctx1, ctx2 = scmutil.revpair(repo, revs)
2510 ctx1, ctx2 = scmutil.revpair(repo, revs)
2517
2511
2518 if reverse:
2512 if reverse:
2519 ctxleft = ctx2
2513 ctxleft = ctx2
2520 ctxright = ctx1
2514 ctxright = ctx1
2521 else:
2515 else:
2522 ctxleft = ctx1
2516 ctxleft = ctx1
2523 ctxright = ctx2
2517 ctxright = ctx2
2524
2518
2525 diffopts = patch.diffallopts(ui, opts)
2519 diffopts = patch.diffallopts(ui, opts)
2526 m = scmutil.match(ctx2, pats, opts)
2520 m = scmutil.match(ctx2, pats, opts)
2527 m = repo.narrowmatch(m)
2521 m = repo.narrowmatch(m)
2528 ui.pager(b'diff')
2522 ui.pager(b'diff')
2529 logcmdutil.diffordiffstat(
2523 logcmdutil.diffordiffstat(
2530 ui,
2524 ui,
2531 repo,
2525 repo,
2532 diffopts,
2526 diffopts,
2533 ctxleft,
2527 ctxleft,
2534 ctxright,
2528 ctxright,
2535 m,
2529 m,
2536 stat=stat,
2530 stat=stat,
2537 listsubrepos=opts.get(b'subrepos'),
2531 listsubrepos=opts.get(b'subrepos'),
2538 root=opts.get(b'root'),
2532 root=opts.get(b'root'),
2539 )
2533 )
2540
2534
2541
2535
2542 @command(
2536 @command(
2543 b'export',
2537 b'export',
2544 [
2538 [
2545 (
2539 (
2546 b'B',
2540 b'B',
2547 b'bookmark',
2541 b'bookmark',
2548 b'',
2542 b'',
2549 _(b'export changes only reachable by given bookmark'),
2543 _(b'export changes only reachable by given bookmark'),
2550 _(b'BOOKMARK'),
2544 _(b'BOOKMARK'),
2551 ),
2545 ),
2552 (
2546 (
2553 b'o',
2547 b'o',
2554 b'output',
2548 b'output',
2555 b'',
2549 b'',
2556 _(b'print output to file with formatted name'),
2550 _(b'print output to file with formatted name'),
2557 _(b'FORMAT'),
2551 _(b'FORMAT'),
2558 ),
2552 ),
2559 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2553 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2560 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2554 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2561 ]
2555 ]
2562 + diffopts
2556 + diffopts
2563 + formatteropts,
2557 + formatteropts,
2564 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2558 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2565 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2559 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2566 helpbasic=True,
2560 helpbasic=True,
2567 intents={INTENT_READONLY},
2561 intents={INTENT_READONLY},
2568 )
2562 )
2569 def export(ui, repo, *changesets, **opts):
2563 def export(ui, repo, *changesets, **opts):
2570 """dump the header and diffs for one or more changesets
2564 """dump the header and diffs for one or more changesets
2571
2565
2572 Print the changeset header and diffs for one or more revisions.
2566 Print the changeset header and diffs for one or more revisions.
2573 If no revision is given, the parent of the working directory is used.
2567 If no revision is given, the parent of the working directory is used.
2574
2568
2575 The information shown in the changeset header is: author, date,
2569 The information shown in the changeset header is: author, date,
2576 branch name (if non-default), changeset hash, parent(s) and commit
2570 branch name (if non-default), changeset hash, parent(s) and commit
2577 comment.
2571 comment.
2578
2572
2579 .. note::
2573 .. note::
2580
2574
2581 :hg:`export` may generate unexpected diff output for merge
2575 :hg:`export` may generate unexpected diff output for merge
2582 changesets, as it will compare the merge changeset against its
2576 changesets, as it will compare the merge changeset against its
2583 first parent only.
2577 first parent only.
2584
2578
2585 Output may be to a file, in which case the name of the file is
2579 Output may be to a file, in which case the name of the file is
2586 given using a template string. See :hg:`help templates`. In addition
2580 given using a template string. See :hg:`help templates`. In addition
2587 to the common template keywords, the following formatting rules are
2581 to the common template keywords, the following formatting rules are
2588 supported:
2582 supported:
2589
2583
2590 :``%%``: literal "%" character
2584 :``%%``: literal "%" character
2591 :``%H``: changeset hash (40 hexadecimal digits)
2585 :``%H``: changeset hash (40 hexadecimal digits)
2592 :``%N``: number of patches being generated
2586 :``%N``: number of patches being generated
2593 :``%R``: changeset revision number
2587 :``%R``: changeset revision number
2594 :``%b``: basename of the exporting repository
2588 :``%b``: basename of the exporting repository
2595 :``%h``: short-form changeset hash (12 hexadecimal digits)
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2596 :``%m``: first line of the commit message (only alphanumeric characters)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2597 :``%n``: zero-padded sequence number, starting at 1
2591 :``%n``: zero-padded sequence number, starting at 1
2598 :``%r``: zero-padded changeset revision number
2592 :``%r``: zero-padded changeset revision number
2599 :``\\``: literal "\\" character
2593 :``\\``: literal "\\" character
2600
2594
2601 Without the -a/--text option, export will avoid generating diffs
2595 Without the -a/--text option, export will avoid generating diffs
2602 of files it detects as binary. With -a, export will generate a
2596 of files it detects as binary. With -a, export will generate a
2603 diff anyway, probably with undesirable results.
2597 diff anyway, probably with undesirable results.
2604
2598
2605 With -B/--bookmark changesets reachable by the given bookmark are
2599 With -B/--bookmark changesets reachable by the given bookmark are
2606 selected.
2600 selected.
2607
2601
2608 Use the -g/--git option to generate diffs in the git extended diff
2602 Use the -g/--git option to generate diffs in the git extended diff
2609 format. See :hg:`help diffs` for more information.
2603 format. See :hg:`help diffs` for more information.
2610
2604
2611 With the --switch-parent option, the diff will be against the
2605 With the --switch-parent option, the diff will be against the
2612 second parent. It can be useful to review a merge.
2606 second parent. It can be useful to review a merge.
2613
2607
2614 .. container:: verbose
2608 .. container:: verbose
2615
2609
2616 Template:
2610 Template:
2617
2611
2618 The following keywords are supported in addition to the common template
2612 The following keywords are supported in addition to the common template
2619 keywords and functions. See also :hg:`help templates`.
2613 keywords and functions. See also :hg:`help templates`.
2620
2614
2621 :diff: String. Diff content.
2615 :diff: String. Diff content.
2622 :parents: List of strings. Parent nodes of the changeset.
2616 :parents: List of strings. Parent nodes of the changeset.
2623
2617
2624 Examples:
2618 Examples:
2625
2619
2626 - use export and import to transplant a bugfix to the current
2620 - use export and import to transplant a bugfix to the current
2627 branch::
2621 branch::
2628
2622
2629 hg export -r 9353 | hg import -
2623 hg export -r 9353 | hg import -
2630
2624
2631 - export all the changesets between two revisions to a file with
2625 - export all the changesets between two revisions to a file with
2632 rename information::
2626 rename information::
2633
2627
2634 hg export --git -r 123:150 > changes.txt
2628 hg export --git -r 123:150 > changes.txt
2635
2629
2636 - split outgoing changes into a series of patches with
2630 - split outgoing changes into a series of patches with
2637 descriptive names::
2631 descriptive names::
2638
2632
2639 hg export -r "outgoing()" -o "%n-%m.patch"
2633 hg export -r "outgoing()" -o "%n-%m.patch"
2640
2634
2641 Returns 0 on success.
2635 Returns 0 on success.
2642 """
2636 """
2643 opts = pycompat.byteskwargs(opts)
2637 opts = pycompat.byteskwargs(opts)
2644 bookmark = opts.get(b'bookmark')
2638 bookmark = opts.get(b'bookmark')
2645 changesets += tuple(opts.get(b'rev', []))
2639 changesets += tuple(opts.get(b'rev', []))
2646
2640
2647 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2641 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2648
2642
2649 if bookmark:
2643 if bookmark:
2650 if bookmark not in repo._bookmarks:
2644 if bookmark not in repo._bookmarks:
2651 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2645 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2652
2646
2653 revs = scmutil.bookmarkrevs(repo, bookmark)
2647 revs = scmutil.bookmarkrevs(repo, bookmark)
2654 else:
2648 else:
2655 if not changesets:
2649 if not changesets:
2656 changesets = [b'.']
2650 changesets = [b'.']
2657
2651
2658 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2652 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2659 revs = scmutil.revrange(repo, changesets)
2653 revs = scmutil.revrange(repo, changesets)
2660
2654
2661 if not revs:
2655 if not revs:
2662 raise error.Abort(_(b"export requires at least one changeset"))
2656 raise error.Abort(_(b"export requires at least one changeset"))
2663 if len(revs) > 1:
2657 if len(revs) > 1:
2664 ui.note(_(b'exporting patches:\n'))
2658 ui.note(_(b'exporting patches:\n'))
2665 else:
2659 else:
2666 ui.note(_(b'exporting patch:\n'))
2660 ui.note(_(b'exporting patch:\n'))
2667
2661
2668 fntemplate = opts.get(b'output')
2662 fntemplate = opts.get(b'output')
2669 if cmdutil.isstdiofilename(fntemplate):
2663 if cmdutil.isstdiofilename(fntemplate):
2670 fntemplate = b''
2664 fntemplate = b''
2671
2665
2672 if fntemplate:
2666 if fntemplate:
2673 fm = formatter.nullformatter(ui, b'export', opts)
2667 fm = formatter.nullformatter(ui, b'export', opts)
2674 else:
2668 else:
2675 ui.pager(b'export')
2669 ui.pager(b'export')
2676 fm = ui.formatter(b'export', opts)
2670 fm = ui.formatter(b'export', opts)
2677 with fm:
2671 with fm:
2678 cmdutil.export(
2672 cmdutil.export(
2679 repo,
2673 repo,
2680 revs,
2674 revs,
2681 fm,
2675 fm,
2682 fntemplate=fntemplate,
2676 fntemplate=fntemplate,
2683 switch_parent=opts.get(b'switch_parent'),
2677 switch_parent=opts.get(b'switch_parent'),
2684 opts=patch.diffallopts(ui, opts),
2678 opts=patch.diffallopts(ui, opts),
2685 )
2679 )
2686
2680
2687
2681
2688 @command(
2682 @command(
2689 b'files',
2683 b'files',
2690 [
2684 [
2691 (
2685 (
2692 b'r',
2686 b'r',
2693 b'rev',
2687 b'rev',
2694 b'',
2688 b'',
2695 _(b'search the repository as it is in REV'),
2689 _(b'search the repository as it is in REV'),
2696 _(b'REV'),
2690 _(b'REV'),
2697 ),
2691 ),
2698 (
2692 (
2699 b'0',
2693 b'0',
2700 b'print0',
2694 b'print0',
2701 None,
2695 None,
2702 _(b'end filenames with NUL, for use with xargs'),
2696 _(b'end filenames with NUL, for use with xargs'),
2703 ),
2697 ),
2704 ]
2698 ]
2705 + walkopts
2699 + walkopts
2706 + formatteropts
2700 + formatteropts
2707 + subrepoopts,
2701 + subrepoopts,
2708 _(b'[OPTION]... [FILE]...'),
2702 _(b'[OPTION]... [FILE]...'),
2709 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2703 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2710 intents={INTENT_READONLY},
2704 intents={INTENT_READONLY},
2711 )
2705 )
2712 def files(ui, repo, *pats, **opts):
2706 def files(ui, repo, *pats, **opts):
2713 """list tracked files
2707 """list tracked files
2714
2708
2715 Print files under Mercurial control in the working directory or
2709 Print files under Mercurial control in the working directory or
2716 specified revision for given files (excluding removed files).
2710 specified revision for given files (excluding removed files).
2717 Files can be specified as filenames or filesets.
2711 Files can be specified as filenames or filesets.
2718
2712
2719 If no files are given to match, this command prints the names
2713 If no files are given to match, this command prints the names
2720 of all files under Mercurial control.
2714 of all files under Mercurial control.
2721
2715
2722 .. container:: verbose
2716 .. container:: verbose
2723
2717
2724 Template:
2718 Template:
2725
2719
2726 The following keywords are supported in addition to the common template
2720 The following keywords are supported in addition to the common template
2727 keywords and functions. See also :hg:`help templates`.
2721 keywords and functions. See also :hg:`help templates`.
2728
2722
2729 :flags: String. Character denoting file's symlink and executable bits.
2723 :flags: String. Character denoting file's symlink and executable bits.
2730 :path: String. Repository-absolute path of the file.
2724 :path: String. Repository-absolute path of the file.
2731 :size: Integer. Size of the file in bytes.
2725 :size: Integer. Size of the file in bytes.
2732
2726
2733 Examples:
2727 Examples:
2734
2728
2735 - list all files under the current directory::
2729 - list all files under the current directory::
2736
2730
2737 hg files .
2731 hg files .
2738
2732
2739 - shows sizes and flags for current revision::
2733 - shows sizes and flags for current revision::
2740
2734
2741 hg files -vr .
2735 hg files -vr .
2742
2736
2743 - list all files named README::
2737 - list all files named README::
2744
2738
2745 hg files -I "**/README"
2739 hg files -I "**/README"
2746
2740
2747 - list all binary files::
2741 - list all binary files::
2748
2742
2749 hg files "set:binary()"
2743 hg files "set:binary()"
2750
2744
2751 - find files containing a regular expression::
2745 - find files containing a regular expression::
2752
2746
2753 hg files "set:grep('bob')"
2747 hg files "set:grep('bob')"
2754
2748
2755 - search tracked file contents with xargs and grep::
2749 - search tracked file contents with xargs and grep::
2756
2750
2757 hg files -0 | xargs -0 grep foo
2751 hg files -0 | xargs -0 grep foo
2758
2752
2759 See :hg:`help patterns` and :hg:`help filesets` for more information
2753 See :hg:`help patterns` and :hg:`help filesets` for more information
2760 on specifying file patterns.
2754 on specifying file patterns.
2761
2755
2762 Returns 0 if a match is found, 1 otherwise.
2756 Returns 0 if a match is found, 1 otherwise.
2763
2757
2764 """
2758 """
2765
2759
2766 opts = pycompat.byteskwargs(opts)
2760 opts = pycompat.byteskwargs(opts)
2767 rev = opts.get(b'rev')
2761 rev = opts.get(b'rev')
2768 if rev:
2762 if rev:
2769 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2770 ctx = scmutil.revsingle(repo, rev, None)
2764 ctx = scmutil.revsingle(repo, rev, None)
2771
2765
2772 end = b'\n'
2766 end = b'\n'
2773 if opts.get(b'print0'):
2767 if opts.get(b'print0'):
2774 end = b'\0'
2768 end = b'\0'
2775 fmt = b'%s' + end
2769 fmt = b'%s' + end
2776
2770
2777 m = scmutil.match(ctx, pats, opts)
2771 m = scmutil.match(ctx, pats, opts)
2778 ui.pager(b'files')
2772 ui.pager(b'files')
2779 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2773 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2780 with ui.formatter(b'files', opts) as fm:
2774 with ui.formatter(b'files', opts) as fm:
2781 return cmdutil.files(
2775 return cmdutil.files(
2782 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2776 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2783 )
2777 )
2784
2778
2785
2779
2786 @command(
2780 @command(
2787 b'forget',
2781 b'forget',
2788 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2782 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2789 + walkopts
2783 + walkopts
2790 + dryrunopts,
2784 + dryrunopts,
2791 _(b'[OPTION]... FILE...'),
2785 _(b'[OPTION]... FILE...'),
2792 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2786 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2793 helpbasic=True,
2787 helpbasic=True,
2794 inferrepo=True,
2788 inferrepo=True,
2795 )
2789 )
2796 def forget(ui, repo, *pats, **opts):
2790 def forget(ui, repo, *pats, **opts):
2797 """forget the specified files on the next commit
2791 """forget the specified files on the next commit
2798
2792
2799 Mark the specified files so they will no longer be tracked
2793 Mark the specified files so they will no longer be tracked
2800 after the next commit.
2794 after the next commit.
2801
2795
2802 This only removes files from the current branch, not from the
2796 This only removes files from the current branch, not from the
2803 entire project history, and it does not delete them from the
2797 entire project history, and it does not delete them from the
2804 working directory.
2798 working directory.
2805
2799
2806 To delete the file from the working directory, see :hg:`remove`.
2800 To delete the file from the working directory, see :hg:`remove`.
2807
2801
2808 To undo a forget before the next commit, see :hg:`add`.
2802 To undo a forget before the next commit, see :hg:`add`.
2809
2803
2810 .. container:: verbose
2804 .. container:: verbose
2811
2805
2812 Examples:
2806 Examples:
2813
2807
2814 - forget newly-added binary files::
2808 - forget newly-added binary files::
2815
2809
2816 hg forget "set:added() and binary()"
2810 hg forget "set:added() and binary()"
2817
2811
2818 - forget files that would be excluded by .hgignore::
2812 - forget files that would be excluded by .hgignore::
2819
2813
2820 hg forget "set:hgignore()"
2814 hg forget "set:hgignore()"
2821
2815
2822 Returns 0 on success.
2816 Returns 0 on success.
2823 """
2817 """
2824
2818
2825 opts = pycompat.byteskwargs(opts)
2819 opts = pycompat.byteskwargs(opts)
2826 if not pats:
2820 if not pats:
2827 raise error.Abort(_(b'no files specified'))
2821 raise error.Abort(_(b'no files specified'))
2828
2822
2829 m = scmutil.match(repo[None], pats, opts)
2823 m = scmutil.match(repo[None], pats, opts)
2830 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2824 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2831 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2825 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2832 rejected = cmdutil.forget(
2826 rejected = cmdutil.forget(
2833 ui,
2827 ui,
2834 repo,
2828 repo,
2835 m,
2829 m,
2836 prefix=b"",
2830 prefix=b"",
2837 uipathfn=uipathfn,
2831 uipathfn=uipathfn,
2838 explicitonly=False,
2832 explicitonly=False,
2839 dryrun=dryrun,
2833 dryrun=dryrun,
2840 interactive=interactive,
2834 interactive=interactive,
2841 )[0]
2835 )[0]
2842 return rejected and 1 or 0
2836 return rejected and 1 or 0
2843
2837
2844
2838
2845 @command(
2839 @command(
2846 b'graft',
2840 b'graft',
2847 [
2841 [
2848 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2842 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2849 (
2843 (
2850 b'',
2844 b'',
2851 b'base',
2845 b'base',
2852 b'',
2846 b'',
2853 _(b'base revision when doing the graft merge (ADVANCED)'),
2847 _(b'base revision when doing the graft merge (ADVANCED)'),
2854 _(b'REV'),
2848 _(b'REV'),
2855 ),
2849 ),
2856 (b'c', b'continue', False, _(b'resume interrupted graft')),
2850 (b'c', b'continue', False, _(b'resume interrupted graft')),
2857 (b'', b'stop', False, _(b'stop interrupted graft')),
2851 (b'', b'stop', False, _(b'stop interrupted graft')),
2858 (b'', b'abort', False, _(b'abort interrupted graft')),
2852 (b'', b'abort', False, _(b'abort interrupted graft')),
2859 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2853 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2860 (b'', b'log', None, _(b'append graft info to log message')),
2854 (b'', b'log', None, _(b'append graft info to log message')),
2861 (
2855 (
2862 b'',
2856 b'',
2863 b'no-commit',
2857 b'no-commit',
2864 None,
2858 None,
2865 _(b"don't commit, just apply the changes in working directory"),
2859 _(b"don't commit, just apply the changes in working directory"),
2866 ),
2860 ),
2867 (b'f', b'force', False, _(b'force graft')),
2861 (b'f', b'force', False, _(b'force graft')),
2868 (
2862 (
2869 b'D',
2863 b'D',
2870 b'currentdate',
2864 b'currentdate',
2871 False,
2865 False,
2872 _(b'record the current date as commit date'),
2866 _(b'record the current date as commit date'),
2873 ),
2867 ),
2874 (
2868 (
2875 b'U',
2869 b'U',
2876 b'currentuser',
2870 b'currentuser',
2877 False,
2871 False,
2878 _(b'record the current user as committer'),
2872 _(b'record the current user as committer'),
2879 ),
2873 ),
2880 ]
2874 ]
2881 + commitopts2
2875 + commitopts2
2882 + mergetoolopts
2876 + mergetoolopts
2883 + dryrunopts,
2877 + dryrunopts,
2884 _(b'[OPTION]... [-r REV]... REV...'),
2878 _(b'[OPTION]... [-r REV]... REV...'),
2885 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2879 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2886 )
2880 )
2887 def graft(ui, repo, *revs, **opts):
2881 def graft(ui, repo, *revs, **opts):
2888 '''copy changes from other branches onto the current branch
2882 '''copy changes from other branches onto the current branch
2889
2883
2890 This command uses Mercurial's merge logic to copy individual
2884 This command uses Mercurial's merge logic to copy individual
2891 changes from other branches without merging branches in the
2885 changes from other branches without merging branches in the
2892 history graph. This is sometimes known as 'backporting' or
2886 history graph. This is sometimes known as 'backporting' or
2893 'cherry-picking'. By default, graft will copy user, date, and
2887 'cherry-picking'. By default, graft will copy user, date, and
2894 description from the source changesets.
2888 description from the source changesets.
2895
2889
2896 Changesets that are ancestors of the current revision, that have
2890 Changesets that are ancestors of the current revision, that have
2897 already been grafted, or that are merges will be skipped.
2891 already been grafted, or that are merges will be skipped.
2898
2892
2899 If --log is specified, log messages will have a comment appended
2893 If --log is specified, log messages will have a comment appended
2900 of the form::
2894 of the form::
2901
2895
2902 (grafted from CHANGESETHASH)
2896 (grafted from CHANGESETHASH)
2903
2897
2904 If --force is specified, revisions will be grafted even if they
2898 If --force is specified, revisions will be grafted even if they
2905 are already ancestors of, or have been grafted to, the destination.
2899 are already ancestors of, or have been grafted to, the destination.
2906 This is useful when the revisions have since been backed out.
2900 This is useful when the revisions have since been backed out.
2907
2901
2908 If a graft merge results in conflicts, the graft process is
2902 If a graft merge results in conflicts, the graft process is
2909 interrupted so that the current merge can be manually resolved.
2903 interrupted so that the current merge can be manually resolved.
2910 Once all conflicts are addressed, the graft process can be
2904 Once all conflicts are addressed, the graft process can be
2911 continued with the -c/--continue option.
2905 continued with the -c/--continue option.
2912
2906
2913 The -c/--continue option reapplies all the earlier options.
2907 The -c/--continue option reapplies all the earlier options.
2914
2908
2915 .. container:: verbose
2909 .. container:: verbose
2916
2910
2917 The --base option exposes more of how graft internally uses merge with a
2911 The --base option exposes more of how graft internally uses merge with a
2918 custom base revision. --base can be used to specify another ancestor than
2912 custom base revision. --base can be used to specify another ancestor than
2919 the first and only parent.
2913 the first and only parent.
2920
2914
2921 The command::
2915 The command::
2922
2916
2923 hg graft -r 345 --base 234
2917 hg graft -r 345 --base 234
2924
2918
2925 is thus pretty much the same as::
2919 is thus pretty much the same as::
2926
2920
2927 hg diff -r 234 -r 345 | hg import
2921 hg diff -r 234 -r 345 | hg import
2928
2922
2929 but using merge to resolve conflicts and track moved files.
2923 but using merge to resolve conflicts and track moved files.
2930
2924
2931 The result of a merge can thus be backported as a single commit by
2925 The result of a merge can thus be backported as a single commit by
2932 specifying one of the merge parents as base, and thus effectively
2926 specifying one of the merge parents as base, and thus effectively
2933 grafting the changes from the other side.
2927 grafting the changes from the other side.
2934
2928
2935 It is also possible to collapse multiple changesets and clean up history
2929 It is also possible to collapse multiple changesets and clean up history
2936 by specifying another ancestor as base, much like rebase --collapse
2930 by specifying another ancestor as base, much like rebase --collapse
2937 --keep.
2931 --keep.
2938
2932
2939 The commit message can be tweaked after the fact using commit --amend .
2933 The commit message can be tweaked after the fact using commit --amend .
2940
2934
2941 For using non-ancestors as the base to backout changes, see the backout
2935 For using non-ancestors as the base to backout changes, see the backout
2942 command and the hidden --parent option.
2936 command and the hidden --parent option.
2943
2937
2944 .. container:: verbose
2938 .. container:: verbose
2945
2939
2946 Examples:
2940 Examples:
2947
2941
2948 - copy a single change to the stable branch and edit its description::
2942 - copy a single change to the stable branch and edit its description::
2949
2943
2950 hg update stable
2944 hg update stable
2951 hg graft --edit 9393
2945 hg graft --edit 9393
2952
2946
2953 - graft a range of changesets with one exception, updating dates::
2947 - graft a range of changesets with one exception, updating dates::
2954
2948
2955 hg graft -D "2085::2093 and not 2091"
2949 hg graft -D "2085::2093 and not 2091"
2956
2950
2957 - continue a graft after resolving conflicts::
2951 - continue a graft after resolving conflicts::
2958
2952
2959 hg graft -c
2953 hg graft -c
2960
2954
2961 - show the source of a grafted changeset::
2955 - show the source of a grafted changeset::
2962
2956
2963 hg log --debug -r .
2957 hg log --debug -r .
2964
2958
2965 - show revisions sorted by date::
2959 - show revisions sorted by date::
2966
2960
2967 hg log -r "sort(all(), date)"
2961 hg log -r "sort(all(), date)"
2968
2962
2969 - backport the result of a merge as a single commit::
2963 - backport the result of a merge as a single commit::
2970
2964
2971 hg graft -r 123 --base 123^
2965 hg graft -r 123 --base 123^
2972
2966
2973 - land a feature branch as one changeset::
2967 - land a feature branch as one changeset::
2974
2968
2975 hg up -cr default
2969 hg up -cr default
2976 hg graft -r featureX --base "ancestor('featureX', 'default')"
2970 hg graft -r featureX --base "ancestor('featureX', 'default')"
2977
2971
2978 See :hg:`help revisions` for more about specifying revisions.
2972 See :hg:`help revisions` for more about specifying revisions.
2979
2973
2980 Returns 0 on successful completion, 1 if there are unresolved files.
2974 Returns 0 on successful completion, 1 if there are unresolved files.
2981 '''
2975 '''
2982 with repo.wlock():
2976 with repo.wlock():
2983 return _dograft(ui, repo, *revs, **opts)
2977 return _dograft(ui, repo, *revs, **opts)
2984
2978
2985
2979
2986 def _dograft(ui, repo, *revs, **opts):
2980 def _dograft(ui, repo, *revs, **opts):
2987 opts = pycompat.byteskwargs(opts)
2981 opts = pycompat.byteskwargs(opts)
2988 if revs and opts.get(b'rev'):
2982 if revs and opts.get(b'rev'):
2989 ui.warn(
2983 ui.warn(
2990 _(
2984 _(
2991 b'warning: inconsistent use of --rev might give unexpected '
2985 b'warning: inconsistent use of --rev might give unexpected '
2992 b'revision ordering!\n'
2986 b'revision ordering!\n'
2993 )
2987 )
2994 )
2988 )
2995
2989
2996 revs = list(revs)
2990 revs = list(revs)
2997 revs.extend(opts.get(b'rev'))
2991 revs.extend(opts.get(b'rev'))
2998 # a dict of data to be stored in state file
2992 # a dict of data to be stored in state file
2999 statedata = {}
2993 statedata = {}
3000 # list of new nodes created by ongoing graft
2994 # list of new nodes created by ongoing graft
3001 statedata[b'newnodes'] = []
2995 statedata[b'newnodes'] = []
3002
2996
3003 cmdutil.resolvecommitoptions(ui, opts)
2997 cmdutil.resolvecommitoptions(ui, opts)
3004
2998
3005 editor = cmdutil.getcommiteditor(
2999 editor = cmdutil.getcommiteditor(
3006 editform=b'graft', **pycompat.strkwargs(opts)
3000 editform=b'graft', **pycompat.strkwargs(opts)
3007 )
3001 )
3008
3002
3009 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3003 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3010
3004
3011 cont = False
3005 cont = False
3012 if opts.get(b'no_commit'):
3006 if opts.get(b'no_commit'):
3013 cmdutil.check_incompatible_arguments(
3007 cmdutil.check_incompatible_arguments(
3014 opts,
3008 opts,
3015 b'no_commit',
3009 b'no_commit',
3016 [b'edit', b'currentuser', b'currentdate', b'log'],
3010 [b'edit', b'currentuser', b'currentdate', b'log'],
3017 )
3011 )
3018
3012
3019 graftstate = statemod.cmdstate(repo, b'graftstate')
3013 graftstate = statemod.cmdstate(repo, b'graftstate')
3020
3014
3021 if opts.get(b'stop'):
3015 if opts.get(b'stop'):
3022 cmdutil.check_incompatible_arguments(
3016 cmdutil.check_incompatible_arguments(
3023 opts,
3017 opts,
3024 b'stop',
3018 b'stop',
3025 [
3019 [
3026 b'edit',
3020 b'edit',
3027 b'log',
3021 b'log',
3028 b'user',
3022 b'user',
3029 b'date',
3023 b'date',
3030 b'currentdate',
3024 b'currentdate',
3031 b'currentuser',
3025 b'currentuser',
3032 b'rev',
3026 b'rev',
3033 ],
3027 ],
3034 )
3028 )
3035 return _stopgraft(ui, repo, graftstate)
3029 return _stopgraft(ui, repo, graftstate)
3036 elif opts.get(b'abort'):
3030 elif opts.get(b'abort'):
3037 cmdutil.check_incompatible_arguments(
3031 cmdutil.check_incompatible_arguments(
3038 opts,
3032 opts,
3039 b'abort',
3033 b'abort',
3040 [
3034 [
3041 b'edit',
3035 b'edit',
3042 b'log',
3036 b'log',
3043 b'user',
3037 b'user',
3044 b'date',
3038 b'date',
3045 b'currentdate',
3039 b'currentdate',
3046 b'currentuser',
3040 b'currentuser',
3047 b'rev',
3041 b'rev',
3048 ],
3042 ],
3049 )
3043 )
3050 return cmdutil.abortgraft(ui, repo, graftstate)
3044 return cmdutil.abortgraft(ui, repo, graftstate)
3051 elif opts.get(b'continue'):
3045 elif opts.get(b'continue'):
3052 cont = True
3046 cont = True
3053 if revs:
3047 if revs:
3054 raise error.Abort(_(b"can't specify --continue and revisions"))
3048 raise error.Abort(_(b"can't specify --continue and revisions"))
3055 # read in unfinished revisions
3049 # read in unfinished revisions
3056 if graftstate.exists():
3050 if graftstate.exists():
3057 statedata = cmdutil.readgraftstate(repo, graftstate)
3051 statedata = cmdutil.readgraftstate(repo, graftstate)
3058 if statedata.get(b'date'):
3052 if statedata.get(b'date'):
3059 opts[b'date'] = statedata[b'date']
3053 opts[b'date'] = statedata[b'date']
3060 if statedata.get(b'user'):
3054 if statedata.get(b'user'):
3061 opts[b'user'] = statedata[b'user']
3055 opts[b'user'] = statedata[b'user']
3062 if statedata.get(b'log'):
3056 if statedata.get(b'log'):
3063 opts[b'log'] = True
3057 opts[b'log'] = True
3064 if statedata.get(b'no_commit'):
3058 if statedata.get(b'no_commit'):
3065 opts[b'no_commit'] = statedata.get(b'no_commit')
3059 opts[b'no_commit'] = statedata.get(b'no_commit')
3066 if statedata.get(b'base'):
3060 if statedata.get(b'base'):
3067 opts[b'base'] = statedata.get(b'base')
3061 opts[b'base'] = statedata.get(b'base')
3068 nodes = statedata[b'nodes']
3062 nodes = statedata[b'nodes']
3069 revs = [repo[node].rev() for node in nodes]
3063 revs = [repo[node].rev() for node in nodes]
3070 else:
3064 else:
3071 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3065 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3072 else:
3066 else:
3073 if not revs:
3067 if not revs:
3074 raise error.Abort(_(b'no revisions specified'))
3068 raise error.Abort(_(b'no revisions specified'))
3075 cmdutil.checkunfinished(repo)
3069 cmdutil.checkunfinished(repo)
3076 cmdutil.bailifchanged(repo)
3070 cmdutil.bailifchanged(repo)
3077 revs = scmutil.revrange(repo, revs)
3071 revs = scmutil.revrange(repo, revs)
3078
3072
3079 skipped = set()
3073 skipped = set()
3080 basectx = None
3074 basectx = None
3081 if opts.get(b'base'):
3075 if opts.get(b'base'):
3082 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3076 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3083 if basectx is None:
3077 if basectx is None:
3084 # check for merges
3078 # check for merges
3085 for rev in repo.revs(b'%ld and merge()', revs):
3079 for rev in repo.revs(b'%ld and merge()', revs):
3086 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3087 skipped.add(rev)
3081 skipped.add(rev)
3088 revs = [r for r in revs if r not in skipped]
3082 revs = [r for r in revs if r not in skipped]
3089 if not revs:
3083 if not revs:
3090 return -1
3084 return -1
3091 if basectx is not None and len(revs) != 1:
3085 if basectx is not None and len(revs) != 1:
3092 raise error.Abort(_(b'only one revision allowed with --base '))
3086 raise error.Abort(_(b'only one revision allowed with --base '))
3093
3087
3094 # Don't check in the --continue case, in effect retaining --force across
3088 # Don't check in the --continue case, in effect retaining --force across
3095 # --continues. That's because without --force, any revisions we decided to
3089 # --continues. That's because without --force, any revisions we decided to
3096 # skip would have been filtered out here, so they wouldn't have made their
3090 # skip would have been filtered out here, so they wouldn't have made their
3097 # way to the graftstate. With --force, any revisions we would have otherwise
3091 # way to the graftstate. With --force, any revisions we would have otherwise
3098 # skipped would not have been filtered out, and if they hadn't been applied
3092 # skipped would not have been filtered out, and if they hadn't been applied
3099 # already, they'd have been in the graftstate.
3093 # already, they'd have been in the graftstate.
3100 if not (cont or opts.get(b'force')) and basectx is None:
3094 if not (cont or opts.get(b'force')) and basectx is None:
3101 # check for ancestors of dest branch
3095 # check for ancestors of dest branch
3102 ancestors = repo.revs(b'%ld & (::.)', revs)
3096 ancestors = repo.revs(b'%ld & (::.)', revs)
3103 for rev in ancestors:
3097 for rev in ancestors:
3104 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3098 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3105
3099
3106 revs = [r for r in revs if r not in ancestors]
3100 revs = [r for r in revs if r not in ancestors]
3107
3101
3108 if not revs:
3102 if not revs:
3109 return -1
3103 return -1
3110
3104
3111 # analyze revs for earlier grafts
3105 # analyze revs for earlier grafts
3112 ids = {}
3106 ids = {}
3113 for ctx in repo.set(b"%ld", revs):
3107 for ctx in repo.set(b"%ld", revs):
3114 ids[ctx.hex()] = ctx.rev()
3108 ids[ctx.hex()] = ctx.rev()
3115 n = ctx.extra().get(b'source')
3109 n = ctx.extra().get(b'source')
3116 if n:
3110 if n:
3117 ids[n] = ctx.rev()
3111 ids[n] = ctx.rev()
3118
3112
3119 # check ancestors for earlier grafts
3113 # check ancestors for earlier grafts
3120 ui.debug(b'scanning for duplicate grafts\n')
3114 ui.debug(b'scanning for duplicate grafts\n')
3121
3115
3122 # The only changesets we can be sure doesn't contain grafts of any
3116 # The only changesets we can be sure doesn't contain grafts of any
3123 # revs, are the ones that are common ancestors of *all* revs:
3117 # revs, are the ones that are common ancestors of *all* revs:
3124 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3118 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3125 ctx = repo[rev]
3119 ctx = repo[rev]
3126 n = ctx.extra().get(b'source')
3120 n = ctx.extra().get(b'source')
3127 if n in ids:
3121 if n in ids:
3128 try:
3122 try:
3129 r = repo[n].rev()
3123 r = repo[n].rev()
3130 except error.RepoLookupError:
3124 except error.RepoLookupError:
3131 r = None
3125 r = None
3132 if r in revs:
3126 if r in revs:
3133 ui.warn(
3127 ui.warn(
3134 _(
3128 _(
3135 b'skipping revision %d:%s '
3129 b'skipping revision %d:%s '
3136 b'(already grafted to %d:%s)\n'
3130 b'(already grafted to %d:%s)\n'
3137 )
3131 )
3138 % (r, repo[r], rev, ctx)
3132 % (r, repo[r], rev, ctx)
3139 )
3133 )
3140 revs.remove(r)
3134 revs.remove(r)
3141 elif ids[n] in revs:
3135 elif ids[n] in revs:
3142 if r is None:
3136 if r is None:
3143 ui.warn(
3137 ui.warn(
3144 _(
3138 _(
3145 b'skipping already grafted revision %d:%s '
3139 b'skipping already grafted revision %d:%s '
3146 b'(%d:%s also has unknown origin %s)\n'
3140 b'(%d:%s also has unknown origin %s)\n'
3147 )
3141 )
3148 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3142 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3149 )
3143 )
3150 else:
3144 else:
3151 ui.warn(
3145 ui.warn(
3152 _(
3146 _(
3153 b'skipping already grafted revision %d:%s '
3147 b'skipping already grafted revision %d:%s '
3154 b'(%d:%s also has origin %d:%s)\n'
3148 b'(%d:%s also has origin %d:%s)\n'
3155 )
3149 )
3156 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3150 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3157 )
3151 )
3158 revs.remove(ids[n])
3152 revs.remove(ids[n])
3159 elif ctx.hex() in ids:
3153 elif ctx.hex() in ids:
3160 r = ids[ctx.hex()]
3154 r = ids[ctx.hex()]
3161 if r in revs:
3155 if r in revs:
3162 ui.warn(
3156 ui.warn(
3163 _(
3157 _(
3164 b'skipping already grafted revision %d:%s '
3158 b'skipping already grafted revision %d:%s '
3165 b'(was grafted from %d:%s)\n'
3159 b'(was grafted from %d:%s)\n'
3166 )
3160 )
3167 % (r, repo[r], rev, ctx)
3161 % (r, repo[r], rev, ctx)
3168 )
3162 )
3169 revs.remove(r)
3163 revs.remove(r)
3170 if not revs:
3164 if not revs:
3171 return -1
3165 return -1
3172
3166
3173 if opts.get(b'no_commit'):
3167 if opts.get(b'no_commit'):
3174 statedata[b'no_commit'] = True
3168 statedata[b'no_commit'] = True
3175 if opts.get(b'base'):
3169 if opts.get(b'base'):
3176 statedata[b'base'] = opts[b'base']
3170 statedata[b'base'] = opts[b'base']
3177 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3171 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3178 desc = b'%d:%s "%s"' % (
3172 desc = b'%d:%s "%s"' % (
3179 ctx.rev(),
3173 ctx.rev(),
3180 ctx,
3174 ctx,
3181 ctx.description().split(b'\n', 1)[0],
3175 ctx.description().split(b'\n', 1)[0],
3182 )
3176 )
3183 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3177 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3184 if names:
3178 if names:
3185 desc += b' (%s)' % b' '.join(names)
3179 desc += b' (%s)' % b' '.join(names)
3186 ui.status(_(b'grafting %s\n') % desc)
3180 ui.status(_(b'grafting %s\n') % desc)
3187 if opts.get(b'dry_run'):
3181 if opts.get(b'dry_run'):
3188 continue
3182 continue
3189
3183
3190 source = ctx.extra().get(b'source')
3184 source = ctx.extra().get(b'source')
3191 extra = {}
3185 extra = {}
3192 if source:
3186 if source:
3193 extra[b'source'] = source
3187 extra[b'source'] = source
3194 extra[b'intermediate-source'] = ctx.hex()
3188 extra[b'intermediate-source'] = ctx.hex()
3195 else:
3189 else:
3196 extra[b'source'] = ctx.hex()
3190 extra[b'source'] = ctx.hex()
3197 user = ctx.user()
3191 user = ctx.user()
3198 if opts.get(b'user'):
3192 if opts.get(b'user'):
3199 user = opts[b'user']
3193 user = opts[b'user']
3200 statedata[b'user'] = user
3194 statedata[b'user'] = user
3201 date = ctx.date()
3195 date = ctx.date()
3202 if opts.get(b'date'):
3196 if opts.get(b'date'):
3203 date = opts[b'date']
3197 date = opts[b'date']
3204 statedata[b'date'] = date
3198 statedata[b'date'] = date
3205 message = ctx.description()
3199 message = ctx.description()
3206 if opts.get(b'log'):
3200 if opts.get(b'log'):
3207 message += b'\n(grafted from %s)' % ctx.hex()
3201 message += b'\n(grafted from %s)' % ctx.hex()
3208 statedata[b'log'] = True
3202 statedata[b'log'] = True
3209
3203
3210 # we don't merge the first commit when continuing
3204 # we don't merge the first commit when continuing
3211 if not cont:
3205 if not cont:
3212 # perform the graft merge with p1(rev) as 'ancestor'
3206 # perform the graft merge with p1(rev) as 'ancestor'
3213 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3207 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3214 base = ctx.p1() if basectx is None else basectx
3208 base = ctx.p1() if basectx is None else basectx
3215 with ui.configoverride(overrides, b'graft'):
3209 with ui.configoverride(overrides, b'graft'):
3216 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3210 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3217 # report any conflicts
3211 # report any conflicts
3218 if stats.unresolvedcount > 0:
3212 if stats.unresolvedcount > 0:
3219 # write out state for --continue
3213 # write out state for --continue
3220 nodes = [repo[rev].hex() for rev in revs[pos:]]
3214 nodes = [repo[rev].hex() for rev in revs[pos:]]
3221 statedata[b'nodes'] = nodes
3215 statedata[b'nodes'] = nodes
3222 stateversion = 1
3216 stateversion = 1
3223 graftstate.save(stateversion, statedata)
3217 graftstate.save(stateversion, statedata)
3224 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3218 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3225 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3219 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3226 return 1
3220 return 1
3227 else:
3221 else:
3228 cont = False
3222 cont = False
3229
3223
3230 # commit if --no-commit is false
3224 # commit if --no-commit is false
3231 if not opts.get(b'no_commit'):
3225 if not opts.get(b'no_commit'):
3232 node = repo.commit(
3226 node = repo.commit(
3233 text=message, user=user, date=date, extra=extra, editor=editor
3227 text=message, user=user, date=date, extra=extra, editor=editor
3234 )
3228 )
3235 if node is None:
3229 if node is None:
3236 ui.warn(
3230 ui.warn(
3237 _(b'note: graft of %d:%s created no changes to commit\n')
3231 _(b'note: graft of %d:%s created no changes to commit\n')
3238 % (ctx.rev(), ctx)
3232 % (ctx.rev(), ctx)
3239 )
3233 )
3240 # checking that newnodes exist because old state files won't have it
3234 # checking that newnodes exist because old state files won't have it
3241 elif statedata.get(b'newnodes') is not None:
3235 elif statedata.get(b'newnodes') is not None:
3242 statedata[b'newnodes'].append(node)
3236 statedata[b'newnodes'].append(node)
3243
3237
3244 # remove state when we complete successfully
3238 # remove state when we complete successfully
3245 if not opts.get(b'dry_run'):
3239 if not opts.get(b'dry_run'):
3246 graftstate.delete()
3240 graftstate.delete()
3247
3241
3248 return 0
3242 return 0
3249
3243
3250
3244
3251 def _stopgraft(ui, repo, graftstate):
3245 def _stopgraft(ui, repo, graftstate):
3252 """stop the interrupted graft"""
3246 """stop the interrupted graft"""
3253 if not graftstate.exists():
3247 if not graftstate.exists():
3254 raise error.Abort(_(b"no interrupted graft found"))
3248 raise error.Abort(_(b"no interrupted graft found"))
3255 pctx = repo[b'.']
3249 pctx = repo[b'.']
3256 hg.updaterepo(repo, pctx.node(), overwrite=True)
3250 hg.updaterepo(repo, pctx.node(), overwrite=True)
3257 graftstate.delete()
3251 graftstate.delete()
3258 ui.status(_(b"stopped the interrupted graft\n"))
3252 ui.status(_(b"stopped the interrupted graft\n"))
3259 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3253 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3260 return 0
3254 return 0
3261
3255
3262
3256
3263 statemod.addunfinished(
3257 statemod.addunfinished(
3264 b'graft',
3258 b'graft',
3265 fname=b'graftstate',
3259 fname=b'graftstate',
3266 clearable=True,
3260 clearable=True,
3267 stopflag=True,
3261 stopflag=True,
3268 continueflag=True,
3262 continueflag=True,
3269 abortfunc=cmdutil.hgabortgraft,
3263 abortfunc=cmdutil.hgabortgraft,
3270 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3264 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3271 )
3265 )
3272
3266
3273
3267
3274 @command(
3268 @command(
3275 b'grep',
3269 b'grep',
3276 [
3270 [
3277 (b'0', b'print0', None, _(b'end fields with NUL')),
3271 (b'0', b'print0', None, _(b'end fields with NUL')),
3278 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3272 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3279 (
3273 (
3280 b'',
3274 b'',
3281 b'diff',
3275 b'diff',
3282 None,
3276 None,
3283 _(
3277 _(
3284 b'search revision differences for when the pattern was added '
3278 b'search revision differences for when the pattern was added '
3285 b'or removed'
3279 b'or removed'
3286 ),
3280 ),
3287 ),
3281 ),
3288 (b'a', b'text', None, _(b'treat all files as text')),
3282 (b'a', b'text', None, _(b'treat all files as text')),
3289 (
3283 (
3290 b'f',
3284 b'f',
3291 b'follow',
3285 b'follow',
3292 None,
3286 None,
3293 _(
3287 _(
3294 b'follow changeset history,'
3288 b'follow changeset history,'
3295 b' or file history across copies and renames'
3289 b' or file history across copies and renames'
3296 ),
3290 ),
3297 ),
3291 ),
3298 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3292 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3299 (
3293 (
3300 b'l',
3294 b'l',
3301 b'files-with-matches',
3295 b'files-with-matches',
3302 None,
3296 None,
3303 _(b'print only filenames and revisions that match'),
3297 _(b'print only filenames and revisions that match'),
3304 ),
3298 ),
3305 (b'n', b'line-number', None, _(b'print matching line numbers')),
3299 (b'n', b'line-number', None, _(b'print matching line numbers')),
3306 (
3300 (
3307 b'r',
3301 b'r',
3308 b'rev',
3302 b'rev',
3309 [],
3303 [],
3310 _(b'search files changed within revision range'),
3304 _(b'search files changed within revision range'),
3311 _(b'REV'),
3305 _(b'REV'),
3312 ),
3306 ),
3313 (
3307 (
3314 b'',
3308 b'',
3315 b'all-files',
3309 b'all-files',
3316 None,
3310 None,
3317 _(
3311 _(
3318 b'include all files in the changeset while grepping (DEPRECATED)'
3312 b'include all files in the changeset while grepping (DEPRECATED)'
3319 ),
3313 ),
3320 ),
3314 ),
3321 (b'u', b'user', None, _(b'list the author (long with -v)')),
3315 (b'u', b'user', None, _(b'list the author (long with -v)')),
3322 (b'd', b'date', None, _(b'list the date (short with -q)')),
3316 (b'd', b'date', None, _(b'list the date (short with -q)')),
3323 ]
3317 ]
3324 + formatteropts
3318 + formatteropts
3325 + walkopts,
3319 + walkopts,
3326 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3320 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3327 helpcategory=command.CATEGORY_FILE_CONTENTS,
3321 helpcategory=command.CATEGORY_FILE_CONTENTS,
3328 inferrepo=True,
3322 inferrepo=True,
3329 intents={INTENT_READONLY},
3323 intents={INTENT_READONLY},
3330 )
3324 )
3331 def grep(ui, repo, pattern, *pats, **opts):
3325 def grep(ui, repo, pattern, *pats, **opts):
3332 """search for a pattern in specified files
3326 """search for a pattern in specified files
3333
3327
3334 Search the working directory or revision history for a regular
3328 Search the working directory or revision history for a regular
3335 expression in the specified files for the entire repository.
3329 expression in the specified files for the entire repository.
3336
3330
3337 By default, grep searches the repository files in the working
3331 By default, grep searches the repository files in the working
3338 directory and prints the files where it finds a match. To specify
3332 directory and prints the files where it finds a match. To specify
3339 historical revisions instead of the working directory, use the
3333 historical revisions instead of the working directory, use the
3340 --rev flag.
3334 --rev flag.
3341
3335
3342 To search instead historical revision differences that contains a
3336 To search instead historical revision differences that contains a
3343 change in match status ("-" for a match that becomes a non-match,
3337 change in match status ("-" for a match that becomes a non-match,
3344 or "+" for a non-match that becomes a match), use the --diff flag.
3338 or "+" for a non-match that becomes a match), use the --diff flag.
3345
3339
3346 PATTERN can be any Python (roughly Perl-compatible) regular
3340 PATTERN can be any Python (roughly Perl-compatible) regular
3347 expression.
3341 expression.
3348
3342
3349 If no FILEs are specified and the --rev flag isn't supplied, all
3343 If no FILEs are specified and the --rev flag isn't supplied, all
3350 files in the working directory are searched. When using the --rev
3344 files in the working directory are searched. When using the --rev
3351 flag and specifying FILEs, use the --follow argument to also
3345 flag and specifying FILEs, use the --follow argument to also
3352 follow the specified FILEs across renames and copies.
3346 follow the specified FILEs across renames and copies.
3353
3347
3354 .. container:: verbose
3348 .. container:: verbose
3355
3349
3356 Template:
3350 Template:
3357
3351
3358 The following keywords are supported in addition to the common template
3352 The following keywords are supported in addition to the common template
3359 keywords and functions. See also :hg:`help templates`.
3353 keywords and functions. See also :hg:`help templates`.
3360
3354
3361 :change: String. Character denoting insertion ``+`` or removal ``-``.
3355 :change: String. Character denoting insertion ``+`` or removal ``-``.
3362 Available if ``--diff`` is specified.
3356 Available if ``--diff`` is specified.
3363 :lineno: Integer. Line number of the match.
3357 :lineno: Integer. Line number of the match.
3364 :path: String. Repository-absolute path of the file.
3358 :path: String. Repository-absolute path of the file.
3365 :texts: List of text chunks.
3359 :texts: List of text chunks.
3366
3360
3367 And each entry of ``{texts}`` provides the following sub-keywords.
3361 And each entry of ``{texts}`` provides the following sub-keywords.
3368
3362
3369 :matched: Boolean. True if the chunk matches the specified pattern.
3363 :matched: Boolean. True if the chunk matches the specified pattern.
3370 :text: String. Chunk content.
3364 :text: String. Chunk content.
3371
3365
3372 See :hg:`help templates.operators` for the list expansion syntax.
3366 See :hg:`help templates.operators` for the list expansion syntax.
3373
3367
3374 Returns 0 if a match is found, 1 otherwise.
3368 Returns 0 if a match is found, 1 otherwise.
3375
3369
3376 """
3370 """
3377 opts = pycompat.byteskwargs(opts)
3371 opts = pycompat.byteskwargs(opts)
3378 diff = opts.get(b'all') or opts.get(b'diff')
3372 diff = opts.get(b'all') or opts.get(b'diff')
3379 if diff and opts.get(b'all_files'):
3373 if diff and opts.get(b'all_files'):
3380 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3374 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3381 if opts.get(b'all_files') is None and not diff:
3375 if opts.get(b'all_files') is None and not diff:
3382 opts[b'all_files'] = True
3376 opts[b'all_files'] = True
3383 plaingrep = (
3377 plaingrep = (
3384 opts.get(b'all_files')
3378 opts.get(b'all_files')
3385 and not opts.get(b'rev')
3379 and not opts.get(b'rev')
3386 and not opts.get(b'follow')
3380 and not opts.get(b'follow')
3387 )
3381 )
3388 all_files = opts.get(b'all_files')
3382 all_files = opts.get(b'all_files')
3389 if plaingrep:
3383 if plaingrep:
3390 opts[b'rev'] = [b'wdir()']
3384 opts[b'rev'] = [b'wdir()']
3391
3385
3392 reflags = re.M
3386 reflags = re.M
3393 if opts.get(b'ignore_case'):
3387 if opts.get(b'ignore_case'):
3394 reflags |= re.I
3388 reflags |= re.I
3395 try:
3389 try:
3396 regexp = util.re.compile(pattern, reflags)
3390 regexp = util.re.compile(pattern, reflags)
3397 except re.error as inst:
3391 except re.error as inst:
3398 ui.warn(
3392 ui.warn(
3399 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3400 )
3394 )
3401 return 1
3395 return 1
3402 sep, eol = b':', b'\n'
3396 sep, eol = b':', b'\n'
3403 if opts.get(b'print0'):
3397 if opts.get(b'print0'):
3404 sep = eol = b'\0'
3398 sep = eol = b'\0'
3405
3399
3406 getfile = util.lrucachefunc(repo.file)
3400 getfile = util.lrucachefunc(repo.file)
3407
3401
3408 def matchlines(body):
3402 def matchlines(body):
3409 begin = 0
3403 begin = 0
3410 linenum = 0
3404 linenum = 0
3411 while begin < len(body):
3405 while begin < len(body):
3412 match = regexp.search(body, begin)
3406 match = regexp.search(body, begin)
3413 if not match:
3407 if not match:
3414 break
3408 break
3415 mstart, mend = match.span()
3409 mstart, mend = match.span()
3416 linenum += body.count(b'\n', begin, mstart) + 1
3410 linenum += body.count(b'\n', begin, mstart) + 1
3417 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3418 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3419 lend = begin - 1
3413 lend = begin - 1
3420 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3421
3415
3422 class linestate(object):
3416 class linestate(object):
3423 def __init__(self, line, linenum, colstart, colend):
3417 def __init__(self, line, linenum, colstart, colend):
3424 self.line = line
3418 self.line = line
3425 self.linenum = linenum
3419 self.linenum = linenum
3426 self.colstart = colstart
3420 self.colstart = colstart
3427 self.colend = colend
3421 self.colend = colend
3428
3422
3429 def __hash__(self):
3423 def __hash__(self):
3430 return hash(self.line)
3424 return hash(self.line)
3431
3425
3432 def __eq__(self, other):
3426 def __eq__(self, other):
3433 return self.line == other.line
3427 return self.line == other.line
3434
3428
3435 def findpos(self):
3429 def findpos(self):
3436 """Iterate all (start, end) indices of matches"""
3430 """Iterate all (start, end) indices of matches"""
3437 yield self.colstart, self.colend
3431 yield self.colstart, self.colend
3438 p = self.colend
3432 p = self.colend
3439 while p < len(self.line):
3433 while p < len(self.line):
3440 m = regexp.search(self.line, p)
3434 m = regexp.search(self.line, p)
3441 if not m:
3435 if not m:
3442 break
3436 break
3443 if m.end() == p:
3437 if m.end() == p:
3444 p += 1
3438 p += 1
3445 else:
3439 else:
3446 yield m.span()
3440 yield m.span()
3447 p = m.end()
3441 p = m.end()
3448
3442
3449 matches = {}
3443 matches = {}
3450 copies = {}
3444 copies = {}
3451
3445
3452 def grepbody(fn, rev, body):
3446 def grepbody(fn, rev, body):
3453 matches[rev].setdefault(fn, [])
3447 matches[rev].setdefault(fn, [])
3454 m = matches[rev][fn]
3448 m = matches[rev][fn]
3455 if body is None:
3449 if body is None:
3456 return
3450 return
3457
3451
3458 for lnum, cstart, cend, line in matchlines(body):
3452 for lnum, cstart, cend, line in matchlines(body):
3459 s = linestate(line, lnum, cstart, cend)
3453 s = linestate(line, lnum, cstart, cend)
3460 m.append(s)
3454 m.append(s)
3461
3455
3462 def difflinestates(a, b):
3456 def difflinestates(a, b):
3463 sm = difflib.SequenceMatcher(None, a, b)
3457 sm = difflib.SequenceMatcher(None, a, b)
3464 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3458 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3465 if tag == 'insert':
3459 if tag == 'insert':
3466 for i in pycompat.xrange(blo, bhi):
3460 for i in pycompat.xrange(blo, bhi):
3467 yield (b'+', b[i])
3461 yield (b'+', b[i])
3468 elif tag == 'delete':
3462 elif tag == 'delete':
3469 for i in pycompat.xrange(alo, ahi):
3463 for i in pycompat.xrange(alo, ahi):
3470 yield (b'-', a[i])
3464 yield (b'-', a[i])
3471 elif tag == 'replace':
3465 elif tag == 'replace':
3472 for i in pycompat.xrange(alo, ahi):
3466 for i in pycompat.xrange(alo, ahi):
3473 yield (b'-', a[i])
3467 yield (b'-', a[i])
3474 for i in pycompat.xrange(blo, bhi):
3468 for i in pycompat.xrange(blo, bhi):
3475 yield (b'+', b[i])
3469 yield (b'+', b[i])
3476
3470
3477 uipathfn = scmutil.getuipathfn(repo)
3471 uipathfn = scmutil.getuipathfn(repo)
3478
3472
3479 def display(fm, fn, ctx, pstates, states):
3473 def display(fm, fn, ctx, pstates, states):
3480 rev = scmutil.intrev(ctx)
3474 rev = scmutil.intrev(ctx)
3481 if fm.isplain():
3475 if fm.isplain():
3482 formatuser = ui.shortuser
3476 formatuser = ui.shortuser
3483 else:
3477 else:
3484 formatuser = pycompat.bytestr
3478 formatuser = pycompat.bytestr
3485 if ui.quiet:
3479 if ui.quiet:
3486 datefmt = b'%Y-%m-%d'
3480 datefmt = b'%Y-%m-%d'
3487 else:
3481 else:
3488 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3482 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3489 found = False
3483 found = False
3490
3484
3491 @util.cachefunc
3485 @util.cachefunc
3492 def binary():
3486 def binary():
3493 flog = getfile(fn)
3487 flog = getfile(fn)
3494 try:
3488 try:
3495 return stringutil.binary(flog.read(ctx.filenode(fn)))
3489 return stringutil.binary(flog.read(ctx.filenode(fn)))
3496 except error.WdirUnsupported:
3490 except error.WdirUnsupported:
3497 return ctx[fn].isbinary()
3491 return ctx[fn].isbinary()
3498
3492
3499 fieldnamemap = {b'linenumber': b'lineno'}
3493 fieldnamemap = {b'linenumber': b'lineno'}
3500 if diff:
3494 if diff:
3501 iter = difflinestates(pstates, states)
3495 iter = difflinestates(pstates, states)
3502 else:
3496 else:
3503 iter = [(b'', l) for l in states]
3497 iter = [(b'', l) for l in states]
3504 for change, l in iter:
3498 for change, l in iter:
3505 fm.startitem()
3499 fm.startitem()
3506 fm.context(ctx=ctx)
3500 fm.context(ctx=ctx)
3507 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3501 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3508 fm.plain(uipathfn(fn), label=b'grep.filename')
3502 fm.plain(uipathfn(fn), label=b'grep.filename')
3509
3503
3510 cols = [
3504 cols = [
3511 (b'rev', b'%d', rev, not plaingrep, b''),
3505 (b'rev', b'%d', rev, not plaingrep, b''),
3512 (
3506 (
3513 b'linenumber',
3507 b'linenumber',
3514 b'%d',
3508 b'%d',
3515 l.linenum,
3509 l.linenum,
3516 opts.get(b'line_number'),
3510 opts.get(b'line_number'),
3517 b'',
3511 b'',
3518 ),
3512 ),
3519 ]
3513 ]
3520 if diff:
3514 if diff:
3521 cols.append(
3515 cols.append(
3522 (
3516 (
3523 b'change',
3517 b'change',
3524 b'%s',
3518 b'%s',
3525 change,
3519 change,
3526 True,
3520 True,
3527 b'grep.inserted '
3521 b'grep.inserted '
3528 if change == b'+'
3522 if change == b'+'
3529 else b'grep.deleted ',
3523 else b'grep.deleted ',
3530 )
3524 )
3531 )
3525 )
3532 cols.extend(
3526 cols.extend(
3533 [
3527 [
3534 (
3528 (
3535 b'user',
3529 b'user',
3536 b'%s',
3530 b'%s',
3537 formatuser(ctx.user()),
3531 formatuser(ctx.user()),
3538 opts.get(b'user'),
3532 opts.get(b'user'),
3539 b'',
3533 b'',
3540 ),
3534 ),
3541 (
3535 (
3542 b'date',
3536 b'date',
3543 b'%s',
3537 b'%s',
3544 fm.formatdate(ctx.date(), datefmt),
3538 fm.formatdate(ctx.date(), datefmt),
3545 opts.get(b'date'),
3539 opts.get(b'date'),
3546 b'',
3540 b'',
3547 ),
3541 ),
3548 ]
3542 ]
3549 )
3543 )
3550 for name, fmt, data, cond, extra_label in cols:
3544 for name, fmt, data, cond, extra_label in cols:
3551 if cond:
3545 if cond:
3552 fm.plain(sep, label=b'grep.sep')
3546 fm.plain(sep, label=b'grep.sep')
3553 field = fieldnamemap.get(name, name)
3547 field = fieldnamemap.get(name, name)
3554 label = extra_label + (b'grep.%s' % name)
3548 label = extra_label + (b'grep.%s' % name)
3555 fm.condwrite(cond, field, fmt, data, label=label)
3549 fm.condwrite(cond, field, fmt, data, label=label)
3556 if not opts.get(b'files_with_matches'):
3550 if not opts.get(b'files_with_matches'):
3557 fm.plain(sep, label=b'grep.sep')
3551 fm.plain(sep, label=b'grep.sep')
3558 if not opts.get(b'text') and binary():
3552 if not opts.get(b'text') and binary():
3559 fm.plain(_(b" Binary file matches"))
3553 fm.plain(_(b" Binary file matches"))
3560 else:
3554 else:
3561 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3555 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3562 fm.plain(eol)
3556 fm.plain(eol)
3563 found = True
3557 found = True
3564 if opts.get(b'files_with_matches'):
3558 if opts.get(b'files_with_matches'):
3565 break
3559 break
3566 return found
3560 return found
3567
3561
3568 def displaymatches(fm, l):
3562 def displaymatches(fm, l):
3569 p = 0
3563 p = 0
3570 for s, e in l.findpos():
3564 for s, e in l.findpos():
3571 if p < s:
3565 if p < s:
3572 fm.startitem()
3566 fm.startitem()
3573 fm.write(b'text', b'%s', l.line[p:s])
3567 fm.write(b'text', b'%s', l.line[p:s])
3574 fm.data(matched=False)
3568 fm.data(matched=False)
3575 fm.startitem()
3569 fm.startitem()
3576 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3570 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3577 fm.data(matched=True)
3571 fm.data(matched=True)
3578 p = e
3572 p = e
3579 if p < len(l.line):
3573 if p < len(l.line):
3580 fm.startitem()
3574 fm.startitem()
3581 fm.write(b'text', b'%s', l.line[p:])
3575 fm.write(b'text', b'%s', l.line[p:])
3582 fm.data(matched=False)
3576 fm.data(matched=False)
3583 fm.end()
3577 fm.end()
3584
3578
3585 skip = set()
3579 skip = set()
3586 revfiles = {}
3580 revfiles = {}
3587 match = scmutil.match(repo[None], pats, opts)
3581 match = scmutil.match(repo[None], pats, opts)
3588 found = False
3582 found = False
3589 follow = opts.get(b'follow')
3583 follow = opts.get(b'follow')
3590
3584
3591 getrenamed = scmutil.getrenamedfn(repo)
3585 getrenamed = scmutil.getrenamedfn(repo)
3592
3586
3593 def readfile(ctx, fn):
3587 def readfile(ctx, fn):
3594 rev = ctx.rev()
3588 rev = ctx.rev()
3595 if rev is None:
3589 if rev is None:
3596 fctx = ctx[fn]
3590 fctx = ctx[fn]
3597 try:
3591 try:
3598 return fctx.data()
3592 return fctx.data()
3599 except IOError as e:
3593 except IOError as e:
3600 if e.errno != errno.ENOENT:
3594 if e.errno != errno.ENOENT:
3601 raise
3595 raise
3602 else:
3596 else:
3603 flog = getfile(fn)
3597 flog = getfile(fn)
3604 fnode = ctx.filenode(fn)
3598 fnode = ctx.filenode(fn)
3605 try:
3599 try:
3606 return flog.read(fnode)
3600 return flog.read(fnode)
3607 except error.CensoredNodeError:
3601 except error.CensoredNodeError:
3608 ui.warn(
3602 ui.warn(
3609 _(
3603 _(
3610 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3604 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3611 )
3605 )
3612 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3606 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3613 )
3607 )
3614
3608
3615 def prep(ctx, fns):
3609 def prep(ctx, fns):
3616 rev = ctx.rev()
3610 rev = ctx.rev()
3617 pctx = ctx.p1()
3611 pctx = ctx.p1()
3618 matches.setdefault(rev, {})
3612 matches.setdefault(rev, {})
3619 if diff:
3613 if diff:
3620 parent = pctx.rev()
3614 parent = pctx.rev()
3621 matches.setdefault(parent, {})
3615 matches.setdefault(parent, {})
3622 files = revfiles.setdefault(rev, [])
3616 files = revfiles.setdefault(rev, [])
3623 if rev is None:
3617 if rev is None:
3624 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3618 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3625 # pathauditor checks without this in mozilla-central
3619 # pathauditor checks without this in mozilla-central
3626 contextmanager = repo.wvfs.audit.cached
3620 contextmanager = repo.wvfs.audit.cached
3627 else:
3621 else:
3628 contextmanager = util.nullcontextmanager
3622 contextmanager = util.nullcontextmanager
3629 with contextmanager():
3623 with contextmanager():
3630 for fn in fns:
3624 for fn in fns:
3631 # fn might not exist in the revision (could be a file removed by
3625 # fn might not exist in the revision (could be a file removed by
3632 # the revision). We could check `fn not in ctx` even when rev is
3626 # the revision). We could check `fn not in ctx` even when rev is
3633 # None, but it's less racy to protect againt that in readfile.
3627 # None, but it's less racy to protect againt that in readfile.
3634 if rev is not None and fn not in ctx:
3628 if rev is not None and fn not in ctx:
3635 continue
3629 continue
3636
3630
3637 copy = None
3631 copy = None
3638 if follow:
3632 if follow:
3639 copy = getrenamed(fn, rev)
3633 copy = getrenamed(fn, rev)
3640 if copy:
3634 if copy:
3641 copies.setdefault(rev, {})[fn] = copy
3635 copies.setdefault(rev, {})[fn] = copy
3642 if fn in skip:
3636 if fn in skip:
3643 skip.add(copy)
3637 skip.add(copy)
3644 if fn in skip:
3638 if fn in skip:
3645 continue
3639 continue
3646 files.append(fn)
3640 files.append(fn)
3647
3641
3648 if fn not in matches[rev]:
3642 if fn not in matches[rev]:
3649 grepbody(fn, rev, readfile(ctx, fn))
3643 grepbody(fn, rev, readfile(ctx, fn))
3650
3644
3651 if diff:
3645 if diff:
3652 pfn = copy or fn
3646 pfn = copy or fn
3653 if pfn not in matches[parent] and pfn in pctx:
3647 if pfn not in matches[parent] and pfn in pctx:
3654 grepbody(pfn, parent, readfile(pctx, pfn))
3648 grepbody(pfn, parent, readfile(pctx, pfn))
3655
3649
3656 ui.pager(b'grep')
3650 ui.pager(b'grep')
3657 fm = ui.formatter(b'grep', opts)
3651 fm = ui.formatter(b'grep', opts)
3658 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3652 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3659 rev = ctx.rev()
3653 rev = ctx.rev()
3660 parent = ctx.p1().rev()
3654 parent = ctx.p1().rev()
3661 for fn in sorted(revfiles.get(rev, [])):
3655 for fn in sorted(revfiles.get(rev, [])):
3662 states = matches[rev][fn]
3656 states = matches[rev][fn]
3663 copy = copies.get(rev, {}).get(fn)
3657 copy = copies.get(rev, {}).get(fn)
3664 if fn in skip:
3658 if fn in skip:
3665 if copy:
3659 if copy:
3666 skip.add(copy)
3660 skip.add(copy)
3667 continue
3661 continue
3668 pstates = matches.get(parent, {}).get(copy or fn, [])
3662 pstates = matches.get(parent, {}).get(copy or fn, [])
3669 if pstates or states:
3663 if pstates or states:
3670 r = display(fm, fn, ctx, pstates, states)
3664 r = display(fm, fn, ctx, pstates, states)
3671 found = found or r
3665 found = found or r
3672 if r and not diff and not all_files:
3666 if r and not diff and not all_files:
3673 skip.add(fn)
3667 skip.add(fn)
3674 if copy:
3668 if copy:
3675 skip.add(copy)
3669 skip.add(copy)
3676 del revfiles[rev]
3670 del revfiles[rev]
3677 # We will keep the matches dict for the duration of the window
3671 # We will keep the matches dict for the duration of the window
3678 # clear the matches dict once the window is over
3672 # clear the matches dict once the window is over
3679 if not revfiles:
3673 if not revfiles:
3680 matches.clear()
3674 matches.clear()
3681 fm.end()
3675 fm.end()
3682
3676
3683 return not found
3677 return not found
3684
3678
3685
3679
3686 @command(
3680 @command(
3687 b'heads',
3681 b'heads',
3688 [
3682 [
3689 (
3683 (
3690 b'r',
3684 b'r',
3691 b'rev',
3685 b'rev',
3692 b'',
3686 b'',
3693 _(b'show only heads which are descendants of STARTREV'),
3687 _(b'show only heads which are descendants of STARTREV'),
3694 _(b'STARTREV'),
3688 _(b'STARTREV'),
3695 ),
3689 ),
3696 (b't', b'topo', False, _(b'show topological heads only')),
3690 (b't', b'topo', False, _(b'show topological heads only')),
3697 (
3691 (
3698 b'a',
3692 b'a',
3699 b'active',
3693 b'active',
3700 False,
3694 False,
3701 _(b'show active branchheads only (DEPRECATED)'),
3695 _(b'show active branchheads only (DEPRECATED)'),
3702 ),
3696 ),
3703 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3697 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3704 ]
3698 ]
3705 + templateopts,
3699 + templateopts,
3706 _(b'[-ct] [-r STARTREV] [REV]...'),
3700 _(b'[-ct] [-r STARTREV] [REV]...'),
3707 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3701 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3708 intents={INTENT_READONLY},
3702 intents={INTENT_READONLY},
3709 )
3703 )
3710 def heads(ui, repo, *branchrevs, **opts):
3704 def heads(ui, repo, *branchrevs, **opts):
3711 """show branch heads
3705 """show branch heads
3712
3706
3713 With no arguments, show all open branch heads in the repository.
3707 With no arguments, show all open branch heads in the repository.
3714 Branch heads are changesets that have no descendants on the
3708 Branch heads are changesets that have no descendants on the
3715 same branch. They are where development generally takes place and
3709 same branch. They are where development generally takes place and
3716 are the usual targets for update and merge operations.
3710 are the usual targets for update and merge operations.
3717
3711
3718 If one or more REVs are given, only open branch heads on the
3712 If one or more REVs are given, only open branch heads on the
3719 branches associated with the specified changesets are shown. This
3713 branches associated with the specified changesets are shown. This
3720 means that you can use :hg:`heads .` to see the heads on the
3714 means that you can use :hg:`heads .` to see the heads on the
3721 currently checked-out branch.
3715 currently checked-out branch.
3722
3716
3723 If -c/--closed is specified, also show branch heads marked closed
3717 If -c/--closed is specified, also show branch heads marked closed
3724 (see :hg:`commit --close-branch`).
3718 (see :hg:`commit --close-branch`).
3725
3719
3726 If STARTREV is specified, only those heads that are descendants of
3720 If STARTREV is specified, only those heads that are descendants of
3727 STARTREV will be displayed.
3721 STARTREV will be displayed.
3728
3722
3729 If -t/--topo is specified, named branch mechanics will be ignored and only
3723 If -t/--topo is specified, named branch mechanics will be ignored and only
3730 topological heads (changesets with no children) will be shown.
3724 topological heads (changesets with no children) will be shown.
3731
3725
3732 Returns 0 if matching heads are found, 1 if not.
3726 Returns 0 if matching heads are found, 1 if not.
3733 """
3727 """
3734
3728
3735 opts = pycompat.byteskwargs(opts)
3729 opts = pycompat.byteskwargs(opts)
3736 start = None
3730 start = None
3737 rev = opts.get(b'rev')
3731 rev = opts.get(b'rev')
3738 if rev:
3732 if rev:
3739 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3740 start = scmutil.revsingle(repo, rev, None).node()
3734 start = scmutil.revsingle(repo, rev, None).node()
3741
3735
3742 if opts.get(b'topo'):
3736 if opts.get(b'topo'):
3743 heads = [repo[h] for h in repo.heads(start)]
3737 heads = [repo[h] for h in repo.heads(start)]
3744 else:
3738 else:
3745 heads = []
3739 heads = []
3746 for branch in repo.branchmap():
3740 for branch in repo.branchmap():
3747 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3741 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3748 heads = [repo[h] for h in heads]
3742 heads = [repo[h] for h in heads]
3749
3743
3750 if branchrevs:
3744 if branchrevs:
3751 branches = {
3745 branches = {
3752 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3746 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3753 }
3747 }
3754 heads = [h for h in heads if h.branch() in branches]
3748 heads = [h for h in heads if h.branch() in branches]
3755
3749
3756 if opts.get(b'active') and branchrevs:
3750 if opts.get(b'active') and branchrevs:
3757 dagheads = repo.heads(start)
3751 dagheads = repo.heads(start)
3758 heads = [h for h in heads if h.node() in dagheads]
3752 heads = [h for h in heads if h.node() in dagheads]
3759
3753
3760 if branchrevs:
3754 if branchrevs:
3761 haveheads = {h.branch() for h in heads}
3755 haveheads = {h.branch() for h in heads}
3762 if branches - haveheads:
3756 if branches - haveheads:
3763 headless = b', '.join(b for b in branches - haveheads)
3757 headless = b', '.join(b for b in branches - haveheads)
3764 msg = _(b'no open branch heads found on branches %s')
3758 msg = _(b'no open branch heads found on branches %s')
3765 if opts.get(b'rev'):
3759 if opts.get(b'rev'):
3766 msg += _(b' (started at %s)') % opts[b'rev']
3760 msg += _(b' (started at %s)') % opts[b'rev']
3767 ui.warn((msg + b'\n') % headless)
3761 ui.warn((msg + b'\n') % headless)
3768
3762
3769 if not heads:
3763 if not heads:
3770 return 1
3764 return 1
3771
3765
3772 ui.pager(b'heads')
3766 ui.pager(b'heads')
3773 heads = sorted(heads, key=lambda x: -(x.rev()))
3767 heads = sorted(heads, key=lambda x: -(x.rev()))
3774 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3768 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3775 for ctx in heads:
3769 for ctx in heads:
3776 displayer.show(ctx)
3770 displayer.show(ctx)
3777 displayer.close()
3771 displayer.close()
3778
3772
3779
3773
3780 @command(
3774 @command(
3781 b'help',
3775 b'help',
3782 [
3776 [
3783 (b'e', b'extension', None, _(b'show only help for extensions')),
3777 (b'e', b'extension', None, _(b'show only help for extensions')),
3784 (b'c', b'command', None, _(b'show only help for commands')),
3778 (b'c', b'command', None, _(b'show only help for commands')),
3785 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3779 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3786 (
3780 (
3787 b's',
3781 b's',
3788 b'system',
3782 b'system',
3789 [],
3783 [],
3790 _(b'show help for specific platform(s)'),
3784 _(b'show help for specific platform(s)'),
3791 _(b'PLATFORM'),
3785 _(b'PLATFORM'),
3792 ),
3786 ),
3793 ],
3787 ],
3794 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3788 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3795 helpcategory=command.CATEGORY_HELP,
3789 helpcategory=command.CATEGORY_HELP,
3796 norepo=True,
3790 norepo=True,
3797 intents={INTENT_READONLY},
3791 intents={INTENT_READONLY},
3798 )
3792 )
3799 def help_(ui, name=None, **opts):
3793 def help_(ui, name=None, **opts):
3800 """show help for a given topic or a help overview
3794 """show help for a given topic or a help overview
3801
3795
3802 With no arguments, print a list of commands with short help messages.
3796 With no arguments, print a list of commands with short help messages.
3803
3797
3804 Given a topic, extension, or command name, print help for that
3798 Given a topic, extension, or command name, print help for that
3805 topic.
3799 topic.
3806
3800
3807 Returns 0 if successful.
3801 Returns 0 if successful.
3808 """
3802 """
3809
3803
3810 keep = opts.get('system') or []
3804 keep = opts.get('system') or []
3811 if len(keep) == 0:
3805 if len(keep) == 0:
3812 if pycompat.sysplatform.startswith(b'win'):
3806 if pycompat.sysplatform.startswith(b'win'):
3813 keep.append(b'windows')
3807 keep.append(b'windows')
3814 elif pycompat.sysplatform == b'OpenVMS':
3808 elif pycompat.sysplatform == b'OpenVMS':
3815 keep.append(b'vms')
3809 keep.append(b'vms')
3816 elif pycompat.sysplatform == b'plan9':
3810 elif pycompat.sysplatform == b'plan9':
3817 keep.append(b'plan9')
3811 keep.append(b'plan9')
3818 else:
3812 else:
3819 keep.append(b'unix')
3813 keep.append(b'unix')
3820 keep.append(pycompat.sysplatform.lower())
3814 keep.append(pycompat.sysplatform.lower())
3821 if ui.verbose:
3815 if ui.verbose:
3822 keep.append(b'verbose')
3816 keep.append(b'verbose')
3823
3817
3824 commands = sys.modules[__name__]
3818 commands = sys.modules[__name__]
3825 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3819 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3826 ui.pager(b'help')
3820 ui.pager(b'help')
3827 ui.write(formatted)
3821 ui.write(formatted)
3828
3822
3829
3823
3830 @command(
3824 @command(
3831 b'identify|id',
3825 b'identify|id',
3832 [
3826 [
3833 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3827 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3834 (b'n', b'num', None, _(b'show local revision number')),
3828 (b'n', b'num', None, _(b'show local revision number')),
3835 (b'i', b'id', None, _(b'show global revision id')),
3829 (b'i', b'id', None, _(b'show global revision id')),
3836 (b'b', b'branch', None, _(b'show branch')),
3830 (b'b', b'branch', None, _(b'show branch')),
3837 (b't', b'tags', None, _(b'show tags')),
3831 (b't', b'tags', None, _(b'show tags')),
3838 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3832 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3839 ]
3833 ]
3840 + remoteopts
3834 + remoteopts
3841 + formatteropts,
3835 + formatteropts,
3842 _(b'[-nibtB] [-r REV] [SOURCE]'),
3836 _(b'[-nibtB] [-r REV] [SOURCE]'),
3843 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3837 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3844 optionalrepo=True,
3838 optionalrepo=True,
3845 intents={INTENT_READONLY},
3839 intents={INTENT_READONLY},
3846 )
3840 )
3847 def identify(
3841 def identify(
3848 ui,
3842 ui,
3849 repo,
3843 repo,
3850 source=None,
3844 source=None,
3851 rev=None,
3845 rev=None,
3852 num=None,
3846 num=None,
3853 id=None,
3847 id=None,
3854 branch=None,
3848 branch=None,
3855 tags=None,
3849 tags=None,
3856 bookmarks=None,
3850 bookmarks=None,
3857 **opts
3851 **opts
3858 ):
3852 ):
3859 """identify the working directory or specified revision
3853 """identify the working directory or specified revision
3860
3854
3861 Print a summary identifying the repository state at REV using one or
3855 Print a summary identifying the repository state at REV using one or
3862 two parent hash identifiers, followed by a "+" if the working
3856 two parent hash identifiers, followed by a "+" if the working
3863 directory has uncommitted changes, the branch name (if not default),
3857 directory has uncommitted changes, the branch name (if not default),
3864 a list of tags, and a list of bookmarks.
3858 a list of tags, and a list of bookmarks.
3865
3859
3866 When REV is not given, print a summary of the current state of the
3860 When REV is not given, print a summary of the current state of the
3867 repository including the working directory. Specify -r. to get information
3861 repository including the working directory. Specify -r. to get information
3868 of the working directory parent without scanning uncommitted changes.
3862 of the working directory parent without scanning uncommitted changes.
3869
3863
3870 Specifying a path to a repository root or Mercurial bundle will
3864 Specifying a path to a repository root or Mercurial bundle will
3871 cause lookup to operate on that repository/bundle.
3865 cause lookup to operate on that repository/bundle.
3872
3866
3873 .. container:: verbose
3867 .. container:: verbose
3874
3868
3875 Template:
3869 Template:
3876
3870
3877 The following keywords are supported in addition to the common template
3871 The following keywords are supported in addition to the common template
3878 keywords and functions. See also :hg:`help templates`.
3872 keywords and functions. See also :hg:`help templates`.
3879
3873
3880 :dirty: String. Character ``+`` denoting if the working directory has
3874 :dirty: String. Character ``+`` denoting if the working directory has
3881 uncommitted changes.
3875 uncommitted changes.
3882 :id: String. One or two nodes, optionally followed by ``+``.
3876 :id: String. One or two nodes, optionally followed by ``+``.
3883 :parents: List of strings. Parent nodes of the changeset.
3877 :parents: List of strings. Parent nodes of the changeset.
3884
3878
3885 Examples:
3879 Examples:
3886
3880
3887 - generate a build identifier for the working directory::
3881 - generate a build identifier for the working directory::
3888
3882
3889 hg id --id > build-id.dat
3883 hg id --id > build-id.dat
3890
3884
3891 - find the revision corresponding to a tag::
3885 - find the revision corresponding to a tag::
3892
3886
3893 hg id -n -r 1.3
3887 hg id -n -r 1.3
3894
3888
3895 - check the most recent revision of a remote repository::
3889 - check the most recent revision of a remote repository::
3896
3890
3897 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3891 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3898
3892
3899 See :hg:`log` for generating more information about specific revisions,
3893 See :hg:`log` for generating more information about specific revisions,
3900 including full hash identifiers.
3894 including full hash identifiers.
3901
3895
3902 Returns 0 if successful.
3896 Returns 0 if successful.
3903 """
3897 """
3904
3898
3905 opts = pycompat.byteskwargs(opts)
3899 opts = pycompat.byteskwargs(opts)
3906 if not repo and not source:
3900 if not repo and not source:
3907 raise error.Abort(
3901 raise error.Abort(
3908 _(b"there is no Mercurial repository here (.hg not found)")
3902 _(b"there is no Mercurial repository here (.hg not found)")
3909 )
3903 )
3910
3904
3911 default = not (num or id or branch or tags or bookmarks)
3905 default = not (num or id or branch or tags or bookmarks)
3912 output = []
3906 output = []
3913 revs = []
3907 revs = []
3914
3908
3915 if source:
3909 if source:
3916 source, branches = hg.parseurl(ui.expandpath(source))
3910 source, branches = hg.parseurl(ui.expandpath(source))
3917 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3911 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3918 repo = peer.local()
3912 repo = peer.local()
3919 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3913 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3920
3914
3921 fm = ui.formatter(b'identify', opts)
3915 fm = ui.formatter(b'identify', opts)
3922 fm.startitem()
3916 fm.startitem()
3923
3917
3924 if not repo:
3918 if not repo:
3925 if num or branch or tags:
3919 if num or branch or tags:
3926 raise error.Abort(
3920 raise error.Abort(
3927 _(b"can't query remote revision number, branch, or tags")
3921 _(b"can't query remote revision number, branch, or tags")
3928 )
3922 )
3929 if not rev and revs:
3923 if not rev and revs:
3930 rev = revs[0]
3924 rev = revs[0]
3931 if not rev:
3925 if not rev:
3932 rev = b"tip"
3926 rev = b"tip"
3933
3927
3934 remoterev = peer.lookup(rev)
3928 remoterev = peer.lookup(rev)
3935 hexrev = fm.hexfunc(remoterev)
3929 hexrev = fm.hexfunc(remoterev)
3936 if default or id:
3930 if default or id:
3937 output = [hexrev]
3931 output = [hexrev]
3938 fm.data(id=hexrev)
3932 fm.data(id=hexrev)
3939
3933
3940 @util.cachefunc
3934 @util.cachefunc
3941 def getbms():
3935 def getbms():
3942 bms = []
3936 bms = []
3943
3937
3944 if b'bookmarks' in peer.listkeys(b'namespaces'):
3938 if b'bookmarks' in peer.listkeys(b'namespaces'):
3945 hexremoterev = hex(remoterev)
3939 hexremoterev = hex(remoterev)
3946 bms = [
3940 bms = [
3947 bm
3941 bm
3948 for bm, bmr in pycompat.iteritems(
3942 for bm, bmr in pycompat.iteritems(
3949 peer.listkeys(b'bookmarks')
3943 peer.listkeys(b'bookmarks')
3950 )
3944 )
3951 if bmr == hexremoterev
3945 if bmr == hexremoterev
3952 ]
3946 ]
3953
3947
3954 return sorted(bms)
3948 return sorted(bms)
3955
3949
3956 if fm.isplain():
3950 if fm.isplain():
3957 if bookmarks:
3951 if bookmarks:
3958 output.extend(getbms())
3952 output.extend(getbms())
3959 elif default and not ui.quiet:
3953 elif default and not ui.quiet:
3960 # multiple bookmarks for a single parent separated by '/'
3954 # multiple bookmarks for a single parent separated by '/'
3961 bm = b'/'.join(getbms())
3955 bm = b'/'.join(getbms())
3962 if bm:
3956 if bm:
3963 output.append(bm)
3957 output.append(bm)
3964 else:
3958 else:
3965 fm.data(node=hex(remoterev))
3959 fm.data(node=hex(remoterev))
3966 if bookmarks or b'bookmarks' in fm.datahint():
3960 if bookmarks or b'bookmarks' in fm.datahint():
3967 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3961 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3968 else:
3962 else:
3969 if rev:
3963 if rev:
3970 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3964 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3971 ctx = scmutil.revsingle(repo, rev, None)
3965 ctx = scmutil.revsingle(repo, rev, None)
3972
3966
3973 if ctx.rev() is None:
3967 if ctx.rev() is None:
3974 ctx = repo[None]
3968 ctx = repo[None]
3975 parents = ctx.parents()
3969 parents = ctx.parents()
3976 taglist = []
3970 taglist = []
3977 for p in parents:
3971 for p in parents:
3978 taglist.extend(p.tags())
3972 taglist.extend(p.tags())
3979
3973
3980 dirty = b""
3974 dirty = b""
3981 if ctx.dirty(missing=True, merge=False, branch=False):
3975 if ctx.dirty(missing=True, merge=False, branch=False):
3982 dirty = b'+'
3976 dirty = b'+'
3983 fm.data(dirty=dirty)
3977 fm.data(dirty=dirty)
3984
3978
3985 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3979 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3986 if default or id:
3980 if default or id:
3987 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3981 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3988 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3982 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3989
3983
3990 if num:
3984 if num:
3991 numoutput = [b"%d" % p.rev() for p in parents]
3985 numoutput = [b"%d" % p.rev() for p in parents]
3992 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3986 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3993
3987
3994 fm.data(
3988 fm.data(
3995 parents=fm.formatlist(
3989 parents=fm.formatlist(
3996 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3990 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3997 )
3991 )
3998 )
3992 )
3999 else:
3993 else:
4000 hexoutput = fm.hexfunc(ctx.node())
3994 hexoutput = fm.hexfunc(ctx.node())
4001 if default or id:
3995 if default or id:
4002 output = [hexoutput]
3996 output = [hexoutput]
4003 fm.data(id=hexoutput)
3997 fm.data(id=hexoutput)
4004
3998
4005 if num:
3999 if num:
4006 output.append(pycompat.bytestr(ctx.rev()))
4000 output.append(pycompat.bytestr(ctx.rev()))
4007 taglist = ctx.tags()
4001 taglist = ctx.tags()
4008
4002
4009 if default and not ui.quiet:
4003 if default and not ui.quiet:
4010 b = ctx.branch()
4004 b = ctx.branch()
4011 if b != b'default':
4005 if b != b'default':
4012 output.append(b"(%s)" % b)
4006 output.append(b"(%s)" % b)
4013
4007
4014 # multiple tags for a single parent separated by '/'
4008 # multiple tags for a single parent separated by '/'
4015 t = b'/'.join(taglist)
4009 t = b'/'.join(taglist)
4016 if t:
4010 if t:
4017 output.append(t)
4011 output.append(t)
4018
4012
4019 # multiple bookmarks for a single parent separated by '/'
4013 # multiple bookmarks for a single parent separated by '/'
4020 bm = b'/'.join(ctx.bookmarks())
4014 bm = b'/'.join(ctx.bookmarks())
4021 if bm:
4015 if bm:
4022 output.append(bm)
4016 output.append(bm)
4023 else:
4017 else:
4024 if branch:
4018 if branch:
4025 output.append(ctx.branch())
4019 output.append(ctx.branch())
4026
4020
4027 if tags:
4021 if tags:
4028 output.extend(taglist)
4022 output.extend(taglist)
4029
4023
4030 if bookmarks:
4024 if bookmarks:
4031 output.extend(ctx.bookmarks())
4025 output.extend(ctx.bookmarks())
4032
4026
4033 fm.data(node=ctx.hex())
4027 fm.data(node=ctx.hex())
4034 fm.data(branch=ctx.branch())
4028 fm.data(branch=ctx.branch())
4035 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4029 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4036 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4030 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4037 fm.context(ctx=ctx)
4031 fm.context(ctx=ctx)
4038
4032
4039 fm.plain(b"%s\n" % b' '.join(output))
4033 fm.plain(b"%s\n" % b' '.join(output))
4040 fm.end()
4034 fm.end()
4041
4035
4042
4036
4043 @command(
4037 @command(
4044 b'import|patch',
4038 b'import|patch',
4045 [
4039 [
4046 (
4040 (
4047 b'p',
4041 b'p',
4048 b'strip',
4042 b'strip',
4049 1,
4043 1,
4050 _(
4044 _(
4051 b'directory strip option for patch. This has the same '
4045 b'directory strip option for patch. This has the same '
4052 b'meaning as the corresponding patch option'
4046 b'meaning as the corresponding patch option'
4053 ),
4047 ),
4054 _(b'NUM'),
4048 _(b'NUM'),
4055 ),
4049 ),
4056 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4050 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4057 (b'', b'secret', None, _(b'use the secret phase for committing')),
4051 (b'', b'secret', None, _(b'use the secret phase for committing')),
4058 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4052 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4059 (
4053 (
4060 b'f',
4054 b'f',
4061 b'force',
4055 b'force',
4062 None,
4056 None,
4063 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4057 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4064 ),
4058 ),
4065 (
4059 (
4066 b'',
4060 b'',
4067 b'no-commit',
4061 b'no-commit',
4068 None,
4062 None,
4069 _(b"don't commit, just update the working directory"),
4063 _(b"don't commit, just update the working directory"),
4070 ),
4064 ),
4071 (
4065 (
4072 b'',
4066 b'',
4073 b'bypass',
4067 b'bypass',
4074 None,
4068 None,
4075 _(b"apply patch without touching the working directory"),
4069 _(b"apply patch without touching the working directory"),
4076 ),
4070 ),
4077 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4071 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4078 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4072 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4079 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4073 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4080 (
4074 (
4081 b'',
4075 b'',
4082 b'import-branch',
4076 b'import-branch',
4083 None,
4077 None,
4084 _(b'use any branch information in patch (implied by --exact)'),
4078 _(b'use any branch information in patch (implied by --exact)'),
4085 ),
4079 ),
4086 ]
4080 ]
4087 + commitopts
4081 + commitopts
4088 + commitopts2
4082 + commitopts2
4089 + similarityopts,
4083 + similarityopts,
4090 _(b'[OPTION]... PATCH...'),
4084 _(b'[OPTION]... PATCH...'),
4091 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4085 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4092 )
4086 )
4093 def import_(ui, repo, patch1=None, *patches, **opts):
4087 def import_(ui, repo, patch1=None, *patches, **opts):
4094 """import an ordered set of patches
4088 """import an ordered set of patches
4095
4089
4096 Import a list of patches and commit them individually (unless
4090 Import a list of patches and commit them individually (unless
4097 --no-commit is specified).
4091 --no-commit is specified).
4098
4092
4099 To read a patch from standard input (stdin), use "-" as the patch
4093 To read a patch from standard input (stdin), use "-" as the patch
4100 name. If a URL is specified, the patch will be downloaded from
4094 name. If a URL is specified, the patch will be downloaded from
4101 there.
4095 there.
4102
4096
4103 Import first applies changes to the working directory (unless
4097 Import first applies changes to the working directory (unless
4104 --bypass is specified), import will abort if there are outstanding
4098 --bypass is specified), import will abort if there are outstanding
4105 changes.
4099 changes.
4106
4100
4107 Use --bypass to apply and commit patches directly to the
4101 Use --bypass to apply and commit patches directly to the
4108 repository, without affecting the working directory. Without
4102 repository, without affecting the working directory. Without
4109 --exact, patches will be applied on top of the working directory
4103 --exact, patches will be applied on top of the working directory
4110 parent revision.
4104 parent revision.
4111
4105
4112 You can import a patch straight from a mail message. Even patches
4106 You can import a patch straight from a mail message. Even patches
4113 as attachments work (to use the body part, it must have type
4107 as attachments work (to use the body part, it must have type
4114 text/plain or text/x-patch). From and Subject headers of email
4108 text/plain or text/x-patch). From and Subject headers of email
4115 message are used as default committer and commit message. All
4109 message are used as default committer and commit message. All
4116 text/plain body parts before first diff are added to the commit
4110 text/plain body parts before first diff are added to the commit
4117 message.
4111 message.
4118
4112
4119 If the imported patch was generated by :hg:`export`, user and
4113 If the imported patch was generated by :hg:`export`, user and
4120 description from patch override values from message headers and
4114 description from patch override values from message headers and
4121 body. Values given on command line with -m/--message and -u/--user
4115 body. Values given on command line with -m/--message and -u/--user
4122 override these.
4116 override these.
4123
4117
4124 If --exact is specified, import will set the working directory to
4118 If --exact is specified, import will set the working directory to
4125 the parent of each patch before applying it, and will abort if the
4119 the parent of each patch before applying it, and will abort if the
4126 resulting changeset has a different ID than the one recorded in
4120 resulting changeset has a different ID than the one recorded in
4127 the patch. This will guard against various ways that portable
4121 the patch. This will guard against various ways that portable
4128 patch formats and mail systems might fail to transfer Mercurial
4122 patch formats and mail systems might fail to transfer Mercurial
4129 data or metadata. See :hg:`bundle` for lossless transmission.
4123 data or metadata. See :hg:`bundle` for lossless transmission.
4130
4124
4131 Use --partial to ensure a changeset will be created from the patch
4125 Use --partial to ensure a changeset will be created from the patch
4132 even if some hunks fail to apply. Hunks that fail to apply will be
4126 even if some hunks fail to apply. Hunks that fail to apply will be
4133 written to a <target-file>.rej file. Conflicts can then be resolved
4127 written to a <target-file>.rej file. Conflicts can then be resolved
4134 by hand before :hg:`commit --amend` is run to update the created
4128 by hand before :hg:`commit --amend` is run to update the created
4135 changeset. This flag exists to let people import patches that
4129 changeset. This flag exists to let people import patches that
4136 partially apply without losing the associated metadata (author,
4130 partially apply without losing the associated metadata (author,
4137 date, description, ...).
4131 date, description, ...).
4138
4132
4139 .. note::
4133 .. note::
4140
4134
4141 When no hunks apply cleanly, :hg:`import --partial` will create
4135 When no hunks apply cleanly, :hg:`import --partial` will create
4142 an empty changeset, importing only the patch metadata.
4136 an empty changeset, importing only the patch metadata.
4143
4137
4144 With -s/--similarity, hg will attempt to discover renames and
4138 With -s/--similarity, hg will attempt to discover renames and
4145 copies in the patch in the same way as :hg:`addremove`.
4139 copies in the patch in the same way as :hg:`addremove`.
4146
4140
4147 It is possible to use external patch programs to perform the patch
4141 It is possible to use external patch programs to perform the patch
4148 by setting the ``ui.patch`` configuration option. For the default
4142 by setting the ``ui.patch`` configuration option. For the default
4149 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4143 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4150 See :hg:`help config` for more information about configuration
4144 See :hg:`help config` for more information about configuration
4151 files and how to use these options.
4145 files and how to use these options.
4152
4146
4153 See :hg:`help dates` for a list of formats valid for -d/--date.
4147 See :hg:`help dates` for a list of formats valid for -d/--date.
4154
4148
4155 .. container:: verbose
4149 .. container:: verbose
4156
4150
4157 Examples:
4151 Examples:
4158
4152
4159 - import a traditional patch from a website and detect renames::
4153 - import a traditional patch from a website and detect renames::
4160
4154
4161 hg import -s 80 http://example.com/bugfix.patch
4155 hg import -s 80 http://example.com/bugfix.patch
4162
4156
4163 - import a changeset from an hgweb server::
4157 - import a changeset from an hgweb server::
4164
4158
4165 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4159 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4166
4160
4167 - import all the patches in an Unix-style mbox::
4161 - import all the patches in an Unix-style mbox::
4168
4162
4169 hg import incoming-patches.mbox
4163 hg import incoming-patches.mbox
4170
4164
4171 - import patches from stdin::
4165 - import patches from stdin::
4172
4166
4173 hg import -
4167 hg import -
4174
4168
4175 - attempt to exactly restore an exported changeset (not always
4169 - attempt to exactly restore an exported changeset (not always
4176 possible)::
4170 possible)::
4177
4171
4178 hg import --exact proposed-fix.patch
4172 hg import --exact proposed-fix.patch
4179
4173
4180 - use an external tool to apply a patch which is too fuzzy for
4174 - use an external tool to apply a patch which is too fuzzy for
4181 the default internal tool.
4175 the default internal tool.
4182
4176
4183 hg import --config ui.patch="patch --merge" fuzzy.patch
4177 hg import --config ui.patch="patch --merge" fuzzy.patch
4184
4178
4185 - change the default fuzzing from 2 to a less strict 7
4179 - change the default fuzzing from 2 to a less strict 7
4186
4180
4187 hg import --config ui.fuzz=7 fuzz.patch
4181 hg import --config ui.fuzz=7 fuzz.patch
4188
4182
4189 Returns 0 on success, 1 on partial success (see --partial).
4183 Returns 0 on success, 1 on partial success (see --partial).
4190 """
4184 """
4191
4185
4192 opts = pycompat.byteskwargs(opts)
4186 opts = pycompat.byteskwargs(opts)
4193 if not patch1:
4187 if not patch1:
4194 raise error.Abort(_(b'need at least one patch to import'))
4188 raise error.Abort(_(b'need at least one patch to import'))
4195
4189
4196 patches = (patch1,) + patches
4190 patches = (patch1,) + patches
4197
4191
4198 date = opts.get(b'date')
4192 date = opts.get(b'date')
4199 if date:
4193 if date:
4200 opts[b'date'] = dateutil.parsedate(date)
4194 opts[b'date'] = dateutil.parsedate(date)
4201
4195
4202 exact = opts.get(b'exact')
4196 exact = opts.get(b'exact')
4203 update = not opts.get(b'bypass')
4197 update = not opts.get(b'bypass')
4204 if not update and opts.get(b'no_commit'):
4198 if not update and opts.get(b'no_commit'):
4205 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4199 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4206 if opts.get(b'secret') and opts.get(b'no_commit'):
4200 if opts.get(b'secret') and opts.get(b'no_commit'):
4207 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4201 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4208 try:
4202 try:
4209 sim = float(opts.get(b'similarity') or 0)
4203 sim = float(opts.get(b'similarity') or 0)
4210 except ValueError:
4204 except ValueError:
4211 raise error.Abort(_(b'similarity must be a number'))
4205 raise error.Abort(_(b'similarity must be a number'))
4212 if sim < 0 or sim > 100:
4206 if sim < 0 or sim > 100:
4213 raise error.Abort(_(b'similarity must be between 0 and 100'))
4207 raise error.Abort(_(b'similarity must be between 0 and 100'))
4214 if sim and not update:
4208 if sim and not update:
4215 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4209 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4216 if exact:
4210 if exact:
4217 if opts.get(b'edit'):
4211 if opts.get(b'edit'):
4218 raise error.Abort(_(b'cannot use --exact with --edit'))
4212 raise error.Abort(_(b'cannot use --exact with --edit'))
4219 if opts.get(b'prefix'):
4213 if opts.get(b'prefix'):
4220 raise error.Abort(_(b'cannot use --exact with --prefix'))
4214 raise error.Abort(_(b'cannot use --exact with --prefix'))
4221
4215
4222 base = opts[b"base"]
4216 base = opts[b"base"]
4223 msgs = []
4217 msgs = []
4224 ret = 0
4218 ret = 0
4225
4219
4226 with repo.wlock():
4220 with repo.wlock():
4227 if update:
4221 if update:
4228 cmdutil.checkunfinished(repo)
4222 cmdutil.checkunfinished(repo)
4229 if exact or not opts.get(b'force'):
4223 if exact or not opts.get(b'force'):
4230 cmdutil.bailifchanged(repo)
4224 cmdutil.bailifchanged(repo)
4231
4225
4232 if not opts.get(b'no_commit'):
4226 if not opts.get(b'no_commit'):
4233 lock = repo.lock
4227 lock = repo.lock
4234 tr = lambda: repo.transaction(b'import')
4228 tr = lambda: repo.transaction(b'import')
4235 dsguard = util.nullcontextmanager
4229 dsguard = util.nullcontextmanager
4236 else:
4230 else:
4237 lock = util.nullcontextmanager
4231 lock = util.nullcontextmanager
4238 tr = util.nullcontextmanager
4232 tr = util.nullcontextmanager
4239 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4233 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4240 with lock(), tr(), dsguard():
4234 with lock(), tr(), dsguard():
4241 parents = repo[None].parents()
4235 parents = repo[None].parents()
4242 for patchurl in patches:
4236 for patchurl in patches:
4243 if patchurl == b'-':
4237 if patchurl == b'-':
4244 ui.status(_(b'applying patch from stdin\n'))
4238 ui.status(_(b'applying patch from stdin\n'))
4245 patchfile = ui.fin
4239 patchfile = ui.fin
4246 patchurl = b'stdin' # for error message
4240 patchurl = b'stdin' # for error message
4247 else:
4241 else:
4248 patchurl = os.path.join(base, patchurl)
4242 patchurl = os.path.join(base, patchurl)
4249 ui.status(_(b'applying %s\n') % patchurl)
4243 ui.status(_(b'applying %s\n') % patchurl)
4250 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4244 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4251
4245
4252 haspatch = False
4246 haspatch = False
4253 for hunk in patch.split(patchfile):
4247 for hunk in patch.split(patchfile):
4254 with patch.extract(ui, hunk) as patchdata:
4248 with patch.extract(ui, hunk) as patchdata:
4255 msg, node, rej = cmdutil.tryimportone(
4249 msg, node, rej = cmdutil.tryimportone(
4256 ui, repo, patchdata, parents, opts, msgs, hg.clean
4250 ui, repo, patchdata, parents, opts, msgs, hg.clean
4257 )
4251 )
4258 if msg:
4252 if msg:
4259 haspatch = True
4253 haspatch = True
4260 ui.note(msg + b'\n')
4254 ui.note(msg + b'\n')
4261 if update or exact:
4255 if update or exact:
4262 parents = repo[None].parents()
4256 parents = repo[None].parents()
4263 else:
4257 else:
4264 parents = [repo[node]]
4258 parents = [repo[node]]
4265 if rej:
4259 if rej:
4266 ui.write_err(_(b"patch applied partially\n"))
4260 ui.write_err(_(b"patch applied partially\n"))
4267 ui.write_err(
4261 ui.write_err(
4268 _(
4262 _(
4269 b"(fix the .rej files and run "
4263 b"(fix the .rej files and run "
4270 b"`hg commit --amend`)\n"
4264 b"`hg commit --amend`)\n"
4271 )
4265 )
4272 )
4266 )
4273 ret = 1
4267 ret = 1
4274 break
4268 break
4275
4269
4276 if not haspatch:
4270 if not haspatch:
4277 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4271 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4278
4272
4279 if msgs:
4273 if msgs:
4280 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4274 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4281 return ret
4275 return ret
4282
4276
4283
4277
4284 @command(
4278 @command(
4285 b'incoming|in',
4279 b'incoming|in',
4286 [
4280 [
4287 (
4281 (
4288 b'f',
4282 b'f',
4289 b'force',
4283 b'force',
4290 None,
4284 None,
4291 _(b'run even if remote repository is unrelated'),
4285 _(b'run even if remote repository is unrelated'),
4292 ),
4286 ),
4293 (b'n', b'newest-first', None, _(b'show newest record first')),
4287 (b'n', b'newest-first', None, _(b'show newest record first')),
4294 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4288 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4295 (
4289 (
4296 b'r',
4290 b'r',
4297 b'rev',
4291 b'rev',
4298 [],
4292 [],
4299 _(b'a remote changeset intended to be added'),
4293 _(b'a remote changeset intended to be added'),
4300 _(b'REV'),
4294 _(b'REV'),
4301 ),
4295 ),
4302 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4296 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4303 (
4297 (
4304 b'b',
4298 b'b',
4305 b'branch',
4299 b'branch',
4306 [],
4300 [],
4307 _(b'a specific branch you would like to pull'),
4301 _(b'a specific branch you would like to pull'),
4308 _(b'BRANCH'),
4302 _(b'BRANCH'),
4309 ),
4303 ),
4310 ]
4304 ]
4311 + logopts
4305 + logopts
4312 + remoteopts
4306 + remoteopts
4313 + subrepoopts,
4307 + subrepoopts,
4314 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4308 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4315 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4309 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4316 )
4310 )
4317 def incoming(ui, repo, source=b"default", **opts):
4311 def incoming(ui, repo, source=b"default", **opts):
4318 """show new changesets found in source
4312 """show new changesets found in source
4319
4313
4320 Show new changesets found in the specified path/URL or the default
4314 Show new changesets found in the specified path/URL or the default
4321 pull location. These are the changesets that would have been pulled
4315 pull location. These are the changesets that would have been pulled
4322 by :hg:`pull` at the time you issued this command.
4316 by :hg:`pull` at the time you issued this command.
4323
4317
4324 See pull for valid source format details.
4318 See pull for valid source format details.
4325
4319
4326 .. container:: verbose
4320 .. container:: verbose
4327
4321
4328 With -B/--bookmarks, the result of bookmark comparison between
4322 With -B/--bookmarks, the result of bookmark comparison between
4329 local and remote repositories is displayed. With -v/--verbose,
4323 local and remote repositories is displayed. With -v/--verbose,
4330 status is also displayed for each bookmark like below::
4324 status is also displayed for each bookmark like below::
4331
4325
4332 BM1 01234567890a added
4326 BM1 01234567890a added
4333 BM2 1234567890ab advanced
4327 BM2 1234567890ab advanced
4334 BM3 234567890abc diverged
4328 BM3 234567890abc diverged
4335 BM4 34567890abcd changed
4329 BM4 34567890abcd changed
4336
4330
4337 The action taken locally when pulling depends on the
4331 The action taken locally when pulling depends on the
4338 status of each bookmark:
4332 status of each bookmark:
4339
4333
4340 :``added``: pull will create it
4334 :``added``: pull will create it
4341 :``advanced``: pull will update it
4335 :``advanced``: pull will update it
4342 :``diverged``: pull will create a divergent bookmark
4336 :``diverged``: pull will create a divergent bookmark
4343 :``changed``: result depends on remote changesets
4337 :``changed``: result depends on remote changesets
4344
4338
4345 From the point of view of pulling behavior, bookmark
4339 From the point of view of pulling behavior, bookmark
4346 existing only in the remote repository are treated as ``added``,
4340 existing only in the remote repository are treated as ``added``,
4347 even if it is in fact locally deleted.
4341 even if it is in fact locally deleted.
4348
4342
4349 .. container:: verbose
4343 .. container:: verbose
4350
4344
4351 For remote repository, using --bundle avoids downloading the
4345 For remote repository, using --bundle avoids downloading the
4352 changesets twice if the incoming is followed by a pull.
4346 changesets twice if the incoming is followed by a pull.
4353
4347
4354 Examples:
4348 Examples:
4355
4349
4356 - show incoming changes with patches and full description::
4350 - show incoming changes with patches and full description::
4357
4351
4358 hg incoming -vp
4352 hg incoming -vp
4359
4353
4360 - show incoming changes excluding merges, store a bundle::
4354 - show incoming changes excluding merges, store a bundle::
4361
4355
4362 hg in -vpM --bundle incoming.hg
4356 hg in -vpM --bundle incoming.hg
4363 hg pull incoming.hg
4357 hg pull incoming.hg
4364
4358
4365 - briefly list changes inside a bundle::
4359 - briefly list changes inside a bundle::
4366
4360
4367 hg in changes.hg -T "{desc|firstline}\\n"
4361 hg in changes.hg -T "{desc|firstline}\\n"
4368
4362
4369 Returns 0 if there are incoming changes, 1 otherwise.
4363 Returns 0 if there are incoming changes, 1 otherwise.
4370 """
4364 """
4371 opts = pycompat.byteskwargs(opts)
4365 opts = pycompat.byteskwargs(opts)
4372 if opts.get(b'graph'):
4366 if opts.get(b'graph'):
4373 logcmdutil.checkunsupportedgraphflags([], opts)
4367 logcmdutil.checkunsupportedgraphflags([], opts)
4374
4368
4375 def display(other, chlist, displayer):
4369 def display(other, chlist, displayer):
4376 revdag = logcmdutil.graphrevs(other, chlist, opts)
4370 revdag = logcmdutil.graphrevs(other, chlist, opts)
4377 logcmdutil.displaygraph(
4371 logcmdutil.displaygraph(
4378 ui, repo, revdag, displayer, graphmod.asciiedges
4372 ui, repo, revdag, displayer, graphmod.asciiedges
4379 )
4373 )
4380
4374
4381 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4375 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4382 return 0
4376 return 0
4383
4377
4384 if opts.get(b'bundle') and opts.get(b'subrepos'):
4378 if opts.get(b'bundle') and opts.get(b'subrepos'):
4385 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4379 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4386
4380
4387 if opts.get(b'bookmarks'):
4381 if opts.get(b'bookmarks'):
4388 source, branches = hg.parseurl(
4382 source, branches = hg.parseurl(
4389 ui.expandpath(source), opts.get(b'branch')
4383 ui.expandpath(source), opts.get(b'branch')
4390 )
4384 )
4391 other = hg.peer(repo, opts, source)
4385 other = hg.peer(repo, opts, source)
4392 if b'bookmarks' not in other.listkeys(b'namespaces'):
4386 if b'bookmarks' not in other.listkeys(b'namespaces'):
4393 ui.warn(_(b"remote doesn't support bookmarks\n"))
4387 ui.warn(_(b"remote doesn't support bookmarks\n"))
4394 return 0
4388 return 0
4395 ui.pager(b'incoming')
4389 ui.pager(b'incoming')
4396 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4390 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4397 return bookmarks.incoming(ui, repo, other)
4391 return bookmarks.incoming(ui, repo, other)
4398
4392
4399 repo._subtoppath = ui.expandpath(source)
4393 repo._subtoppath = ui.expandpath(source)
4400 try:
4394 try:
4401 return hg.incoming(ui, repo, source, opts)
4395 return hg.incoming(ui, repo, source, opts)
4402 finally:
4396 finally:
4403 del repo._subtoppath
4397 del repo._subtoppath
4404
4398
4405
4399
4406 @command(
4400 @command(
4407 b'init',
4401 b'init',
4408 remoteopts,
4402 remoteopts,
4409 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4403 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4410 helpcategory=command.CATEGORY_REPO_CREATION,
4404 helpcategory=command.CATEGORY_REPO_CREATION,
4411 helpbasic=True,
4405 helpbasic=True,
4412 norepo=True,
4406 norepo=True,
4413 )
4407 )
4414 def init(ui, dest=b".", **opts):
4408 def init(ui, dest=b".", **opts):
4415 """create a new repository in the given directory
4409 """create a new repository in the given directory
4416
4410
4417 Initialize a new repository in the given directory. If the given
4411 Initialize a new repository in the given directory. If the given
4418 directory does not exist, it will be created.
4412 directory does not exist, it will be created.
4419
4413
4420 If no directory is given, the current directory is used.
4414 If no directory is given, the current directory is used.
4421
4415
4422 It is possible to specify an ``ssh://`` URL as the destination.
4416 It is possible to specify an ``ssh://`` URL as the destination.
4423 See :hg:`help urls` for more information.
4417 See :hg:`help urls` for more information.
4424
4418
4425 Returns 0 on success.
4419 Returns 0 on success.
4426 """
4420 """
4427 opts = pycompat.byteskwargs(opts)
4421 opts = pycompat.byteskwargs(opts)
4428 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4422 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4429
4423
4430
4424
4431 @command(
4425 @command(
4432 b'locate',
4426 b'locate',
4433 [
4427 [
4434 (
4428 (
4435 b'r',
4429 b'r',
4436 b'rev',
4430 b'rev',
4437 b'',
4431 b'',
4438 _(b'search the repository as it is in REV'),
4432 _(b'search the repository as it is in REV'),
4439 _(b'REV'),
4433 _(b'REV'),
4440 ),
4434 ),
4441 (
4435 (
4442 b'0',
4436 b'0',
4443 b'print0',
4437 b'print0',
4444 None,
4438 None,
4445 _(b'end filenames with NUL, for use with xargs'),
4439 _(b'end filenames with NUL, for use with xargs'),
4446 ),
4440 ),
4447 (
4441 (
4448 b'f',
4442 b'f',
4449 b'fullpath',
4443 b'fullpath',
4450 None,
4444 None,
4451 _(b'print complete paths from the filesystem root'),
4445 _(b'print complete paths from the filesystem root'),
4452 ),
4446 ),
4453 ]
4447 ]
4454 + walkopts,
4448 + walkopts,
4455 _(b'[OPTION]... [PATTERN]...'),
4449 _(b'[OPTION]... [PATTERN]...'),
4456 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4450 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4457 )
4451 )
4458 def locate(ui, repo, *pats, **opts):
4452 def locate(ui, repo, *pats, **opts):
4459 """locate files matching specific patterns (DEPRECATED)
4453 """locate files matching specific patterns (DEPRECATED)
4460
4454
4461 Print files under Mercurial control in the working directory whose
4455 Print files under Mercurial control in the working directory whose
4462 names match the given patterns.
4456 names match the given patterns.
4463
4457
4464 By default, this command searches all directories in the working
4458 By default, this command searches all directories in the working
4465 directory. To search just the current directory and its
4459 directory. To search just the current directory and its
4466 subdirectories, use "--include .".
4460 subdirectories, use "--include .".
4467
4461
4468 If no patterns are given to match, this command prints the names
4462 If no patterns are given to match, this command prints the names
4469 of all files under Mercurial control in the working directory.
4463 of all files under Mercurial control in the working directory.
4470
4464
4471 If you want to feed the output of this command into the "xargs"
4465 If you want to feed the output of this command into the "xargs"
4472 command, use the -0 option to both this command and "xargs". This
4466 command, use the -0 option to both this command and "xargs". This
4473 will avoid the problem of "xargs" treating single filenames that
4467 will avoid the problem of "xargs" treating single filenames that
4474 contain whitespace as multiple filenames.
4468 contain whitespace as multiple filenames.
4475
4469
4476 See :hg:`help files` for a more versatile command.
4470 See :hg:`help files` for a more versatile command.
4477
4471
4478 Returns 0 if a match is found, 1 otherwise.
4472 Returns 0 if a match is found, 1 otherwise.
4479 """
4473 """
4480 opts = pycompat.byteskwargs(opts)
4474 opts = pycompat.byteskwargs(opts)
4481 if opts.get(b'print0'):
4475 if opts.get(b'print0'):
4482 end = b'\0'
4476 end = b'\0'
4483 else:
4477 else:
4484 end = b'\n'
4478 end = b'\n'
4485 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4479 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4486
4480
4487 ret = 1
4481 ret = 1
4488 m = scmutil.match(
4482 m = scmutil.match(
4489 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4483 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4490 )
4484 )
4491
4485
4492 ui.pager(b'locate')
4486 ui.pager(b'locate')
4493 if ctx.rev() is None:
4487 if ctx.rev() is None:
4494 # When run on the working copy, "locate" includes removed files, so
4488 # When run on the working copy, "locate" includes removed files, so
4495 # we get the list of files from the dirstate.
4489 # we get the list of files from the dirstate.
4496 filesgen = sorted(repo.dirstate.matches(m))
4490 filesgen = sorted(repo.dirstate.matches(m))
4497 else:
4491 else:
4498 filesgen = ctx.matches(m)
4492 filesgen = ctx.matches(m)
4499 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4493 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4500 for abs in filesgen:
4494 for abs in filesgen:
4501 if opts.get(b'fullpath'):
4495 if opts.get(b'fullpath'):
4502 ui.write(repo.wjoin(abs), end)
4496 ui.write(repo.wjoin(abs), end)
4503 else:
4497 else:
4504 ui.write(uipathfn(abs), end)
4498 ui.write(uipathfn(abs), end)
4505 ret = 0
4499 ret = 0
4506
4500
4507 return ret
4501 return ret
4508
4502
4509
4503
4510 @command(
4504 @command(
4511 b'log|history',
4505 b'log|history',
4512 [
4506 [
4513 (
4507 (
4514 b'f',
4508 b'f',
4515 b'follow',
4509 b'follow',
4516 None,
4510 None,
4517 _(
4511 _(
4518 b'follow changeset history, or file history across copies and renames'
4512 b'follow changeset history, or file history across copies and renames'
4519 ),
4513 ),
4520 ),
4514 ),
4521 (
4515 (
4522 b'',
4516 b'',
4523 b'follow-first',
4517 b'follow-first',
4524 None,
4518 None,
4525 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4519 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4526 ),
4520 ),
4527 (
4521 (
4528 b'd',
4522 b'd',
4529 b'date',
4523 b'date',
4530 b'',
4524 b'',
4531 _(b'show revisions matching date spec'),
4525 _(b'show revisions matching date spec'),
4532 _(b'DATE'),
4526 _(b'DATE'),
4533 ),
4527 ),
4534 (b'C', b'copies', None, _(b'show copied files')),
4528 (b'C', b'copies', None, _(b'show copied files')),
4535 (
4529 (
4536 b'k',
4530 b'k',
4537 b'keyword',
4531 b'keyword',
4538 [],
4532 [],
4539 _(b'do case-insensitive search for a given text'),
4533 _(b'do case-insensitive search for a given text'),
4540 _(b'TEXT'),
4534 _(b'TEXT'),
4541 ),
4535 ),
4542 (
4536 (
4543 b'r',
4537 b'r',
4544 b'rev',
4538 b'rev',
4545 [],
4539 [],
4546 _(b'show the specified revision or revset'),
4540 _(b'show the specified revision or revset'),
4547 _(b'REV'),
4541 _(b'REV'),
4548 ),
4542 ),
4549 (
4543 (
4550 b'L',
4544 b'L',
4551 b'line-range',
4545 b'line-range',
4552 [],
4546 [],
4553 _(b'follow line range of specified file (EXPERIMENTAL)'),
4547 _(b'follow line range of specified file (EXPERIMENTAL)'),
4554 _(b'FILE,RANGE'),
4548 _(b'FILE,RANGE'),
4555 ),
4549 ),
4556 (
4550 (
4557 b'',
4551 b'',
4558 b'removed',
4552 b'removed',
4559 None,
4553 None,
4560 _(b'include revisions where files were removed'),
4554 _(b'include revisions where files were removed'),
4561 ),
4555 ),
4562 (
4556 (
4563 b'm',
4557 b'm',
4564 b'only-merges',
4558 b'only-merges',
4565 None,
4559 None,
4566 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4560 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4567 ),
4561 ),
4568 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4562 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4569 (
4563 (
4570 b'',
4564 b'',
4571 b'only-branch',
4565 b'only-branch',
4572 [],
4566 [],
4573 _(
4567 _(
4574 b'show only changesets within the given named branch (DEPRECATED)'
4568 b'show only changesets within the given named branch (DEPRECATED)'
4575 ),
4569 ),
4576 _(b'BRANCH'),
4570 _(b'BRANCH'),
4577 ),
4571 ),
4578 (
4572 (
4579 b'b',
4573 b'b',
4580 b'branch',
4574 b'branch',
4581 [],
4575 [],
4582 _(b'show changesets within the given named branch'),
4576 _(b'show changesets within the given named branch'),
4583 _(b'BRANCH'),
4577 _(b'BRANCH'),
4584 ),
4578 ),
4585 (
4579 (
4586 b'P',
4580 b'P',
4587 b'prune',
4581 b'prune',
4588 [],
4582 [],
4589 _(b'do not display revision or any of its ancestors'),
4583 _(b'do not display revision or any of its ancestors'),
4590 _(b'REV'),
4584 _(b'REV'),
4591 ),
4585 ),
4592 ]
4586 ]
4593 + logopts
4587 + logopts
4594 + walkopts,
4588 + walkopts,
4595 _(b'[OPTION]... [FILE]'),
4589 _(b'[OPTION]... [FILE]'),
4596 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4590 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4597 helpbasic=True,
4591 helpbasic=True,
4598 inferrepo=True,
4592 inferrepo=True,
4599 intents={INTENT_READONLY},
4593 intents={INTENT_READONLY},
4600 )
4594 )
4601 def log(ui, repo, *pats, **opts):
4595 def log(ui, repo, *pats, **opts):
4602 """show revision history of entire repository or files
4596 """show revision history of entire repository or files
4603
4597
4604 Print the revision history of the specified files or the entire
4598 Print the revision history of the specified files or the entire
4605 project.
4599 project.
4606
4600
4607 If no revision range is specified, the default is ``tip:0`` unless
4601 If no revision range is specified, the default is ``tip:0`` unless
4608 --follow is set, in which case the working directory parent is
4602 --follow is set, in which case the working directory parent is
4609 used as the starting revision.
4603 used as the starting revision.
4610
4604
4611 File history is shown without following rename or copy history of
4605 File history is shown without following rename or copy history of
4612 files. Use -f/--follow with a filename to follow history across
4606 files. Use -f/--follow with a filename to follow history across
4613 renames and copies. --follow without a filename will only show
4607 renames and copies. --follow without a filename will only show
4614 ancestors of the starting revision.
4608 ancestors of the starting revision.
4615
4609
4616 By default this command prints revision number and changeset id,
4610 By default this command prints revision number and changeset id,
4617 tags, non-trivial parents, user, date and time, and a summary for
4611 tags, non-trivial parents, user, date and time, and a summary for
4618 each commit. When the -v/--verbose switch is used, the list of
4612 each commit. When the -v/--verbose switch is used, the list of
4619 changed files and full commit message are shown.
4613 changed files and full commit message are shown.
4620
4614
4621 With --graph the revisions are shown as an ASCII art DAG with the most
4615 With --graph the revisions are shown as an ASCII art DAG with the most
4622 recent changeset at the top.
4616 recent changeset at the top.
4623 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4617 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4624 involved in an unresolved merge conflict, '_' closes a branch,
4618 involved in an unresolved merge conflict, '_' closes a branch,
4625 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4619 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4626 changeset from the lines below is a parent of the 'o' merge on the same
4620 changeset from the lines below is a parent of the 'o' merge on the same
4627 line.
4621 line.
4628 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4622 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4629 of a '|' indicates one or more revisions in a path are omitted.
4623 of a '|' indicates one or more revisions in a path are omitted.
4630
4624
4631 .. container:: verbose
4625 .. container:: verbose
4632
4626
4633 Use -L/--line-range FILE,M:N options to follow the history of lines
4627 Use -L/--line-range FILE,M:N options to follow the history of lines
4634 from M to N in FILE. With -p/--patch only diff hunks affecting
4628 from M to N in FILE. With -p/--patch only diff hunks affecting
4635 specified line range will be shown. This option requires --follow;
4629 specified line range will be shown. This option requires --follow;
4636 it can be specified multiple times. Currently, this option is not
4630 it can be specified multiple times. Currently, this option is not
4637 compatible with --graph. This option is experimental.
4631 compatible with --graph. This option is experimental.
4638
4632
4639 .. note::
4633 .. note::
4640
4634
4641 :hg:`log --patch` may generate unexpected diff output for merge
4635 :hg:`log --patch` may generate unexpected diff output for merge
4642 changesets, as it will only compare the merge changeset against
4636 changesets, as it will only compare the merge changeset against
4643 its first parent. Also, only files different from BOTH parents
4637 its first parent. Also, only files different from BOTH parents
4644 will appear in files:.
4638 will appear in files:.
4645
4639
4646 .. note::
4640 .. note::
4647
4641
4648 For performance reasons, :hg:`log FILE` may omit duplicate changes
4642 For performance reasons, :hg:`log FILE` may omit duplicate changes
4649 made on branches and will not show removals or mode changes. To
4643 made on branches and will not show removals or mode changes. To
4650 see all such changes, use the --removed switch.
4644 see all such changes, use the --removed switch.
4651
4645
4652 .. container:: verbose
4646 .. container:: verbose
4653
4647
4654 .. note::
4648 .. note::
4655
4649
4656 The history resulting from -L/--line-range options depends on diff
4650 The history resulting from -L/--line-range options depends on diff
4657 options; for instance if white-spaces are ignored, respective changes
4651 options; for instance if white-spaces are ignored, respective changes
4658 with only white-spaces in specified line range will not be listed.
4652 with only white-spaces in specified line range will not be listed.
4659
4653
4660 .. container:: verbose
4654 .. container:: verbose
4661
4655
4662 Some examples:
4656 Some examples:
4663
4657
4664 - changesets with full descriptions and file lists::
4658 - changesets with full descriptions and file lists::
4665
4659
4666 hg log -v
4660 hg log -v
4667
4661
4668 - changesets ancestral to the working directory::
4662 - changesets ancestral to the working directory::
4669
4663
4670 hg log -f
4664 hg log -f
4671
4665
4672 - last 10 commits on the current branch::
4666 - last 10 commits on the current branch::
4673
4667
4674 hg log -l 10 -b .
4668 hg log -l 10 -b .
4675
4669
4676 - changesets showing all modifications of a file, including removals::
4670 - changesets showing all modifications of a file, including removals::
4677
4671
4678 hg log --removed file.c
4672 hg log --removed file.c
4679
4673
4680 - all changesets that touch a directory, with diffs, excluding merges::
4674 - all changesets that touch a directory, with diffs, excluding merges::
4681
4675
4682 hg log -Mp lib/
4676 hg log -Mp lib/
4683
4677
4684 - all revision numbers that match a keyword::
4678 - all revision numbers that match a keyword::
4685
4679
4686 hg log -k bug --template "{rev}\\n"
4680 hg log -k bug --template "{rev}\\n"
4687
4681
4688 - the full hash identifier of the working directory parent::
4682 - the full hash identifier of the working directory parent::
4689
4683
4690 hg log -r . --template "{node}\\n"
4684 hg log -r . --template "{node}\\n"
4691
4685
4692 - list available log templates::
4686 - list available log templates::
4693
4687
4694 hg log -T list
4688 hg log -T list
4695
4689
4696 - check if a given changeset is included in a tagged release::
4690 - check if a given changeset is included in a tagged release::
4697
4691
4698 hg log -r "a21ccf and ancestor(1.9)"
4692 hg log -r "a21ccf and ancestor(1.9)"
4699
4693
4700 - find all changesets by some user in a date range::
4694 - find all changesets by some user in a date range::
4701
4695
4702 hg log -k alice -d "may 2008 to jul 2008"
4696 hg log -k alice -d "may 2008 to jul 2008"
4703
4697
4704 - summary of all changesets after the last tag::
4698 - summary of all changesets after the last tag::
4705
4699
4706 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4700 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4707
4701
4708 - changesets touching lines 13 to 23 for file.c::
4702 - changesets touching lines 13 to 23 for file.c::
4709
4703
4710 hg log -L file.c,13:23
4704 hg log -L file.c,13:23
4711
4705
4712 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4706 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4713 main.c with patch::
4707 main.c with patch::
4714
4708
4715 hg log -L file.c,13:23 -L main.c,2:6 -p
4709 hg log -L file.c,13:23 -L main.c,2:6 -p
4716
4710
4717 See :hg:`help dates` for a list of formats valid for -d/--date.
4711 See :hg:`help dates` for a list of formats valid for -d/--date.
4718
4712
4719 See :hg:`help revisions` for more about specifying and ordering
4713 See :hg:`help revisions` for more about specifying and ordering
4720 revisions.
4714 revisions.
4721
4715
4722 See :hg:`help templates` for more about pre-packaged styles and
4716 See :hg:`help templates` for more about pre-packaged styles and
4723 specifying custom templates. The default template used by the log
4717 specifying custom templates. The default template used by the log
4724 command can be customized via the ``ui.logtemplate`` configuration
4718 command can be customized via the ``ui.logtemplate`` configuration
4725 setting.
4719 setting.
4726
4720
4727 Returns 0 on success.
4721 Returns 0 on success.
4728
4722
4729 """
4723 """
4730 opts = pycompat.byteskwargs(opts)
4724 opts = pycompat.byteskwargs(opts)
4731 linerange = opts.get(b'line_range')
4725 linerange = opts.get(b'line_range')
4732
4726
4733 if linerange and not opts.get(b'follow'):
4727 if linerange and not opts.get(b'follow'):
4734 raise error.Abort(_(b'--line-range requires --follow'))
4728 raise error.Abort(_(b'--line-range requires --follow'))
4735
4729
4736 if linerange and pats:
4730 if linerange and pats:
4737 # TODO: take pats as patterns with no line-range filter
4731 # TODO: take pats as patterns with no line-range filter
4738 raise error.Abort(
4732 raise error.Abort(
4739 _(b'FILE arguments are not compatible with --line-range option')
4733 _(b'FILE arguments are not compatible with --line-range option')
4740 )
4734 )
4741
4735
4742 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4736 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4743 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4737 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4744 if linerange:
4738 if linerange:
4745 # TODO: should follow file history from logcmdutil._initialrevs(),
4739 # TODO: should follow file history from logcmdutil._initialrevs(),
4746 # then filter the result by logcmdutil._makerevset() and --limit
4740 # then filter the result by logcmdutil._makerevset() and --limit
4747 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4741 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4748
4742
4749 getcopies = None
4743 getcopies = None
4750 if opts.get(b'copies'):
4744 if opts.get(b'copies'):
4751 endrev = None
4745 endrev = None
4752 if revs:
4746 if revs:
4753 endrev = revs.max() + 1
4747 endrev = revs.max() + 1
4754 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4748 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4755
4749
4756 ui.pager(b'log')
4750 ui.pager(b'log')
4757 displayer = logcmdutil.changesetdisplayer(
4751 displayer = logcmdutil.changesetdisplayer(
4758 ui, repo, opts, differ, buffered=True
4752 ui, repo, opts, differ, buffered=True
4759 )
4753 )
4760 if opts.get(b'graph'):
4754 if opts.get(b'graph'):
4761 displayfn = logcmdutil.displaygraphrevs
4755 displayfn = logcmdutil.displaygraphrevs
4762 else:
4756 else:
4763 displayfn = logcmdutil.displayrevs
4757 displayfn = logcmdutil.displayrevs
4764 displayfn(ui, repo, revs, displayer, getcopies)
4758 displayfn(ui, repo, revs, displayer, getcopies)
4765
4759
4766
4760
4767 @command(
4761 @command(
4768 b'manifest',
4762 b'manifest',
4769 [
4763 [
4770 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4764 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4771 (b'', b'all', False, _(b"list files from all revisions")),
4765 (b'', b'all', False, _(b"list files from all revisions")),
4772 ]
4766 ]
4773 + formatteropts,
4767 + formatteropts,
4774 _(b'[-r REV]'),
4768 _(b'[-r REV]'),
4775 helpcategory=command.CATEGORY_MAINTENANCE,
4769 helpcategory=command.CATEGORY_MAINTENANCE,
4776 intents={INTENT_READONLY},
4770 intents={INTENT_READONLY},
4777 )
4771 )
4778 def manifest(ui, repo, node=None, rev=None, **opts):
4772 def manifest(ui, repo, node=None, rev=None, **opts):
4779 """output the current or given revision of the project manifest
4773 """output the current or given revision of the project manifest
4780
4774
4781 Print a list of version controlled files for the given revision.
4775 Print a list of version controlled files for the given revision.
4782 If no revision is given, the first parent of the working directory
4776 If no revision is given, the first parent of the working directory
4783 is used, or the null revision if no revision is checked out.
4777 is used, or the null revision if no revision is checked out.
4784
4778
4785 With -v, print file permissions, symlink and executable bits.
4779 With -v, print file permissions, symlink and executable bits.
4786 With --debug, print file revision hashes.
4780 With --debug, print file revision hashes.
4787
4781
4788 If option --all is specified, the list of all files from all revisions
4782 If option --all is specified, the list of all files from all revisions
4789 is printed. This includes deleted and renamed files.
4783 is printed. This includes deleted and renamed files.
4790
4784
4791 Returns 0 on success.
4785 Returns 0 on success.
4792 """
4786 """
4793 opts = pycompat.byteskwargs(opts)
4787 opts = pycompat.byteskwargs(opts)
4794 fm = ui.formatter(b'manifest', opts)
4788 fm = ui.formatter(b'manifest', opts)
4795
4789
4796 if opts.get(b'all'):
4790 if opts.get(b'all'):
4797 if rev or node:
4791 if rev or node:
4798 raise error.Abort(_(b"can't specify a revision with --all"))
4792 raise error.Abort(_(b"can't specify a revision with --all"))
4799
4793
4800 res = set()
4794 res = set()
4801 for rev in repo:
4795 for rev in repo:
4802 ctx = repo[rev]
4796 ctx = repo[rev]
4803 res |= set(ctx.files())
4797 res |= set(ctx.files())
4804
4798
4805 ui.pager(b'manifest')
4799 ui.pager(b'manifest')
4806 for f in sorted(res):
4800 for f in sorted(res):
4807 fm.startitem()
4801 fm.startitem()
4808 fm.write(b"path", b'%s\n', f)
4802 fm.write(b"path", b'%s\n', f)
4809 fm.end()
4803 fm.end()
4810 return
4804 return
4811
4805
4812 if rev and node:
4806 if rev and node:
4813 raise error.Abort(_(b"please specify just one revision"))
4807 raise error.Abort(_(b"please specify just one revision"))
4814
4808
4815 if not node:
4809 if not node:
4816 node = rev
4810 node = rev
4817
4811
4818 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4812 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4819 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4813 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4820 if node:
4814 if node:
4821 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4815 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4822 ctx = scmutil.revsingle(repo, node)
4816 ctx = scmutil.revsingle(repo, node)
4823 mf = ctx.manifest()
4817 mf = ctx.manifest()
4824 ui.pager(b'manifest')
4818 ui.pager(b'manifest')
4825 for f in ctx:
4819 for f in ctx:
4826 fm.startitem()
4820 fm.startitem()
4827 fm.context(ctx=ctx)
4821 fm.context(ctx=ctx)
4828 fl = ctx[f].flags()
4822 fl = ctx[f].flags()
4829 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4823 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4830 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4824 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4831 fm.write(b'path', b'%s\n', f)
4825 fm.write(b'path', b'%s\n', f)
4832 fm.end()
4826 fm.end()
4833
4827
4834
4828
4835 @command(
4829 @command(
4836 b'merge',
4830 b'merge',
4837 [
4831 [
4838 (
4832 (
4839 b'f',
4833 b'f',
4840 b'force',
4834 b'force',
4841 None,
4835 None,
4842 _(b'force a merge including outstanding changes (DEPRECATED)'),
4836 _(b'force a merge including outstanding changes (DEPRECATED)'),
4843 ),
4837 ),
4844 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4838 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4845 (
4839 (
4846 b'P',
4840 b'P',
4847 b'preview',
4841 b'preview',
4848 None,
4842 None,
4849 _(b'review revisions to merge (no merge is performed)'),
4843 _(b'review revisions to merge (no merge is performed)'),
4850 ),
4844 ),
4851 (b'', b'abort', None, _(b'abort the ongoing merge')),
4845 (b'', b'abort', None, _(b'abort the ongoing merge')),
4852 ]
4846 ]
4853 + mergetoolopts,
4847 + mergetoolopts,
4854 _(b'[-P] [[-r] REV]'),
4848 _(b'[-P] [[-r] REV]'),
4855 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4849 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4856 helpbasic=True,
4850 helpbasic=True,
4857 )
4851 )
4858 def merge(ui, repo, node=None, **opts):
4852 def merge(ui, repo, node=None, **opts):
4859 """merge another revision into working directory
4853 """merge another revision into working directory
4860
4854
4861 The current working directory is updated with all changes made in
4855 The current working directory is updated with all changes made in
4862 the requested revision since the last common predecessor revision.
4856 the requested revision since the last common predecessor revision.
4863
4857
4864 Files that changed between either parent are marked as changed for
4858 Files that changed between either parent are marked as changed for
4865 the next commit and a commit must be performed before any further
4859 the next commit and a commit must be performed before any further
4866 updates to the repository are allowed. The next commit will have
4860 updates to the repository are allowed. The next commit will have
4867 two parents.
4861 two parents.
4868
4862
4869 ``--tool`` can be used to specify the merge tool used for file
4863 ``--tool`` can be used to specify the merge tool used for file
4870 merges. It overrides the HGMERGE environment variable and your
4864 merges. It overrides the HGMERGE environment variable and your
4871 configuration files. See :hg:`help merge-tools` for options.
4865 configuration files. See :hg:`help merge-tools` for options.
4872
4866
4873 If no revision is specified, the working directory's parent is a
4867 If no revision is specified, the working directory's parent is a
4874 head revision, and the current branch contains exactly one other
4868 head revision, and the current branch contains exactly one other
4875 head, the other head is merged with by default. Otherwise, an
4869 head, the other head is merged with by default. Otherwise, an
4876 explicit revision with which to merge must be provided.
4870 explicit revision with which to merge must be provided.
4877
4871
4878 See :hg:`help resolve` for information on handling file conflicts.
4872 See :hg:`help resolve` for information on handling file conflicts.
4879
4873
4880 To undo an uncommitted merge, use :hg:`merge --abort` which
4874 To undo an uncommitted merge, use :hg:`merge --abort` which
4881 will check out a clean copy of the original merge parent, losing
4875 will check out a clean copy of the original merge parent, losing
4882 all changes.
4876 all changes.
4883
4877
4884 Returns 0 on success, 1 if there are unresolved files.
4878 Returns 0 on success, 1 if there are unresolved files.
4885 """
4879 """
4886
4880
4887 opts = pycompat.byteskwargs(opts)
4881 opts = pycompat.byteskwargs(opts)
4888 abort = opts.get(b'abort')
4882 abort = opts.get(b'abort')
4889 if abort and repo.dirstate.p2() == nullid:
4883 if abort and repo.dirstate.p2() == nullid:
4890 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4884 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4891 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4885 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4892 if abort:
4886 if abort:
4893 state = cmdutil.getunfinishedstate(repo)
4887 state = cmdutil.getunfinishedstate(repo)
4894 if state and state._opname != b'merge':
4888 if state and state._opname != b'merge':
4895 raise error.Abort(
4889 raise error.Abort(
4896 _(b'cannot abort merge with %s in progress') % (state._opname),
4890 _(b'cannot abort merge with %s in progress') % (state._opname),
4897 hint=state.hint(),
4891 hint=state.hint(),
4898 )
4892 )
4899 if node:
4893 if node:
4900 raise error.Abort(_(b"cannot specify a node with --abort"))
4894 raise error.Abort(_(b"cannot specify a node with --abort"))
4901 return hg.abortmerge(repo.ui, repo)
4895 return hg.abortmerge(repo.ui, repo)
4902
4896
4903 if opts.get(b'rev') and node:
4897 if opts.get(b'rev') and node:
4904 raise error.Abort(_(b"please specify just one revision"))
4898 raise error.Abort(_(b"please specify just one revision"))
4905 if not node:
4899 if not node:
4906 node = opts.get(b'rev')
4900 node = opts.get(b'rev')
4907
4901
4908 if node:
4902 if node:
4909 ctx = scmutil.revsingle(repo, node)
4903 ctx = scmutil.revsingle(repo, node)
4910 else:
4904 else:
4911 if ui.configbool(b'commands', b'merge.require-rev'):
4905 if ui.configbool(b'commands', b'merge.require-rev'):
4912 raise error.Abort(
4906 raise error.Abort(
4913 _(
4907 _(
4914 b'configuration requires specifying revision to merge '
4908 b'configuration requires specifying revision to merge '
4915 b'with'
4909 b'with'
4916 )
4910 )
4917 )
4911 )
4918 ctx = repo[destutil.destmerge(repo)]
4912 ctx = repo[destutil.destmerge(repo)]
4919
4913
4920 if ctx.node() is None:
4914 if ctx.node() is None:
4921 raise error.Abort(_(b'merging with the working copy has no effect'))
4915 raise error.Abort(_(b'merging with the working copy has no effect'))
4922
4916
4923 if opts.get(b'preview'):
4917 if opts.get(b'preview'):
4924 # find nodes that are ancestors of p2 but not of p1
4918 # find nodes that are ancestors of p2 but not of p1
4925 p1 = repo[b'.'].node()
4919 p1 = repo[b'.'].node()
4926 p2 = ctx.node()
4920 p2 = ctx.node()
4927 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4921 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4928
4922
4929 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4923 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4930 for node in nodes:
4924 for node in nodes:
4931 displayer.show(repo[node])
4925 displayer.show(repo[node])
4932 displayer.close()
4926 displayer.close()
4933 return 0
4927 return 0
4934
4928
4935 # ui.forcemerge is an internal variable, do not document
4929 # ui.forcemerge is an internal variable, do not document
4936 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4930 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4937 with ui.configoverride(overrides, b'merge'):
4931 with ui.configoverride(overrides, b'merge'):
4938 force = opts.get(b'force')
4932 force = opts.get(b'force')
4939 labels = [b'working copy', b'merge rev']
4933 labels = [b'working copy', b'merge rev']
4940 return hg.merge(ctx, force=force, labels=labels)
4934 return hg.merge(ctx, force=force, labels=labels)
4941
4935
4942
4936
4943 statemod.addunfinished(
4937 statemod.addunfinished(
4944 b'merge',
4938 b'merge',
4945 fname=None,
4939 fname=None,
4946 clearable=True,
4940 clearable=True,
4947 allowcommit=True,
4941 allowcommit=True,
4948 cmdmsg=_(b'outstanding uncommitted merge'),
4942 cmdmsg=_(b'outstanding uncommitted merge'),
4949 abortfunc=hg.abortmerge,
4943 abortfunc=hg.abortmerge,
4950 statushint=_(
4944 statushint=_(
4951 b'To continue: hg commit\nTo abort: hg merge --abort'
4945 b'To continue: hg commit\nTo abort: hg merge --abort'
4952 ),
4946 ),
4953 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4947 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4954 )
4948 )
4955
4949
4956
4950
4957 @command(
4951 @command(
4958 b'outgoing|out',
4952 b'outgoing|out',
4959 [
4953 [
4960 (
4954 (
4961 b'f',
4955 b'f',
4962 b'force',
4956 b'force',
4963 None,
4957 None,
4964 _(b'run even when the destination is unrelated'),
4958 _(b'run even when the destination is unrelated'),
4965 ),
4959 ),
4966 (
4960 (
4967 b'r',
4961 b'r',
4968 b'rev',
4962 b'rev',
4969 [],
4963 [],
4970 _(b'a changeset intended to be included in the destination'),
4964 _(b'a changeset intended to be included in the destination'),
4971 _(b'REV'),
4965 _(b'REV'),
4972 ),
4966 ),
4973 (b'n', b'newest-first', None, _(b'show newest record first')),
4967 (b'n', b'newest-first', None, _(b'show newest record first')),
4974 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4968 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4975 (
4969 (
4976 b'b',
4970 b'b',
4977 b'branch',
4971 b'branch',
4978 [],
4972 [],
4979 _(b'a specific branch you would like to push'),
4973 _(b'a specific branch you would like to push'),
4980 _(b'BRANCH'),
4974 _(b'BRANCH'),
4981 ),
4975 ),
4982 ]
4976 ]
4983 + logopts
4977 + logopts
4984 + remoteopts
4978 + remoteopts
4985 + subrepoopts,
4979 + subrepoopts,
4986 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4980 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4987 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4981 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4988 )
4982 )
4989 def outgoing(ui, repo, dest=None, **opts):
4983 def outgoing(ui, repo, dest=None, **opts):
4990 """show changesets not found in the destination
4984 """show changesets not found in the destination
4991
4985
4992 Show changesets not found in the specified destination repository
4986 Show changesets not found in the specified destination repository
4993 or the default push location. These are the changesets that would
4987 or the default push location. These are the changesets that would
4994 be pushed if a push was requested.
4988 be pushed if a push was requested.
4995
4989
4996 See pull for details of valid destination formats.
4990 See pull for details of valid destination formats.
4997
4991
4998 .. container:: verbose
4992 .. container:: verbose
4999
4993
5000 With -B/--bookmarks, the result of bookmark comparison between
4994 With -B/--bookmarks, the result of bookmark comparison between
5001 local and remote repositories is displayed. With -v/--verbose,
4995 local and remote repositories is displayed. With -v/--verbose,
5002 status is also displayed for each bookmark like below::
4996 status is also displayed for each bookmark like below::
5003
4997
5004 BM1 01234567890a added
4998 BM1 01234567890a added
5005 BM2 deleted
4999 BM2 deleted
5006 BM3 234567890abc advanced
5000 BM3 234567890abc advanced
5007 BM4 34567890abcd diverged
5001 BM4 34567890abcd diverged
5008 BM5 4567890abcde changed
5002 BM5 4567890abcde changed
5009
5003
5010 The action taken when pushing depends on the
5004 The action taken when pushing depends on the
5011 status of each bookmark:
5005 status of each bookmark:
5012
5006
5013 :``added``: push with ``-B`` will create it
5007 :``added``: push with ``-B`` will create it
5014 :``deleted``: push with ``-B`` will delete it
5008 :``deleted``: push with ``-B`` will delete it
5015 :``advanced``: push will update it
5009 :``advanced``: push will update it
5016 :``diverged``: push with ``-B`` will update it
5010 :``diverged``: push with ``-B`` will update it
5017 :``changed``: push with ``-B`` will update it
5011 :``changed``: push with ``-B`` will update it
5018
5012
5019 From the point of view of pushing behavior, bookmarks
5013 From the point of view of pushing behavior, bookmarks
5020 existing only in the remote repository are treated as
5014 existing only in the remote repository are treated as
5021 ``deleted``, even if it is in fact added remotely.
5015 ``deleted``, even if it is in fact added remotely.
5022
5016
5023 Returns 0 if there are outgoing changes, 1 otherwise.
5017 Returns 0 if there are outgoing changes, 1 otherwise.
5024 """
5018 """
5025 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5019 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5026 # style URLs, so don't overwrite dest.
5020 # style URLs, so don't overwrite dest.
5027 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5021 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5028 if not path:
5022 if not path:
5029 raise error.Abort(
5023 raise error.Abort(
5030 _(b'default repository not configured!'),
5024 _(b'default repository not configured!'),
5031 hint=_(b"see 'hg help config.paths'"),
5025 hint=_(b"see 'hg help config.paths'"),
5032 )
5026 )
5033
5027
5034 opts = pycompat.byteskwargs(opts)
5028 opts = pycompat.byteskwargs(opts)
5035 if opts.get(b'graph'):
5029 if opts.get(b'graph'):
5036 logcmdutil.checkunsupportedgraphflags([], opts)
5030 logcmdutil.checkunsupportedgraphflags([], opts)
5037 o, other = hg._outgoing(ui, repo, dest, opts)
5031 o, other = hg._outgoing(ui, repo, dest, opts)
5038 if not o:
5032 if not o:
5039 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5033 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5040 return
5034 return
5041
5035
5042 revdag = logcmdutil.graphrevs(repo, o, opts)
5036 revdag = logcmdutil.graphrevs(repo, o, opts)
5043 ui.pager(b'outgoing')
5037 ui.pager(b'outgoing')
5044 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5038 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5045 logcmdutil.displaygraph(
5039 logcmdutil.displaygraph(
5046 ui, repo, revdag, displayer, graphmod.asciiedges
5040 ui, repo, revdag, displayer, graphmod.asciiedges
5047 )
5041 )
5048 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5042 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5049 return 0
5043 return 0
5050
5044
5051 if opts.get(b'bookmarks'):
5045 if opts.get(b'bookmarks'):
5052 dest = path.pushloc or path.loc
5046 dest = path.pushloc or path.loc
5053 other = hg.peer(repo, opts, dest)
5047 other = hg.peer(repo, opts, dest)
5054 if b'bookmarks' not in other.listkeys(b'namespaces'):
5048 if b'bookmarks' not in other.listkeys(b'namespaces'):
5055 ui.warn(_(b"remote doesn't support bookmarks\n"))
5049 ui.warn(_(b"remote doesn't support bookmarks\n"))
5056 return 0
5050 return 0
5057 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5051 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5058 ui.pager(b'outgoing')
5052 ui.pager(b'outgoing')
5059 return bookmarks.outgoing(ui, repo, other)
5053 return bookmarks.outgoing(ui, repo, other)
5060
5054
5061 repo._subtoppath = path.pushloc or path.loc
5055 repo._subtoppath = path.pushloc or path.loc
5062 try:
5056 try:
5063 return hg.outgoing(ui, repo, dest, opts)
5057 return hg.outgoing(ui, repo, dest, opts)
5064 finally:
5058 finally:
5065 del repo._subtoppath
5059 del repo._subtoppath
5066
5060
5067
5061
5068 @command(
5062 @command(
5069 b'parents',
5063 b'parents',
5070 [
5064 [
5071 (
5065 (
5072 b'r',
5066 b'r',
5073 b'rev',
5067 b'rev',
5074 b'',
5068 b'',
5075 _(b'show parents of the specified revision'),
5069 _(b'show parents of the specified revision'),
5076 _(b'REV'),
5070 _(b'REV'),
5077 ),
5071 ),
5078 ]
5072 ]
5079 + templateopts,
5073 + templateopts,
5080 _(b'[-r REV] [FILE]'),
5074 _(b'[-r REV] [FILE]'),
5081 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5075 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5082 inferrepo=True,
5076 inferrepo=True,
5083 )
5077 )
5084 def parents(ui, repo, file_=None, **opts):
5078 def parents(ui, repo, file_=None, **opts):
5085 """show the parents of the working directory or revision (DEPRECATED)
5079 """show the parents of the working directory or revision (DEPRECATED)
5086
5080
5087 Print the working directory's parent revisions. If a revision is
5081 Print the working directory's parent revisions. If a revision is
5088 given via -r/--rev, the parent of that revision will be printed.
5082 given via -r/--rev, the parent of that revision will be printed.
5089 If a file argument is given, the revision in which the file was
5083 If a file argument is given, the revision in which the file was
5090 last changed (before the working directory revision or the
5084 last changed (before the working directory revision or the
5091 argument to --rev if given) is printed.
5085 argument to --rev if given) is printed.
5092
5086
5093 This command is equivalent to::
5087 This command is equivalent to::
5094
5088
5095 hg log -r "p1()+p2()" or
5089 hg log -r "p1()+p2()" or
5096 hg log -r "p1(REV)+p2(REV)" or
5090 hg log -r "p1(REV)+p2(REV)" or
5097 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5091 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5098 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5092 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5099
5093
5100 See :hg:`summary` and :hg:`help revsets` for related information.
5094 See :hg:`summary` and :hg:`help revsets` for related information.
5101
5095
5102 Returns 0 on success.
5096 Returns 0 on success.
5103 """
5097 """
5104
5098
5105 opts = pycompat.byteskwargs(opts)
5099 opts = pycompat.byteskwargs(opts)
5106 rev = opts.get(b'rev')
5100 rev = opts.get(b'rev')
5107 if rev:
5101 if rev:
5108 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5102 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5109 ctx = scmutil.revsingle(repo, rev, None)
5103 ctx = scmutil.revsingle(repo, rev, None)
5110
5104
5111 if file_:
5105 if file_:
5112 m = scmutil.match(ctx, (file_,), opts)
5106 m = scmutil.match(ctx, (file_,), opts)
5113 if m.anypats() or len(m.files()) != 1:
5107 if m.anypats() or len(m.files()) != 1:
5114 raise error.Abort(_(b'can only specify an explicit filename'))
5108 raise error.Abort(_(b'can only specify an explicit filename'))
5115 file_ = m.files()[0]
5109 file_ = m.files()[0]
5116 filenodes = []
5110 filenodes = []
5117 for cp in ctx.parents():
5111 for cp in ctx.parents():
5118 if not cp:
5112 if not cp:
5119 continue
5113 continue
5120 try:
5114 try:
5121 filenodes.append(cp.filenode(file_))
5115 filenodes.append(cp.filenode(file_))
5122 except error.LookupError:
5116 except error.LookupError:
5123 pass
5117 pass
5124 if not filenodes:
5118 if not filenodes:
5125 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5119 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5126 p = []
5120 p = []
5127 for fn in filenodes:
5121 for fn in filenodes:
5128 fctx = repo.filectx(file_, fileid=fn)
5122 fctx = repo.filectx(file_, fileid=fn)
5129 p.append(fctx.node())
5123 p.append(fctx.node())
5130 else:
5124 else:
5131 p = [cp.node() for cp in ctx.parents()]
5125 p = [cp.node() for cp in ctx.parents()]
5132
5126
5133 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5127 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5134 for n in p:
5128 for n in p:
5135 if n != nullid:
5129 if n != nullid:
5136 displayer.show(repo[n])
5130 displayer.show(repo[n])
5137 displayer.close()
5131 displayer.close()
5138
5132
5139
5133
5140 @command(
5134 @command(
5141 b'paths',
5135 b'paths',
5142 formatteropts,
5136 formatteropts,
5143 _(b'[NAME]'),
5137 _(b'[NAME]'),
5144 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5138 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5145 optionalrepo=True,
5139 optionalrepo=True,
5146 intents={INTENT_READONLY},
5140 intents={INTENT_READONLY},
5147 )
5141 )
5148 def paths(ui, repo, search=None, **opts):
5142 def paths(ui, repo, search=None, **opts):
5149 """show aliases for remote repositories
5143 """show aliases for remote repositories
5150
5144
5151 Show definition of symbolic path name NAME. If no name is given,
5145 Show definition of symbolic path name NAME. If no name is given,
5152 show definition of all available names.
5146 show definition of all available names.
5153
5147
5154 Option -q/--quiet suppresses all output when searching for NAME
5148 Option -q/--quiet suppresses all output when searching for NAME
5155 and shows only the path names when listing all definitions.
5149 and shows only the path names when listing all definitions.
5156
5150
5157 Path names are defined in the [paths] section of your
5151 Path names are defined in the [paths] section of your
5158 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5152 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5159 repository, ``.hg/hgrc`` is used, too.
5153 repository, ``.hg/hgrc`` is used, too.
5160
5154
5161 The path names ``default`` and ``default-push`` have a special
5155 The path names ``default`` and ``default-push`` have a special
5162 meaning. When performing a push or pull operation, they are used
5156 meaning. When performing a push or pull operation, they are used
5163 as fallbacks if no location is specified on the command-line.
5157 as fallbacks if no location is specified on the command-line.
5164 When ``default-push`` is set, it will be used for push and
5158 When ``default-push`` is set, it will be used for push and
5165 ``default`` will be used for pull; otherwise ``default`` is used
5159 ``default`` will be used for pull; otherwise ``default`` is used
5166 as the fallback for both. When cloning a repository, the clone
5160 as the fallback for both. When cloning a repository, the clone
5167 source is written as ``default`` in ``.hg/hgrc``.
5161 source is written as ``default`` in ``.hg/hgrc``.
5168
5162
5169 .. note::
5163 .. note::
5170
5164
5171 ``default`` and ``default-push`` apply to all inbound (e.g.
5165 ``default`` and ``default-push`` apply to all inbound (e.g.
5172 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5166 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5173 and :hg:`bundle`) operations.
5167 and :hg:`bundle`) operations.
5174
5168
5175 See :hg:`help urls` for more information.
5169 See :hg:`help urls` for more information.
5176
5170
5177 .. container:: verbose
5171 .. container:: verbose
5178
5172
5179 Template:
5173 Template:
5180
5174
5181 The following keywords are supported. See also :hg:`help templates`.
5175 The following keywords are supported. See also :hg:`help templates`.
5182
5176
5183 :name: String. Symbolic name of the path alias.
5177 :name: String. Symbolic name of the path alias.
5184 :pushurl: String. URL for push operations.
5178 :pushurl: String. URL for push operations.
5185 :url: String. URL or directory path for the other operations.
5179 :url: String. URL or directory path for the other operations.
5186
5180
5187 Returns 0 on success.
5181 Returns 0 on success.
5188 """
5182 """
5189
5183
5190 opts = pycompat.byteskwargs(opts)
5184 opts = pycompat.byteskwargs(opts)
5191 ui.pager(b'paths')
5185 ui.pager(b'paths')
5192 if search:
5186 if search:
5193 pathitems = [
5187 pathitems = [
5194 (name, path)
5188 (name, path)
5195 for name, path in pycompat.iteritems(ui.paths)
5189 for name, path in pycompat.iteritems(ui.paths)
5196 if name == search
5190 if name == search
5197 ]
5191 ]
5198 else:
5192 else:
5199 pathitems = sorted(pycompat.iteritems(ui.paths))
5193 pathitems = sorted(pycompat.iteritems(ui.paths))
5200
5194
5201 fm = ui.formatter(b'paths', opts)
5195 fm = ui.formatter(b'paths', opts)
5202 if fm.isplain():
5196 if fm.isplain():
5203 hidepassword = util.hidepassword
5197 hidepassword = util.hidepassword
5204 else:
5198 else:
5205 hidepassword = bytes
5199 hidepassword = bytes
5206 if ui.quiet:
5200 if ui.quiet:
5207 namefmt = b'%s\n'
5201 namefmt = b'%s\n'
5208 else:
5202 else:
5209 namefmt = b'%s = '
5203 namefmt = b'%s = '
5210 showsubopts = not search and not ui.quiet
5204 showsubopts = not search and not ui.quiet
5211
5205
5212 for name, path in pathitems:
5206 for name, path in pathitems:
5213 fm.startitem()
5207 fm.startitem()
5214 fm.condwrite(not search, b'name', namefmt, name)
5208 fm.condwrite(not search, b'name', namefmt, name)
5215 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5209 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5216 for subopt, value in sorted(path.suboptions.items()):
5210 for subopt, value in sorted(path.suboptions.items()):
5217 assert subopt not in (b'name', b'url')
5211 assert subopt not in (b'name', b'url')
5218 if showsubopts:
5212 if showsubopts:
5219 fm.plain(b'%s:%s = ' % (name, subopt))
5213 fm.plain(b'%s:%s = ' % (name, subopt))
5220 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5214 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5221
5215
5222 fm.end()
5216 fm.end()
5223
5217
5224 if search and not pathitems:
5218 if search and not pathitems:
5225 if not ui.quiet:
5219 if not ui.quiet:
5226 ui.warn(_(b"not found!\n"))
5220 ui.warn(_(b"not found!\n"))
5227 return 1
5221 return 1
5228 else:
5222 else:
5229 return 0
5223 return 0
5230
5224
5231
5225
5232 @command(
5226 @command(
5233 b'phase',
5227 b'phase',
5234 [
5228 [
5235 (b'p', b'public', False, _(b'set changeset phase to public')),
5229 (b'p', b'public', False, _(b'set changeset phase to public')),
5236 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5230 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5237 (b's', b'secret', False, _(b'set changeset phase to secret')),
5231 (b's', b'secret', False, _(b'set changeset phase to secret')),
5238 (b'f', b'force', False, _(b'allow to move boundary backward')),
5232 (b'f', b'force', False, _(b'allow to move boundary backward')),
5239 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5233 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5240 ],
5234 ],
5241 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5235 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5242 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5236 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5243 )
5237 )
5244 def phase(ui, repo, *revs, **opts):
5238 def phase(ui, repo, *revs, **opts):
5245 """set or show the current phase name
5239 """set or show the current phase name
5246
5240
5247 With no argument, show the phase name of the current revision(s).
5241 With no argument, show the phase name of the current revision(s).
5248
5242
5249 With one of -p/--public, -d/--draft or -s/--secret, change the
5243 With one of -p/--public, -d/--draft or -s/--secret, change the
5250 phase value of the specified revisions.
5244 phase value of the specified revisions.
5251
5245
5252 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5246 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5253 lower phase to a higher phase. Phases are ordered as follows::
5247 lower phase to a higher phase. Phases are ordered as follows::
5254
5248
5255 public < draft < secret
5249 public < draft < secret
5256
5250
5257 Returns 0 on success, 1 if some phases could not be changed.
5251 Returns 0 on success, 1 if some phases could not be changed.
5258
5252
5259 (For more information about the phases concept, see :hg:`help phases`.)
5253 (For more information about the phases concept, see :hg:`help phases`.)
5260 """
5254 """
5261 opts = pycompat.byteskwargs(opts)
5255 opts = pycompat.byteskwargs(opts)
5262 # search for a unique phase argument
5256 # search for a unique phase argument
5263 targetphase = None
5257 targetphase = None
5264 for idx, name in enumerate(phases.cmdphasenames):
5258 for idx, name in enumerate(phases.cmdphasenames):
5265 if opts[name]:
5259 if opts[name]:
5266 if targetphase is not None:
5260 if targetphase is not None:
5267 raise error.Abort(_(b'only one phase can be specified'))
5261 raise error.Abort(_(b'only one phase can be specified'))
5268 targetphase = idx
5262 targetphase = idx
5269
5263
5270 # look for specified revision
5264 # look for specified revision
5271 revs = list(revs)
5265 revs = list(revs)
5272 revs.extend(opts[b'rev'])
5266 revs.extend(opts[b'rev'])
5273 if not revs:
5267 if not revs:
5274 # display both parents as the second parent phase can influence
5268 # display both parents as the second parent phase can influence
5275 # the phase of a merge commit
5269 # the phase of a merge commit
5276 revs = [c.rev() for c in repo[None].parents()]
5270 revs = [c.rev() for c in repo[None].parents()]
5277
5271
5278 revs = scmutil.revrange(repo, revs)
5272 revs = scmutil.revrange(repo, revs)
5279
5273
5280 ret = 0
5274 ret = 0
5281 if targetphase is None:
5275 if targetphase is None:
5282 # display
5276 # display
5283 for r in revs:
5277 for r in revs:
5284 ctx = repo[r]
5278 ctx = repo[r]
5285 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5279 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5286 else:
5280 else:
5287 with repo.lock(), repo.transaction(b"phase") as tr:
5281 with repo.lock(), repo.transaction(b"phase") as tr:
5288 # set phase
5282 # set phase
5289 if not revs:
5283 if not revs:
5290 raise error.Abort(_(b'empty revision set'))
5284 raise error.Abort(_(b'empty revision set'))
5291 nodes = [repo[r].node() for r in revs]
5285 nodes = [repo[r].node() for r in revs]
5292 # moving revision from public to draft may hide them
5286 # moving revision from public to draft may hide them
5293 # We have to check result on an unfiltered repository
5287 # We have to check result on an unfiltered repository
5294 unfi = repo.unfiltered()
5288 unfi = repo.unfiltered()
5295 getphase = unfi._phasecache.phase
5289 getphase = unfi._phasecache.phase
5296 olddata = [getphase(unfi, r) for r in unfi]
5290 olddata = [getphase(unfi, r) for r in unfi]
5297 phases.advanceboundary(repo, tr, targetphase, nodes)
5291 phases.advanceboundary(repo, tr, targetphase, nodes)
5298 if opts[b'force']:
5292 if opts[b'force']:
5299 phases.retractboundary(repo, tr, targetphase, nodes)
5293 phases.retractboundary(repo, tr, targetphase, nodes)
5300 getphase = unfi._phasecache.phase
5294 getphase = unfi._phasecache.phase
5301 newdata = [getphase(unfi, r) for r in unfi]
5295 newdata = [getphase(unfi, r) for r in unfi]
5302 changes = sum(newdata[r] != olddata[r] for r in unfi)
5296 changes = sum(newdata[r] != olddata[r] for r in unfi)
5303 cl = unfi.changelog
5297 cl = unfi.changelog
5304 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5298 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5305 if rejected:
5299 if rejected:
5306 ui.warn(
5300 ui.warn(
5307 _(
5301 _(
5308 b'cannot move %i changesets to a higher '
5302 b'cannot move %i changesets to a higher '
5309 b'phase, use --force\n'
5303 b'phase, use --force\n'
5310 )
5304 )
5311 % len(rejected)
5305 % len(rejected)
5312 )
5306 )
5313 ret = 1
5307 ret = 1
5314 if changes:
5308 if changes:
5315 msg = _(b'phase changed for %i changesets\n') % changes
5309 msg = _(b'phase changed for %i changesets\n') % changes
5316 if ret:
5310 if ret:
5317 ui.status(msg)
5311 ui.status(msg)
5318 else:
5312 else:
5319 ui.note(msg)
5313 ui.note(msg)
5320 else:
5314 else:
5321 ui.warn(_(b'no phases changed\n'))
5315 ui.warn(_(b'no phases changed\n'))
5322 return ret
5316 return ret
5323
5317
5324
5318
5325 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5319 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5326 """Run after a changegroup has been added via pull/unbundle
5320 """Run after a changegroup has been added via pull/unbundle
5327
5321
5328 This takes arguments below:
5322 This takes arguments below:
5329
5323
5330 :modheads: change of heads by pull/unbundle
5324 :modheads: change of heads by pull/unbundle
5331 :optupdate: updating working directory is needed or not
5325 :optupdate: updating working directory is needed or not
5332 :checkout: update destination revision (or None to default destination)
5326 :checkout: update destination revision (or None to default destination)
5333 :brev: a name, which might be a bookmark to be activated after updating
5327 :brev: a name, which might be a bookmark to be activated after updating
5334 """
5328 """
5335 if modheads == 0:
5329 if modheads == 0:
5336 return
5330 return
5337 if optupdate:
5331 if optupdate:
5338 try:
5332 try:
5339 return hg.updatetotally(ui, repo, checkout, brev)
5333 return hg.updatetotally(ui, repo, checkout, brev)
5340 except error.UpdateAbort as inst:
5334 except error.UpdateAbort as inst:
5341 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5335 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5342 hint = inst.hint
5336 hint = inst.hint
5343 raise error.UpdateAbort(msg, hint=hint)
5337 raise error.UpdateAbort(msg, hint=hint)
5344 if modheads is not None and modheads > 1:
5338 if modheads is not None and modheads > 1:
5345 currentbranchheads = len(repo.branchheads())
5339 currentbranchheads = len(repo.branchheads())
5346 if currentbranchheads == modheads:
5340 if currentbranchheads == modheads:
5347 ui.status(
5341 ui.status(
5348 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5342 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5349 )
5343 )
5350 elif currentbranchheads > 1:
5344 elif currentbranchheads > 1:
5351 ui.status(
5345 ui.status(
5352 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5346 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5353 )
5347 )
5354 else:
5348 else:
5355 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5349 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5356 elif not ui.configbool(b'commands', b'update.requiredest'):
5350 elif not ui.configbool(b'commands', b'update.requiredest'):
5357 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5351 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5358
5352
5359
5353
5360 @command(
5354 @command(
5361 b'pull',
5355 b'pull',
5362 [
5356 [
5363 (
5357 (
5364 b'u',
5358 b'u',
5365 b'update',
5359 b'update',
5366 None,
5360 None,
5367 _(b'update to new branch head if new descendants were pulled'),
5361 _(b'update to new branch head if new descendants were pulled'),
5368 ),
5362 ),
5369 (
5363 (
5370 b'f',
5364 b'f',
5371 b'force',
5365 b'force',
5372 None,
5366 None,
5373 _(b'run even when remote repository is unrelated'),
5367 _(b'run even when remote repository is unrelated'),
5374 ),
5368 ),
5375 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5369 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5376 (
5370 (
5377 b'r',
5371 b'r',
5378 b'rev',
5372 b'rev',
5379 [],
5373 [],
5380 _(b'a remote changeset intended to be added'),
5374 _(b'a remote changeset intended to be added'),
5381 _(b'REV'),
5375 _(b'REV'),
5382 ),
5376 ),
5383 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5377 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5384 (
5378 (
5385 b'b',
5379 b'b',
5386 b'branch',
5380 b'branch',
5387 [],
5381 [],
5388 _(b'a specific branch you would like to pull'),
5382 _(b'a specific branch you would like to pull'),
5389 _(b'BRANCH'),
5383 _(b'BRANCH'),
5390 ),
5384 ),
5391 ]
5385 ]
5392 + remoteopts,
5386 + remoteopts,
5393 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5387 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5394 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5388 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5395 helpbasic=True,
5389 helpbasic=True,
5396 )
5390 )
5397 def pull(ui, repo, source=b"default", **opts):
5391 def pull(ui, repo, source=b"default", **opts):
5398 """pull changes from the specified source
5392 """pull changes from the specified source
5399
5393
5400 Pull changes from a remote repository to a local one.
5394 Pull changes from a remote repository to a local one.
5401
5395
5402 This finds all changes from the repository at the specified path
5396 This finds all changes from the repository at the specified path
5403 or URL and adds them to a local repository (the current one unless
5397 or URL and adds them to a local repository (the current one unless
5404 -R is specified). By default, this does not update the copy of the
5398 -R is specified). By default, this does not update the copy of the
5405 project in the working directory.
5399 project in the working directory.
5406
5400
5407 When cloning from servers that support it, Mercurial may fetch
5401 When cloning from servers that support it, Mercurial may fetch
5408 pre-generated data. When this is done, hooks operating on incoming
5402 pre-generated data. When this is done, hooks operating on incoming
5409 changesets and changegroups may fire more than once, once for each
5403 changesets and changegroups may fire more than once, once for each
5410 pre-generated bundle and as well as for any additional remaining
5404 pre-generated bundle and as well as for any additional remaining
5411 data. See :hg:`help -e clonebundles` for more.
5405 data. See :hg:`help -e clonebundles` for more.
5412
5406
5413 Use :hg:`incoming` if you want to see what would have been added
5407 Use :hg:`incoming` if you want to see what would have been added
5414 by a pull at the time you issued this command. If you then decide
5408 by a pull at the time you issued this command. If you then decide
5415 to add those changes to the repository, you should use :hg:`pull
5409 to add those changes to the repository, you should use :hg:`pull
5416 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5410 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5417
5411
5418 If SOURCE is omitted, the 'default' path will be used.
5412 If SOURCE is omitted, the 'default' path will be used.
5419 See :hg:`help urls` for more information.
5413 See :hg:`help urls` for more information.
5420
5414
5421 Specifying bookmark as ``.`` is equivalent to specifying the active
5415 Specifying bookmark as ``.`` is equivalent to specifying the active
5422 bookmark's name.
5416 bookmark's name.
5423
5417
5424 Returns 0 on success, 1 if an update had unresolved files.
5418 Returns 0 on success, 1 if an update had unresolved files.
5425 """
5419 """
5426
5420
5427 opts = pycompat.byteskwargs(opts)
5421 opts = pycompat.byteskwargs(opts)
5428 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5422 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5429 b'update'
5423 b'update'
5430 ):
5424 ):
5431 msg = _(b'update destination required by configuration')
5425 msg = _(b'update destination required by configuration')
5432 hint = _(b'use hg pull followed by hg update DEST')
5426 hint = _(b'use hg pull followed by hg update DEST')
5433 raise error.Abort(msg, hint=hint)
5427 raise error.Abort(msg, hint=hint)
5434
5428
5435 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5429 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5436 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5430 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5437 other = hg.peer(repo, opts, source)
5431 other = hg.peer(repo, opts, source)
5438 try:
5432 try:
5439 revs, checkout = hg.addbranchrevs(
5433 revs, checkout = hg.addbranchrevs(
5440 repo, other, branches, opts.get(b'rev')
5434 repo, other, branches, opts.get(b'rev')
5441 )
5435 )
5442
5436
5443 pullopargs = {}
5437 pullopargs = {}
5444
5438
5445 nodes = None
5439 nodes = None
5446 if opts.get(b'bookmark') or revs:
5440 if opts.get(b'bookmark') or revs:
5447 # The list of bookmark used here is the same used to actually update
5441 # The list of bookmark used here is the same used to actually update
5448 # the bookmark names, to avoid the race from issue 4689 and we do
5442 # the bookmark names, to avoid the race from issue 4689 and we do
5449 # all lookup and bookmark queries in one go so they see the same
5443 # all lookup and bookmark queries in one go so they see the same
5450 # version of the server state (issue 4700).
5444 # version of the server state (issue 4700).
5451 nodes = []
5445 nodes = []
5452 fnodes = []
5446 fnodes = []
5453 revs = revs or []
5447 revs = revs or []
5454 if revs and not other.capable(b'lookup'):
5448 if revs and not other.capable(b'lookup'):
5455 err = _(
5449 err = _(
5456 b"other repository doesn't support revision lookup, "
5450 b"other repository doesn't support revision lookup, "
5457 b"so a rev cannot be specified."
5451 b"so a rev cannot be specified."
5458 )
5452 )
5459 raise error.Abort(err)
5453 raise error.Abort(err)
5460 with other.commandexecutor() as e:
5454 with other.commandexecutor() as e:
5461 fremotebookmarks = e.callcommand(
5455 fremotebookmarks = e.callcommand(
5462 b'listkeys', {b'namespace': b'bookmarks'}
5456 b'listkeys', {b'namespace': b'bookmarks'}
5463 )
5457 )
5464 for r in revs:
5458 for r in revs:
5465 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5459 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5466 remotebookmarks = fremotebookmarks.result()
5460 remotebookmarks = fremotebookmarks.result()
5467 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5461 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5468 pullopargs[b'remotebookmarks'] = remotebookmarks
5462 pullopargs[b'remotebookmarks'] = remotebookmarks
5469 for b in opts.get(b'bookmark', []):
5463 for b in opts.get(b'bookmark', []):
5470 b = repo._bookmarks.expandname(b)
5464 b = repo._bookmarks.expandname(b)
5471 if b not in remotebookmarks:
5465 if b not in remotebookmarks:
5472 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5466 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5473 nodes.append(remotebookmarks[b])
5467 nodes.append(remotebookmarks[b])
5474 for i, rev in enumerate(revs):
5468 for i, rev in enumerate(revs):
5475 node = fnodes[i].result()
5469 node = fnodes[i].result()
5476 nodes.append(node)
5470 nodes.append(node)
5477 if rev == checkout:
5471 if rev == checkout:
5478 checkout = node
5472 checkout = node
5479
5473
5480 wlock = util.nullcontextmanager()
5474 wlock = util.nullcontextmanager()
5481 if opts.get(b'update'):
5475 if opts.get(b'update'):
5482 wlock = repo.wlock()
5476 wlock = repo.wlock()
5483 with wlock:
5477 with wlock:
5484 pullopargs.update(opts.get(b'opargs', {}))
5478 pullopargs.update(opts.get(b'opargs', {}))
5485 modheads = exchange.pull(
5479 modheads = exchange.pull(
5486 repo,
5480 repo,
5487 other,
5481 other,
5488 heads=nodes,
5482 heads=nodes,
5489 force=opts.get(b'force'),
5483 force=opts.get(b'force'),
5490 bookmarks=opts.get(b'bookmark', ()),
5484 bookmarks=opts.get(b'bookmark', ()),
5491 opargs=pullopargs,
5485 opargs=pullopargs,
5492 confirm=opts.get(b'confirm'),
5486 confirm=opts.get(b'confirm'),
5493 ).cgresult
5487 ).cgresult
5494
5488
5495 # brev is a name, which might be a bookmark to be activated at
5489 # brev is a name, which might be a bookmark to be activated at
5496 # the end of the update. In other words, it is an explicit
5490 # the end of the update. In other words, it is an explicit
5497 # destination of the update
5491 # destination of the update
5498 brev = None
5492 brev = None
5499
5493
5500 if checkout:
5494 if checkout:
5501 checkout = repo.unfiltered().changelog.rev(checkout)
5495 checkout = repo.unfiltered().changelog.rev(checkout)
5502
5496
5503 # order below depends on implementation of
5497 # order below depends on implementation of
5504 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5498 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5505 # because 'checkout' is determined without it.
5499 # because 'checkout' is determined without it.
5506 if opts.get(b'rev'):
5500 if opts.get(b'rev'):
5507 brev = opts[b'rev'][0]
5501 brev = opts[b'rev'][0]
5508 elif opts.get(b'branch'):
5502 elif opts.get(b'branch'):
5509 brev = opts[b'branch'][0]
5503 brev = opts[b'branch'][0]
5510 else:
5504 else:
5511 brev = branches[0]
5505 brev = branches[0]
5512 repo._subtoppath = source
5506 repo._subtoppath = source
5513 try:
5507 try:
5514 ret = postincoming(
5508 ret = postincoming(
5515 ui, repo, modheads, opts.get(b'update'), checkout, brev
5509 ui, repo, modheads, opts.get(b'update'), checkout, brev
5516 )
5510 )
5517 except error.FilteredRepoLookupError as exc:
5511 except error.FilteredRepoLookupError as exc:
5518 msg = _(b'cannot update to target: %s') % exc.args[0]
5512 msg = _(b'cannot update to target: %s') % exc.args[0]
5519 exc.args = (msg,) + exc.args[1:]
5513 exc.args = (msg,) + exc.args[1:]
5520 raise
5514 raise
5521 finally:
5515 finally:
5522 del repo._subtoppath
5516 del repo._subtoppath
5523
5517
5524 finally:
5518 finally:
5525 other.close()
5519 other.close()
5526 return ret
5520 return ret
5527
5521
5528
5522
5529 @command(
5523 @command(
5530 b'push',
5524 b'push',
5531 [
5525 [
5532 (b'f', b'force', None, _(b'force push')),
5526 (b'f', b'force', None, _(b'force push')),
5533 (
5527 (
5534 b'r',
5528 b'r',
5535 b'rev',
5529 b'rev',
5536 [],
5530 [],
5537 _(b'a changeset intended to be included in the destination'),
5531 _(b'a changeset intended to be included in the destination'),
5538 _(b'REV'),
5532 _(b'REV'),
5539 ),
5533 ),
5540 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5534 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5541 (
5535 (
5542 b'b',
5536 b'b',
5543 b'branch',
5537 b'branch',
5544 [],
5538 [],
5545 _(b'a specific branch you would like to push'),
5539 _(b'a specific branch you would like to push'),
5546 _(b'BRANCH'),
5540 _(b'BRANCH'),
5547 ),
5541 ),
5548 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5542 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5549 (
5543 (
5550 b'',
5544 b'',
5551 b'pushvars',
5545 b'pushvars',
5552 [],
5546 [],
5553 _(b'variables that can be sent to server (ADVANCED)'),
5547 _(b'variables that can be sent to server (ADVANCED)'),
5554 ),
5548 ),
5555 (
5549 (
5556 b'',
5550 b'',
5557 b'publish',
5551 b'publish',
5558 False,
5552 False,
5559 _(b'push the changeset as public (EXPERIMENTAL)'),
5553 _(b'push the changeset as public (EXPERIMENTAL)'),
5560 ),
5554 ),
5561 ]
5555 ]
5562 + remoteopts,
5556 + remoteopts,
5563 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5557 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5564 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5558 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5565 helpbasic=True,
5559 helpbasic=True,
5566 )
5560 )
5567 def push(ui, repo, dest=None, **opts):
5561 def push(ui, repo, dest=None, **opts):
5568 """push changes to the specified destination
5562 """push changes to the specified destination
5569
5563
5570 Push changesets from the local repository to the specified
5564 Push changesets from the local repository to the specified
5571 destination.
5565 destination.
5572
5566
5573 This operation is symmetrical to pull: it is identical to a pull
5567 This operation is symmetrical to pull: it is identical to a pull
5574 in the destination repository from the current one.
5568 in the destination repository from the current one.
5575
5569
5576 By default, push will not allow creation of new heads at the
5570 By default, push will not allow creation of new heads at the
5577 destination, since multiple heads would make it unclear which head
5571 destination, since multiple heads would make it unclear which head
5578 to use. In this situation, it is recommended to pull and merge
5572 to use. In this situation, it is recommended to pull and merge
5579 before pushing.
5573 before pushing.
5580
5574
5581 Use --new-branch if you want to allow push to create a new named
5575 Use --new-branch if you want to allow push to create a new named
5582 branch that is not present at the destination. This allows you to
5576 branch that is not present at the destination. This allows you to
5583 only create a new branch without forcing other changes.
5577 only create a new branch without forcing other changes.
5584
5578
5585 .. note::
5579 .. note::
5586
5580
5587 Extra care should be taken with the -f/--force option,
5581 Extra care should be taken with the -f/--force option,
5588 which will push all new heads on all branches, an action which will
5582 which will push all new heads on all branches, an action which will
5589 almost always cause confusion for collaborators.
5583 almost always cause confusion for collaborators.
5590
5584
5591 If -r/--rev is used, the specified revision and all its ancestors
5585 If -r/--rev is used, the specified revision and all its ancestors
5592 will be pushed to the remote repository.
5586 will be pushed to the remote repository.
5593
5587
5594 If -B/--bookmark is used, the specified bookmarked revision, its
5588 If -B/--bookmark is used, the specified bookmarked revision, its
5595 ancestors, and the bookmark will be pushed to the remote
5589 ancestors, and the bookmark will be pushed to the remote
5596 repository. Specifying ``.`` is equivalent to specifying the active
5590 repository. Specifying ``.`` is equivalent to specifying the active
5597 bookmark's name.
5591 bookmark's name.
5598
5592
5599 Please see :hg:`help urls` for important details about ``ssh://``
5593 Please see :hg:`help urls` for important details about ``ssh://``
5600 URLs. If DESTINATION is omitted, a default path will be used.
5594 URLs. If DESTINATION is omitted, a default path will be used.
5601
5595
5602 .. container:: verbose
5596 .. container:: verbose
5603
5597
5604 The --pushvars option sends strings to the server that become
5598 The --pushvars option sends strings to the server that become
5605 environment variables prepended with ``HG_USERVAR_``. For example,
5599 environment variables prepended with ``HG_USERVAR_``. For example,
5606 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5600 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5607 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5601 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5608
5602
5609 pushvars can provide for user-overridable hooks as well as set debug
5603 pushvars can provide for user-overridable hooks as well as set debug
5610 levels. One example is having a hook that blocks commits containing
5604 levels. One example is having a hook that blocks commits containing
5611 conflict markers, but enables the user to override the hook if the file
5605 conflict markers, but enables the user to override the hook if the file
5612 is using conflict markers for testing purposes or the file format has
5606 is using conflict markers for testing purposes or the file format has
5613 strings that look like conflict markers.
5607 strings that look like conflict markers.
5614
5608
5615 By default, servers will ignore `--pushvars`. To enable it add the
5609 By default, servers will ignore `--pushvars`. To enable it add the
5616 following to your configuration file::
5610 following to your configuration file::
5617
5611
5618 [push]
5612 [push]
5619 pushvars.server = true
5613 pushvars.server = true
5620
5614
5621 Returns 0 if push was successful, 1 if nothing to push.
5615 Returns 0 if push was successful, 1 if nothing to push.
5622 """
5616 """
5623
5617
5624 opts = pycompat.byteskwargs(opts)
5618 opts = pycompat.byteskwargs(opts)
5625 if opts.get(b'bookmark'):
5619 if opts.get(b'bookmark'):
5626 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5620 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5627 for b in opts[b'bookmark']:
5621 for b in opts[b'bookmark']:
5628 # translate -B options to -r so changesets get pushed
5622 # translate -B options to -r so changesets get pushed
5629 b = repo._bookmarks.expandname(b)
5623 b = repo._bookmarks.expandname(b)
5630 if b in repo._bookmarks:
5624 if b in repo._bookmarks:
5631 opts.setdefault(b'rev', []).append(b)
5625 opts.setdefault(b'rev', []).append(b)
5632 else:
5626 else:
5633 # if we try to push a deleted bookmark, translate it to null
5627 # if we try to push a deleted bookmark, translate it to null
5634 # this lets simultaneous -r, -b options continue working
5628 # this lets simultaneous -r, -b options continue working
5635 opts.setdefault(b'rev', []).append(b"null")
5629 opts.setdefault(b'rev', []).append(b"null")
5636
5630
5637 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5631 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5638 if not path:
5632 if not path:
5639 raise error.Abort(
5633 raise error.Abort(
5640 _(b'default repository not configured!'),
5634 _(b'default repository not configured!'),
5641 hint=_(b"see 'hg help config.paths'"),
5635 hint=_(b"see 'hg help config.paths'"),
5642 )
5636 )
5643 dest = path.pushloc or path.loc
5637 dest = path.pushloc or path.loc
5644 branches = (path.branch, opts.get(b'branch') or [])
5638 branches = (path.branch, opts.get(b'branch') or [])
5645 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5639 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5646 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5640 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5647 other = hg.peer(repo, opts, dest)
5641 other = hg.peer(repo, opts, dest)
5648
5642
5649 if revs:
5643 if revs:
5650 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5644 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5651 if not revs:
5645 if not revs:
5652 raise error.Abort(
5646 raise error.Abort(
5653 _(b"specified revisions evaluate to an empty set"),
5647 _(b"specified revisions evaluate to an empty set"),
5654 hint=_(b"use different revision arguments"),
5648 hint=_(b"use different revision arguments"),
5655 )
5649 )
5656 elif path.pushrev:
5650 elif path.pushrev:
5657 # It doesn't make any sense to specify ancestor revisions. So limit
5651 # It doesn't make any sense to specify ancestor revisions. So limit
5658 # to DAG heads to make discovery simpler.
5652 # to DAG heads to make discovery simpler.
5659 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5653 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5660 revs = scmutil.revrange(repo, [expr])
5654 revs = scmutil.revrange(repo, [expr])
5661 revs = [repo[rev].node() for rev in revs]
5655 revs = [repo[rev].node() for rev in revs]
5662 if not revs:
5656 if not revs:
5663 raise error.Abort(
5657 raise error.Abort(
5664 _(b'default push revset for path evaluates to an empty set')
5658 _(b'default push revset for path evaluates to an empty set')
5665 )
5659 )
5666 elif ui.configbool(b'commands', b'push.require-revs'):
5660 elif ui.configbool(b'commands', b'push.require-revs'):
5667 raise error.Abort(
5661 raise error.Abort(
5668 _(b'no revisions specified to push'),
5662 _(b'no revisions specified to push'),
5669 hint=_(b'did you mean "hg push -r ."?'),
5663 hint=_(b'did you mean "hg push -r ."?'),
5670 )
5664 )
5671
5665
5672 repo._subtoppath = dest
5666 repo._subtoppath = dest
5673 try:
5667 try:
5674 # push subrepos depth-first for coherent ordering
5668 # push subrepos depth-first for coherent ordering
5675 c = repo[b'.']
5669 c = repo[b'.']
5676 subs = c.substate # only repos that are committed
5670 subs = c.substate # only repos that are committed
5677 for s in sorted(subs):
5671 for s in sorted(subs):
5678 result = c.sub(s).push(opts)
5672 result = c.sub(s).push(opts)
5679 if result == 0:
5673 if result == 0:
5680 return not result
5674 return not result
5681 finally:
5675 finally:
5682 del repo._subtoppath
5676 del repo._subtoppath
5683
5677
5684 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5678 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5685 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5679 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5686
5680
5687 pushop = exchange.push(
5681 pushop = exchange.push(
5688 repo,
5682 repo,
5689 other,
5683 other,
5690 opts.get(b'force'),
5684 opts.get(b'force'),
5691 revs=revs,
5685 revs=revs,
5692 newbranch=opts.get(b'new_branch'),
5686 newbranch=opts.get(b'new_branch'),
5693 bookmarks=opts.get(b'bookmark', ()),
5687 bookmarks=opts.get(b'bookmark', ()),
5694 publish=opts.get(b'publish'),
5688 publish=opts.get(b'publish'),
5695 opargs=opargs,
5689 opargs=opargs,
5696 )
5690 )
5697
5691
5698 result = not pushop.cgresult
5692 result = not pushop.cgresult
5699
5693
5700 if pushop.bkresult is not None:
5694 if pushop.bkresult is not None:
5701 if pushop.bkresult == 2:
5695 if pushop.bkresult == 2:
5702 result = 2
5696 result = 2
5703 elif not result and pushop.bkresult:
5697 elif not result and pushop.bkresult:
5704 result = 2
5698 result = 2
5705
5699
5706 return result
5700 return result
5707
5701
5708
5702
5709 @command(
5703 @command(
5710 b'recover',
5704 b'recover',
5711 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5705 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5712 helpcategory=command.CATEGORY_MAINTENANCE,
5706 helpcategory=command.CATEGORY_MAINTENANCE,
5713 )
5707 )
5714 def recover(ui, repo, **opts):
5708 def recover(ui, repo, **opts):
5715 """roll back an interrupted transaction
5709 """roll back an interrupted transaction
5716
5710
5717 Recover from an interrupted commit or pull.
5711 Recover from an interrupted commit or pull.
5718
5712
5719 This command tries to fix the repository status after an
5713 This command tries to fix the repository status after an
5720 interrupted operation. It should only be necessary when Mercurial
5714 interrupted operation. It should only be necessary when Mercurial
5721 suggests it.
5715 suggests it.
5722
5716
5723 Returns 0 if successful, 1 if nothing to recover or verify fails.
5717 Returns 0 if successful, 1 if nothing to recover or verify fails.
5724 """
5718 """
5725 ret = repo.recover()
5719 ret = repo.recover()
5726 if ret:
5720 if ret:
5727 if opts['verify']:
5721 if opts['verify']:
5728 return hg.verify(repo)
5722 return hg.verify(repo)
5729 else:
5723 else:
5730 msg = _(
5724 msg = _(
5731 b"(verify step skipped, run `hg verify` to check your "
5725 b"(verify step skipped, run `hg verify` to check your "
5732 b"repository content)\n"
5726 b"repository content)\n"
5733 )
5727 )
5734 ui.warn(msg)
5728 ui.warn(msg)
5735 return 0
5729 return 0
5736 return 1
5730 return 1
5737
5731
5738
5732
5739 @command(
5733 @command(
5740 b'remove|rm',
5734 b'remove|rm',
5741 [
5735 [
5742 (b'A', b'after', None, _(b'record delete for missing files')),
5736 (b'A', b'after', None, _(b'record delete for missing files')),
5743 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5737 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5744 ]
5738 ]
5745 + subrepoopts
5739 + subrepoopts
5746 + walkopts
5740 + walkopts
5747 + dryrunopts,
5741 + dryrunopts,
5748 _(b'[OPTION]... FILE...'),
5742 _(b'[OPTION]... FILE...'),
5749 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5743 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5750 helpbasic=True,
5744 helpbasic=True,
5751 inferrepo=True,
5745 inferrepo=True,
5752 )
5746 )
5753 def remove(ui, repo, *pats, **opts):
5747 def remove(ui, repo, *pats, **opts):
5754 """remove the specified files on the next commit
5748 """remove the specified files on the next commit
5755
5749
5756 Schedule the indicated files for removal from the current branch.
5750 Schedule the indicated files for removal from the current branch.
5757
5751
5758 This command schedules the files to be removed at the next commit.
5752 This command schedules the files to be removed at the next commit.
5759 To undo a remove before that, see :hg:`revert`. To undo added
5753 To undo a remove before that, see :hg:`revert`. To undo added
5760 files, see :hg:`forget`.
5754 files, see :hg:`forget`.
5761
5755
5762 .. container:: verbose
5756 .. container:: verbose
5763
5757
5764 -A/--after can be used to remove only files that have already
5758 -A/--after can be used to remove only files that have already
5765 been deleted, -f/--force can be used to force deletion, and -Af
5759 been deleted, -f/--force can be used to force deletion, and -Af
5766 can be used to remove files from the next revision without
5760 can be used to remove files from the next revision without
5767 deleting them from the working directory.
5761 deleting them from the working directory.
5768
5762
5769 The following table details the behavior of remove for different
5763 The following table details the behavior of remove for different
5770 file states (columns) and option combinations (rows). The file
5764 file states (columns) and option combinations (rows). The file
5771 states are Added [A], Clean [C], Modified [M] and Missing [!]
5765 states are Added [A], Clean [C], Modified [M] and Missing [!]
5772 (as reported by :hg:`status`). The actions are Warn, Remove
5766 (as reported by :hg:`status`). The actions are Warn, Remove
5773 (from branch) and Delete (from disk):
5767 (from branch) and Delete (from disk):
5774
5768
5775 ========= == == == ==
5769 ========= == == == ==
5776 opt/state A C M !
5770 opt/state A C M !
5777 ========= == == == ==
5771 ========= == == == ==
5778 none W RD W R
5772 none W RD W R
5779 -f R RD RD R
5773 -f R RD RD R
5780 -A W W W R
5774 -A W W W R
5781 -Af R R R R
5775 -Af R R R R
5782 ========= == == == ==
5776 ========= == == == ==
5783
5777
5784 .. note::
5778 .. note::
5785
5779
5786 :hg:`remove` never deletes files in Added [A] state from the
5780 :hg:`remove` never deletes files in Added [A] state from the
5787 working directory, not even if ``--force`` is specified.
5781 working directory, not even if ``--force`` is specified.
5788
5782
5789 Returns 0 on success, 1 if any warnings encountered.
5783 Returns 0 on success, 1 if any warnings encountered.
5790 """
5784 """
5791
5785
5792 opts = pycompat.byteskwargs(opts)
5786 opts = pycompat.byteskwargs(opts)
5793 after, force = opts.get(b'after'), opts.get(b'force')
5787 after, force = opts.get(b'after'), opts.get(b'force')
5794 dryrun = opts.get(b'dry_run')
5788 dryrun = opts.get(b'dry_run')
5795 if not pats and not after:
5789 if not pats and not after:
5796 raise error.Abort(_(b'no files specified'))
5790 raise error.Abort(_(b'no files specified'))
5797
5791
5798 m = scmutil.match(repo[None], pats, opts)
5792 m = scmutil.match(repo[None], pats, opts)
5799 subrepos = opts.get(b'subrepos')
5793 subrepos = opts.get(b'subrepos')
5800 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5794 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5801 return cmdutil.remove(
5795 return cmdutil.remove(
5802 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5796 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5803 )
5797 )
5804
5798
5805
5799
5806 @command(
5800 @command(
5807 b'rename|move|mv',
5801 b'rename|move|mv',
5808 [
5802 [
5809 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5803 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5810 (
5804 (
5811 b'',
5805 b'',
5812 b'at-rev',
5806 b'at-rev',
5813 b'',
5807 b'',
5814 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5808 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5815 _(b'REV'),
5809 _(b'REV'),
5816 ),
5810 ),
5817 (
5811 (
5818 b'f',
5812 b'f',
5819 b'force',
5813 b'force',
5820 None,
5814 None,
5821 _(b'forcibly move over an existing managed file'),
5815 _(b'forcibly move over an existing managed file'),
5822 ),
5816 ),
5823 ]
5817 ]
5824 + walkopts
5818 + walkopts
5825 + dryrunopts,
5819 + dryrunopts,
5826 _(b'[OPTION]... SOURCE... DEST'),
5820 _(b'[OPTION]... SOURCE... DEST'),
5827 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5821 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5828 )
5822 )
5829 def rename(ui, repo, *pats, **opts):
5823 def rename(ui, repo, *pats, **opts):
5830 """rename files; equivalent of copy + remove
5824 """rename files; equivalent of copy + remove
5831
5825
5832 Mark dest as copies of sources; mark sources for deletion. If dest
5826 Mark dest as copies of sources; mark sources for deletion. If dest
5833 is a directory, copies are put in that directory. If dest is a
5827 is a directory, copies are put in that directory. If dest is a
5834 file, there can only be one source.
5828 file, there can only be one source.
5835
5829
5836 By default, this command copies the contents of files as they
5830 By default, this command copies the contents of files as they
5837 exist in the working directory. If invoked with -A/--after, the
5831 exist in the working directory. If invoked with -A/--after, the
5838 operation is recorded, but no copying is performed.
5832 operation is recorded, but no copying is performed.
5839
5833
5840 This command takes effect at the next commit. To undo a rename
5834 This command takes effect at the next commit. To undo a rename
5841 before that, see :hg:`revert`.
5835 before that, see :hg:`revert`.
5842
5836
5843 Returns 0 on success, 1 if errors are encountered.
5837 Returns 0 on success, 1 if errors are encountered.
5844 """
5838 """
5845 opts = pycompat.byteskwargs(opts)
5839 opts = pycompat.byteskwargs(opts)
5846 with repo.wlock():
5840 with repo.wlock():
5847 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5841 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5848
5842
5849
5843
5850 @command(
5844 @command(
5851 b'resolve',
5845 b'resolve',
5852 [
5846 [
5853 (b'a', b'all', None, _(b'select all unresolved files')),
5847 (b'a', b'all', None, _(b'select all unresolved files')),
5854 (b'l', b'list', None, _(b'list state of files needing merge')),
5848 (b'l', b'list', None, _(b'list state of files needing merge')),
5855 (b'm', b'mark', None, _(b'mark files as resolved')),
5849 (b'm', b'mark', None, _(b'mark files as resolved')),
5856 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5850 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5857 (b'n', b'no-status', None, _(b'hide status prefix')),
5851 (b'n', b'no-status', None, _(b'hide status prefix')),
5858 (b'', b're-merge', None, _(b're-merge files')),
5852 (b'', b're-merge', None, _(b're-merge files')),
5859 ]
5853 ]
5860 + mergetoolopts
5854 + mergetoolopts
5861 + walkopts
5855 + walkopts
5862 + formatteropts,
5856 + formatteropts,
5863 _(b'[OPTION]... [FILE]...'),
5857 _(b'[OPTION]... [FILE]...'),
5864 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5858 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5865 inferrepo=True,
5859 inferrepo=True,
5866 )
5860 )
5867 def resolve(ui, repo, *pats, **opts):
5861 def resolve(ui, repo, *pats, **opts):
5868 """redo merges or set/view the merge status of files
5862 """redo merges or set/view the merge status of files
5869
5863
5870 Merges with unresolved conflicts are often the result of
5864 Merges with unresolved conflicts are often the result of
5871 non-interactive merging using the ``internal:merge`` configuration
5865 non-interactive merging using the ``internal:merge`` configuration
5872 setting, or a command-line merge tool like ``diff3``. The resolve
5866 setting, or a command-line merge tool like ``diff3``. The resolve
5873 command is used to manage the files involved in a merge, after
5867 command is used to manage the files involved in a merge, after
5874 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5868 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5875 working directory must have two parents). See :hg:`help
5869 working directory must have two parents). See :hg:`help
5876 merge-tools` for information on configuring merge tools.
5870 merge-tools` for information on configuring merge tools.
5877
5871
5878 The resolve command can be used in the following ways:
5872 The resolve command can be used in the following ways:
5879
5873
5880 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5874 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5881 the specified files, discarding any previous merge attempts. Re-merging
5875 the specified files, discarding any previous merge attempts. Re-merging
5882 is not performed for files already marked as resolved. Use ``--all/-a``
5876 is not performed for files already marked as resolved. Use ``--all/-a``
5883 to select all unresolved files. ``--tool`` can be used to specify
5877 to select all unresolved files. ``--tool`` can be used to specify
5884 the merge tool used for the given files. It overrides the HGMERGE
5878 the merge tool used for the given files. It overrides the HGMERGE
5885 environment variable and your configuration files. Previous file
5879 environment variable and your configuration files. Previous file
5886 contents are saved with a ``.orig`` suffix.
5880 contents are saved with a ``.orig`` suffix.
5887
5881
5888 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5882 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5889 (e.g. after having manually fixed-up the files). The default is
5883 (e.g. after having manually fixed-up the files). The default is
5890 to mark all unresolved files.
5884 to mark all unresolved files.
5891
5885
5892 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5886 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5893 default is to mark all resolved files.
5887 default is to mark all resolved files.
5894
5888
5895 - :hg:`resolve -l`: list files which had or still have conflicts.
5889 - :hg:`resolve -l`: list files which had or still have conflicts.
5896 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5890 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5897 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5891 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5898 the list. See :hg:`help filesets` for details.
5892 the list. See :hg:`help filesets` for details.
5899
5893
5900 .. note::
5894 .. note::
5901
5895
5902 Mercurial will not let you commit files with unresolved merge
5896 Mercurial will not let you commit files with unresolved merge
5903 conflicts. You must use :hg:`resolve -m ...` before you can
5897 conflicts. You must use :hg:`resolve -m ...` before you can
5904 commit after a conflicting merge.
5898 commit after a conflicting merge.
5905
5899
5906 .. container:: verbose
5900 .. container:: verbose
5907
5901
5908 Template:
5902 Template:
5909
5903
5910 The following keywords are supported in addition to the common template
5904 The following keywords are supported in addition to the common template
5911 keywords and functions. See also :hg:`help templates`.
5905 keywords and functions. See also :hg:`help templates`.
5912
5906
5913 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5907 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5914 :path: String. Repository-absolute path of the file.
5908 :path: String. Repository-absolute path of the file.
5915
5909
5916 Returns 0 on success, 1 if any files fail a resolve attempt.
5910 Returns 0 on success, 1 if any files fail a resolve attempt.
5917 """
5911 """
5918
5912
5919 opts = pycompat.byteskwargs(opts)
5913 opts = pycompat.byteskwargs(opts)
5920 confirm = ui.configbool(b'commands', b'resolve.confirm')
5914 confirm = ui.configbool(b'commands', b'resolve.confirm')
5921 flaglist = b'all mark unmark list no_status re_merge'.split()
5915 flaglist = b'all mark unmark list no_status re_merge'.split()
5922 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5916 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5923
5917
5924 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5918 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5925 if actioncount > 1:
5919 if actioncount > 1:
5926 raise error.Abort(_(b"too many actions specified"))
5920 raise error.Abort(_(b"too many actions specified"))
5927 elif actioncount == 0 and ui.configbool(
5921 elif actioncount == 0 and ui.configbool(
5928 b'commands', b'resolve.explicit-re-merge'
5922 b'commands', b'resolve.explicit-re-merge'
5929 ):
5923 ):
5930 hint = _(b'use --mark, --unmark, --list or --re-merge')
5924 hint = _(b'use --mark, --unmark, --list or --re-merge')
5931 raise error.Abort(_(b'no action specified'), hint=hint)
5925 raise error.Abort(_(b'no action specified'), hint=hint)
5932 if pats and all:
5926 if pats and all:
5933 raise error.Abort(_(b"can't specify --all and patterns"))
5927 raise error.Abort(_(b"can't specify --all and patterns"))
5934 if not (all or pats or show or mark or unmark):
5928 if not (all or pats or show or mark or unmark):
5935 raise error.Abort(
5929 raise error.Abort(
5936 _(b'no files or directories specified'),
5930 _(b'no files or directories specified'),
5937 hint=b'use --all to re-merge all unresolved files',
5931 hint=b'use --all to re-merge all unresolved files',
5938 )
5932 )
5939
5933
5940 if confirm:
5934 if confirm:
5941 if all:
5935 if all:
5942 if ui.promptchoice(
5936 if ui.promptchoice(
5943 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5937 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5944 ):
5938 ):
5945 raise error.Abort(_(b'user quit'))
5939 raise error.Abort(_(b'user quit'))
5946 if mark and not pats:
5940 if mark and not pats:
5947 if ui.promptchoice(
5941 if ui.promptchoice(
5948 _(
5942 _(
5949 b'mark all unresolved files as resolved (yn)?'
5943 b'mark all unresolved files as resolved (yn)?'
5950 b'$$ &Yes $$ &No'
5944 b'$$ &Yes $$ &No'
5951 )
5945 )
5952 ):
5946 ):
5953 raise error.Abort(_(b'user quit'))
5947 raise error.Abort(_(b'user quit'))
5954 if unmark and not pats:
5948 if unmark and not pats:
5955 if ui.promptchoice(
5949 if ui.promptchoice(
5956 _(
5950 _(
5957 b'mark all resolved files as unresolved (yn)?'
5951 b'mark all resolved files as unresolved (yn)?'
5958 b'$$ &Yes $$ &No'
5952 b'$$ &Yes $$ &No'
5959 )
5953 )
5960 ):
5954 ):
5961 raise error.Abort(_(b'user quit'))
5955 raise error.Abort(_(b'user quit'))
5962
5956
5963 uipathfn = scmutil.getuipathfn(repo)
5957 uipathfn = scmutil.getuipathfn(repo)
5964
5958
5965 if show:
5959 if show:
5966 ui.pager(b'resolve')
5960 ui.pager(b'resolve')
5967 fm = ui.formatter(b'resolve', opts)
5961 fm = ui.formatter(b'resolve', opts)
5968 ms = mergestatemod.mergestate.read(repo)
5962 ms = mergestatemod.mergestate.read(repo)
5969 wctx = repo[None]
5963 wctx = repo[None]
5970 m = scmutil.match(wctx, pats, opts)
5964 m = scmutil.match(wctx, pats, opts)
5971
5965
5972 # Labels and keys based on merge state. Unresolved path conflicts show
5966 # Labels and keys based on merge state. Unresolved path conflicts show
5973 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5967 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5974 # resolved conflicts.
5968 # resolved conflicts.
5975 mergestateinfo = {
5969 mergestateinfo = {
5976 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5970 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5977 b'resolve.unresolved',
5971 b'resolve.unresolved',
5978 b'U',
5972 b'U',
5979 ),
5973 ),
5980 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5974 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5981 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5975 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5982 b'resolve.unresolved',
5976 b'resolve.unresolved',
5983 b'P',
5977 b'P',
5984 ),
5978 ),
5985 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5979 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5986 b'resolve.resolved',
5980 b'resolve.resolved',
5987 b'R',
5981 b'R',
5988 ),
5982 ),
5989 }
5983 }
5990
5984
5991 for f in ms:
5985 for f in ms:
5992 if not m(f):
5986 if not m(f):
5993 continue
5987 continue
5994
5988
5995 label, key = mergestateinfo[ms[f]]
5989 label, key = mergestateinfo[ms[f]]
5996 fm.startitem()
5990 fm.startitem()
5997 fm.context(ctx=wctx)
5991 fm.context(ctx=wctx)
5998 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5992 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5999 fm.data(path=f)
5993 fm.data(path=f)
6000 fm.plain(b'%s\n' % uipathfn(f), label=label)
5994 fm.plain(b'%s\n' % uipathfn(f), label=label)
6001 fm.end()
5995 fm.end()
6002 return 0
5996 return 0
6003
5997
6004 with repo.wlock():
5998 with repo.wlock():
6005 ms = mergestatemod.mergestate.read(repo)
5999 ms = mergestatemod.mergestate.read(repo)
6006
6000
6007 if not (ms.active() or repo.dirstate.p2() != nullid):
6001 if not (ms.active() or repo.dirstate.p2() != nullid):
6008 raise error.Abort(
6002 raise error.Abort(
6009 _(b'resolve command not applicable when not merging')
6003 _(b'resolve command not applicable when not merging')
6010 )
6004 )
6011
6005
6012 wctx = repo[None]
6006 wctx = repo[None]
6013 m = scmutil.match(wctx, pats, opts)
6007 m = scmutil.match(wctx, pats, opts)
6014 ret = 0
6008 ret = 0
6015 didwork = False
6009 didwork = False
6016
6010
6017 tocomplete = []
6011 tocomplete = []
6018 hasconflictmarkers = []
6012 hasconflictmarkers = []
6019 if mark:
6013 if mark:
6020 markcheck = ui.config(b'commands', b'resolve.mark-check')
6014 markcheck = ui.config(b'commands', b'resolve.mark-check')
6021 if markcheck not in [b'warn', b'abort']:
6015 if markcheck not in [b'warn', b'abort']:
6022 # Treat all invalid / unrecognized values as 'none'.
6016 # Treat all invalid / unrecognized values as 'none'.
6023 markcheck = False
6017 markcheck = False
6024 for f in ms:
6018 for f in ms:
6025 if not m(f):
6019 if not m(f):
6026 continue
6020 continue
6027
6021
6028 didwork = True
6022 didwork = True
6029
6023
6030 # path conflicts must be resolved manually
6024 # path conflicts must be resolved manually
6031 if ms[f] in (
6025 if ms[f] in (
6032 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6026 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6033 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6027 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6034 ):
6028 ):
6035 if mark:
6029 if mark:
6036 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6030 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6037 elif unmark:
6031 elif unmark:
6038 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6032 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6039 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6033 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6040 ui.warn(
6034 ui.warn(
6041 _(b'%s: path conflict must be resolved manually\n')
6035 _(b'%s: path conflict must be resolved manually\n')
6042 % uipathfn(f)
6036 % uipathfn(f)
6043 )
6037 )
6044 continue
6038 continue
6045
6039
6046 if mark:
6040 if mark:
6047 if markcheck:
6041 if markcheck:
6048 fdata = repo.wvfs.tryread(f)
6042 fdata = repo.wvfs.tryread(f)
6049 if (
6043 if (
6050 filemerge.hasconflictmarkers(fdata)
6044 filemerge.hasconflictmarkers(fdata)
6051 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6045 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6052 ):
6046 ):
6053 hasconflictmarkers.append(f)
6047 hasconflictmarkers.append(f)
6054 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6048 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6055 elif unmark:
6049 elif unmark:
6056 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6050 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6057 else:
6051 else:
6058 # backup pre-resolve (merge uses .orig for its own purposes)
6052 # backup pre-resolve (merge uses .orig for its own purposes)
6059 a = repo.wjoin(f)
6053 a = repo.wjoin(f)
6060 try:
6054 try:
6061 util.copyfile(a, a + b".resolve")
6055 util.copyfile(a, a + b".resolve")
6062 except (IOError, OSError) as inst:
6056 except (IOError, OSError) as inst:
6063 if inst.errno != errno.ENOENT:
6057 if inst.errno != errno.ENOENT:
6064 raise
6058 raise
6065
6059
6066 try:
6060 try:
6067 # preresolve file
6061 # preresolve file
6068 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6062 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6069 with ui.configoverride(overrides, b'resolve'):
6063 with ui.configoverride(overrides, b'resolve'):
6070 complete, r = ms.preresolve(f, wctx)
6064 complete, r = ms.preresolve(f, wctx)
6071 if not complete:
6065 if not complete:
6072 tocomplete.append(f)
6066 tocomplete.append(f)
6073 elif r:
6067 elif r:
6074 ret = 1
6068 ret = 1
6075 finally:
6069 finally:
6076 ms.commit()
6070 ms.commit()
6077
6071
6078 # replace filemerge's .orig file with our resolve file, but only
6072 # replace filemerge's .orig file with our resolve file, but only
6079 # for merges that are complete
6073 # for merges that are complete
6080 if complete:
6074 if complete:
6081 try:
6075 try:
6082 util.rename(
6076 util.rename(
6083 a + b".resolve", scmutil.backuppath(ui, repo, f)
6077 a + b".resolve", scmutil.backuppath(ui, repo, f)
6084 )
6078 )
6085 except OSError as inst:
6079 except OSError as inst:
6086 if inst.errno != errno.ENOENT:
6080 if inst.errno != errno.ENOENT:
6087 raise
6081 raise
6088
6082
6089 if hasconflictmarkers:
6083 if hasconflictmarkers:
6090 ui.warn(
6084 ui.warn(
6091 _(
6085 _(
6092 b'warning: the following files still have conflict '
6086 b'warning: the following files still have conflict '
6093 b'markers:\n'
6087 b'markers:\n'
6094 )
6088 )
6095 + b''.join(
6089 + b''.join(
6096 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6090 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6097 )
6091 )
6098 )
6092 )
6099 if markcheck == b'abort' and not all and not pats:
6093 if markcheck == b'abort' and not all and not pats:
6100 raise error.Abort(
6094 raise error.Abort(
6101 _(b'conflict markers detected'),
6095 _(b'conflict markers detected'),
6102 hint=_(b'use --all to mark anyway'),
6096 hint=_(b'use --all to mark anyway'),
6103 )
6097 )
6104
6098
6105 for f in tocomplete:
6099 for f in tocomplete:
6106 try:
6100 try:
6107 # resolve file
6101 # resolve file
6108 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6102 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6109 with ui.configoverride(overrides, b'resolve'):
6103 with ui.configoverride(overrides, b'resolve'):
6110 r = ms.resolve(f, wctx)
6104 r = ms.resolve(f, wctx)
6111 if r:
6105 if r:
6112 ret = 1
6106 ret = 1
6113 finally:
6107 finally:
6114 ms.commit()
6108 ms.commit()
6115
6109
6116 # replace filemerge's .orig file with our resolve file
6110 # replace filemerge's .orig file with our resolve file
6117 a = repo.wjoin(f)
6111 a = repo.wjoin(f)
6118 try:
6112 try:
6119 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6113 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6120 except OSError as inst:
6114 except OSError as inst:
6121 if inst.errno != errno.ENOENT:
6115 if inst.errno != errno.ENOENT:
6122 raise
6116 raise
6123
6117
6124 ms.commit()
6118 ms.commit()
6125 branchmerge = repo.dirstate.p2() != nullid
6119 branchmerge = repo.dirstate.p2() != nullid
6126 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6120 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6127
6121
6128 if not didwork and pats:
6122 if not didwork and pats:
6129 hint = None
6123 hint = None
6130 if not any([p for p in pats if p.find(b':') >= 0]):
6124 if not any([p for p in pats if p.find(b':') >= 0]):
6131 pats = [b'path:%s' % p for p in pats]
6125 pats = [b'path:%s' % p for p in pats]
6132 m = scmutil.match(wctx, pats, opts)
6126 m = scmutil.match(wctx, pats, opts)
6133 for f in ms:
6127 for f in ms:
6134 if not m(f):
6128 if not m(f):
6135 continue
6129 continue
6136
6130
6137 def flag(o):
6131 def flag(o):
6138 if o == b're_merge':
6132 if o == b're_merge':
6139 return b'--re-merge '
6133 return b'--re-merge '
6140 return b'-%s ' % o[0:1]
6134 return b'-%s ' % o[0:1]
6141
6135
6142 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6136 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6143 hint = _(b"(try: hg resolve %s%s)\n") % (
6137 hint = _(b"(try: hg resolve %s%s)\n") % (
6144 flags,
6138 flags,
6145 b' '.join(pats),
6139 b' '.join(pats),
6146 )
6140 )
6147 break
6141 break
6148 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6142 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6149 if hint:
6143 if hint:
6150 ui.warn(hint)
6144 ui.warn(hint)
6151
6145
6152 unresolvedf = list(ms.unresolved())
6146 unresolvedf = list(ms.unresolved())
6153 if not unresolvedf:
6147 if not unresolvedf:
6154 ui.status(_(b'(no more unresolved files)\n'))
6148 ui.status(_(b'(no more unresolved files)\n'))
6155 cmdutil.checkafterresolved(repo)
6149 cmdutil.checkafterresolved(repo)
6156
6150
6157 return ret
6151 return ret
6158
6152
6159
6153
6160 @command(
6154 @command(
6161 b'revert',
6155 b'revert',
6162 [
6156 [
6163 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6157 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6164 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6158 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6165 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6159 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6166 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6160 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6167 (b'i', b'interactive', None, _(b'interactively select the changes')),
6161 (b'i', b'interactive', None, _(b'interactively select the changes')),
6168 ]
6162 ]
6169 + walkopts
6163 + walkopts
6170 + dryrunopts,
6164 + dryrunopts,
6171 _(b'[OPTION]... [-r REV] [NAME]...'),
6165 _(b'[OPTION]... [-r REV] [NAME]...'),
6172 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6166 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6173 )
6167 )
6174 def revert(ui, repo, *pats, **opts):
6168 def revert(ui, repo, *pats, **opts):
6175 """restore files to their checkout state
6169 """restore files to their checkout state
6176
6170
6177 .. note::
6171 .. note::
6178
6172
6179 To check out earlier revisions, you should use :hg:`update REV`.
6173 To check out earlier revisions, you should use :hg:`update REV`.
6180 To cancel an uncommitted merge (and lose your changes),
6174 To cancel an uncommitted merge (and lose your changes),
6181 use :hg:`merge --abort`.
6175 use :hg:`merge --abort`.
6182
6176
6183 With no revision specified, revert the specified files or directories
6177 With no revision specified, revert the specified files or directories
6184 to the contents they had in the parent of the working directory.
6178 to the contents they had in the parent of the working directory.
6185 This restores the contents of files to an unmodified
6179 This restores the contents of files to an unmodified
6186 state and unschedules adds, removes, copies, and renames. If the
6180 state and unschedules adds, removes, copies, and renames. If the
6187 working directory has two parents, you must explicitly specify a
6181 working directory has two parents, you must explicitly specify a
6188 revision.
6182 revision.
6189
6183
6190 Using the -r/--rev or -d/--date options, revert the given files or
6184 Using the -r/--rev or -d/--date options, revert the given files or
6191 directories to their states as of a specific revision. Because
6185 directories to their states as of a specific revision. Because
6192 revert does not change the working directory parents, this will
6186 revert does not change the working directory parents, this will
6193 cause these files to appear modified. This can be helpful to "back
6187 cause these files to appear modified. This can be helpful to "back
6194 out" some or all of an earlier change. See :hg:`backout` for a
6188 out" some or all of an earlier change. See :hg:`backout` for a
6195 related method.
6189 related method.
6196
6190
6197 Modified files are saved with a .orig suffix before reverting.
6191 Modified files are saved with a .orig suffix before reverting.
6198 To disable these backups, use --no-backup. It is possible to store
6192 To disable these backups, use --no-backup. It is possible to store
6199 the backup files in a custom directory relative to the root of the
6193 the backup files in a custom directory relative to the root of the
6200 repository by setting the ``ui.origbackuppath`` configuration
6194 repository by setting the ``ui.origbackuppath`` configuration
6201 option.
6195 option.
6202
6196
6203 See :hg:`help dates` for a list of formats valid for -d/--date.
6197 See :hg:`help dates` for a list of formats valid for -d/--date.
6204
6198
6205 See :hg:`help backout` for a way to reverse the effect of an
6199 See :hg:`help backout` for a way to reverse the effect of an
6206 earlier changeset.
6200 earlier changeset.
6207
6201
6208 Returns 0 on success.
6202 Returns 0 on success.
6209 """
6203 """
6210
6204
6211 opts = pycompat.byteskwargs(opts)
6205 opts = pycompat.byteskwargs(opts)
6212 if opts.get(b"date"):
6206 if opts.get(b"date"):
6213 if opts.get(b"rev"):
6207 if opts.get(b"rev"):
6214 raise error.Abort(_(b"you can't specify a revision and a date"))
6208 raise error.Abort(_(b"you can't specify a revision and a date"))
6215 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6209 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6216
6210
6217 parent, p2 = repo.dirstate.parents()
6211 parent, p2 = repo.dirstate.parents()
6218 if not opts.get(b'rev') and p2 != nullid:
6212 if not opts.get(b'rev') and p2 != nullid:
6219 # revert after merge is a trap for new users (issue2915)
6213 # revert after merge is a trap for new users (issue2915)
6220 raise error.Abort(
6214 raise error.Abort(
6221 _(b'uncommitted merge with no revision specified'),
6215 _(b'uncommitted merge with no revision specified'),
6222 hint=_(b"use 'hg update' or see 'hg help revert'"),
6216 hint=_(b"use 'hg update' or see 'hg help revert'"),
6223 )
6217 )
6224
6218
6225 rev = opts.get(b'rev')
6219 rev = opts.get(b'rev')
6226 if rev:
6220 if rev:
6227 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6221 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6228 ctx = scmutil.revsingle(repo, rev)
6222 ctx = scmutil.revsingle(repo, rev)
6229
6223
6230 if not (
6224 if not (
6231 pats
6225 pats
6232 or opts.get(b'include')
6226 or opts.get(b'include')
6233 or opts.get(b'exclude')
6227 or opts.get(b'exclude')
6234 or opts.get(b'all')
6228 or opts.get(b'all')
6235 or opts.get(b'interactive')
6229 or opts.get(b'interactive')
6236 ):
6230 ):
6237 msg = _(b"no files or directories specified")
6231 msg = _(b"no files or directories specified")
6238 if p2 != nullid:
6232 if p2 != nullid:
6239 hint = _(
6233 hint = _(
6240 b"uncommitted merge, use --all to discard all changes,"
6234 b"uncommitted merge, use --all to discard all changes,"
6241 b" or 'hg update -C .' to abort the merge"
6235 b" or 'hg update -C .' to abort the merge"
6242 )
6236 )
6243 raise error.Abort(msg, hint=hint)
6237 raise error.Abort(msg, hint=hint)
6244 dirty = any(repo.status())
6238 dirty = any(repo.status())
6245 node = ctx.node()
6239 node = ctx.node()
6246 if node != parent:
6240 if node != parent:
6247 if dirty:
6241 if dirty:
6248 hint = (
6242 hint = (
6249 _(
6243 _(
6250 b"uncommitted changes, use --all to discard all"
6244 b"uncommitted changes, use --all to discard all"
6251 b" changes, or 'hg update %d' to update"
6245 b" changes, or 'hg update %d' to update"
6252 )
6246 )
6253 % ctx.rev()
6247 % ctx.rev()
6254 )
6248 )
6255 else:
6249 else:
6256 hint = (
6250 hint = (
6257 _(
6251 _(
6258 b"use --all to revert all files,"
6252 b"use --all to revert all files,"
6259 b" or 'hg update %d' to update"
6253 b" or 'hg update %d' to update"
6260 )
6254 )
6261 % ctx.rev()
6255 % ctx.rev()
6262 )
6256 )
6263 elif dirty:
6257 elif dirty:
6264 hint = _(b"uncommitted changes, use --all to discard all changes")
6258 hint = _(b"uncommitted changes, use --all to discard all changes")
6265 else:
6259 else:
6266 hint = _(b"use --all to revert all files")
6260 hint = _(b"use --all to revert all files")
6267 raise error.Abort(msg, hint=hint)
6261 raise error.Abort(msg, hint=hint)
6268
6262
6269 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6263 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6270
6264
6271
6265
6272 @command(
6266 @command(
6273 b'rollback',
6267 b'rollback',
6274 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6268 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6275 helpcategory=command.CATEGORY_MAINTENANCE,
6269 helpcategory=command.CATEGORY_MAINTENANCE,
6276 )
6270 )
6277 def rollback(ui, repo, **opts):
6271 def rollback(ui, repo, **opts):
6278 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6272 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6279
6273
6280 Please use :hg:`commit --amend` instead of rollback to correct
6274 Please use :hg:`commit --amend` instead of rollback to correct
6281 mistakes in the last commit.
6275 mistakes in the last commit.
6282
6276
6283 This command should be used with care. There is only one level of
6277 This command should be used with care. There is only one level of
6284 rollback, and there is no way to undo a rollback. It will also
6278 rollback, and there is no way to undo a rollback. It will also
6285 restore the dirstate at the time of the last transaction, losing
6279 restore the dirstate at the time of the last transaction, losing
6286 any dirstate changes since that time. This command does not alter
6280 any dirstate changes since that time. This command does not alter
6287 the working directory.
6281 the working directory.
6288
6282
6289 Transactions are used to encapsulate the effects of all commands
6283 Transactions are used to encapsulate the effects of all commands
6290 that create new changesets or propagate existing changesets into a
6284 that create new changesets or propagate existing changesets into a
6291 repository.
6285 repository.
6292
6286
6293 .. container:: verbose
6287 .. container:: verbose
6294
6288
6295 For example, the following commands are transactional, and their
6289 For example, the following commands are transactional, and their
6296 effects can be rolled back:
6290 effects can be rolled back:
6297
6291
6298 - commit
6292 - commit
6299 - import
6293 - import
6300 - pull
6294 - pull
6301 - push (with this repository as the destination)
6295 - push (with this repository as the destination)
6302 - unbundle
6296 - unbundle
6303
6297
6304 To avoid permanent data loss, rollback will refuse to rollback a
6298 To avoid permanent data loss, rollback will refuse to rollback a
6305 commit transaction if it isn't checked out. Use --force to
6299 commit transaction if it isn't checked out. Use --force to
6306 override this protection.
6300 override this protection.
6307
6301
6308 The rollback command can be entirely disabled by setting the
6302 The rollback command can be entirely disabled by setting the
6309 ``ui.rollback`` configuration setting to false. If you're here
6303 ``ui.rollback`` configuration setting to false. If you're here
6310 because you want to use rollback and it's disabled, you can
6304 because you want to use rollback and it's disabled, you can
6311 re-enable the command by setting ``ui.rollback`` to true.
6305 re-enable the command by setting ``ui.rollback`` to true.
6312
6306
6313 This command is not intended for use on public repositories. Once
6307 This command is not intended for use on public repositories. Once
6314 changes are visible for pull by other users, rolling a transaction
6308 changes are visible for pull by other users, rolling a transaction
6315 back locally is ineffective (someone else may already have pulled
6309 back locally is ineffective (someone else may already have pulled
6316 the changes). Furthermore, a race is possible with readers of the
6310 the changes). Furthermore, a race is possible with readers of the
6317 repository; for example an in-progress pull from the repository
6311 repository; for example an in-progress pull from the repository
6318 may fail if a rollback is performed.
6312 may fail if a rollback is performed.
6319
6313
6320 Returns 0 on success, 1 if no rollback data is available.
6314 Returns 0 on success, 1 if no rollback data is available.
6321 """
6315 """
6322 if not ui.configbool(b'ui', b'rollback'):
6316 if not ui.configbool(b'ui', b'rollback'):
6323 raise error.Abort(
6317 raise error.Abort(
6324 _(b'rollback is disabled because it is unsafe'),
6318 _(b'rollback is disabled because it is unsafe'),
6325 hint=b'see `hg help -v rollback` for information',
6319 hint=b'see `hg help -v rollback` for information',
6326 )
6320 )
6327 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6321 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6328
6322
6329
6323
6330 @command(
6324 @command(
6331 b'root',
6325 b'root',
6332 [] + formatteropts,
6326 [] + formatteropts,
6333 intents={INTENT_READONLY},
6327 intents={INTENT_READONLY},
6334 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6328 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6335 )
6329 )
6336 def root(ui, repo, **opts):
6330 def root(ui, repo, **opts):
6337 """print the root (top) of the current working directory
6331 """print the root (top) of the current working directory
6338
6332
6339 Print the root directory of the current repository.
6333 Print the root directory of the current repository.
6340
6334
6341 .. container:: verbose
6335 .. container:: verbose
6342
6336
6343 Template:
6337 Template:
6344
6338
6345 The following keywords are supported in addition to the common template
6339 The following keywords are supported in addition to the common template
6346 keywords and functions. See also :hg:`help templates`.
6340 keywords and functions. See also :hg:`help templates`.
6347
6341
6348 :hgpath: String. Path to the .hg directory.
6342 :hgpath: String. Path to the .hg directory.
6349 :storepath: String. Path to the directory holding versioned data.
6343 :storepath: String. Path to the directory holding versioned data.
6350
6344
6351 Returns 0 on success.
6345 Returns 0 on success.
6352 """
6346 """
6353 opts = pycompat.byteskwargs(opts)
6347 opts = pycompat.byteskwargs(opts)
6354 with ui.formatter(b'root', opts) as fm:
6348 with ui.formatter(b'root', opts) as fm:
6355 fm.startitem()
6349 fm.startitem()
6356 fm.write(b'reporoot', b'%s\n', repo.root)
6350 fm.write(b'reporoot', b'%s\n', repo.root)
6357 fm.data(hgpath=repo.path, storepath=repo.spath)
6351 fm.data(hgpath=repo.path, storepath=repo.spath)
6358
6352
6359
6353
6360 @command(
6354 @command(
6361 b'serve',
6355 b'serve',
6362 [
6356 [
6363 (
6357 (
6364 b'A',
6358 b'A',
6365 b'accesslog',
6359 b'accesslog',
6366 b'',
6360 b'',
6367 _(b'name of access log file to write to'),
6361 _(b'name of access log file to write to'),
6368 _(b'FILE'),
6362 _(b'FILE'),
6369 ),
6363 ),
6370 (b'd', b'daemon', None, _(b'run server in background')),
6364 (b'd', b'daemon', None, _(b'run server in background')),
6371 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6365 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6372 (
6366 (
6373 b'E',
6367 b'E',
6374 b'errorlog',
6368 b'errorlog',
6375 b'',
6369 b'',
6376 _(b'name of error log file to write to'),
6370 _(b'name of error log file to write to'),
6377 _(b'FILE'),
6371 _(b'FILE'),
6378 ),
6372 ),
6379 # use string type, then we can check if something was passed
6373 # use string type, then we can check if something was passed
6380 (
6374 (
6381 b'p',
6375 b'p',
6382 b'port',
6376 b'port',
6383 b'',
6377 b'',
6384 _(b'port to listen on (default: 8000)'),
6378 _(b'port to listen on (default: 8000)'),
6385 _(b'PORT'),
6379 _(b'PORT'),
6386 ),
6380 ),
6387 (
6381 (
6388 b'a',
6382 b'a',
6389 b'address',
6383 b'address',
6390 b'',
6384 b'',
6391 _(b'address to listen on (default: all interfaces)'),
6385 _(b'address to listen on (default: all interfaces)'),
6392 _(b'ADDR'),
6386 _(b'ADDR'),
6393 ),
6387 ),
6394 (
6388 (
6395 b'',
6389 b'',
6396 b'prefix',
6390 b'prefix',
6397 b'',
6391 b'',
6398 _(b'prefix path to serve from (default: server root)'),
6392 _(b'prefix path to serve from (default: server root)'),
6399 _(b'PREFIX'),
6393 _(b'PREFIX'),
6400 ),
6394 ),
6401 (
6395 (
6402 b'n',
6396 b'n',
6403 b'name',
6397 b'name',
6404 b'',
6398 b'',
6405 _(b'name to show in web pages (default: working directory)'),
6399 _(b'name to show in web pages (default: working directory)'),
6406 _(b'NAME'),
6400 _(b'NAME'),
6407 ),
6401 ),
6408 (
6402 (
6409 b'',
6403 b'',
6410 b'web-conf',
6404 b'web-conf',
6411 b'',
6405 b'',
6412 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6406 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6413 _(b'FILE'),
6407 _(b'FILE'),
6414 ),
6408 ),
6415 (
6409 (
6416 b'',
6410 b'',
6417 b'webdir-conf',
6411 b'webdir-conf',
6418 b'',
6412 b'',
6419 _(b'name of the hgweb config file (DEPRECATED)'),
6413 _(b'name of the hgweb config file (DEPRECATED)'),
6420 _(b'FILE'),
6414 _(b'FILE'),
6421 ),
6415 ),
6422 (
6416 (
6423 b'',
6417 b'',
6424 b'pid-file',
6418 b'pid-file',
6425 b'',
6419 b'',
6426 _(b'name of file to write process ID to'),
6420 _(b'name of file to write process ID to'),
6427 _(b'FILE'),
6421 _(b'FILE'),
6428 ),
6422 ),
6429 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6423 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6430 (
6424 (
6431 b'',
6425 b'',
6432 b'cmdserver',
6426 b'cmdserver',
6433 b'',
6427 b'',
6434 _(b'for remote clients (ADVANCED)'),
6428 _(b'for remote clients (ADVANCED)'),
6435 _(b'MODE'),
6429 _(b'MODE'),
6436 ),
6430 ),
6437 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6431 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6438 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6432 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6439 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6433 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6440 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6434 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6441 (b'', b'print-url', None, _(b'start and print only the URL')),
6435 (b'', b'print-url', None, _(b'start and print only the URL')),
6442 ]
6436 ]
6443 + subrepoopts,
6437 + subrepoopts,
6444 _(b'[OPTION]...'),
6438 _(b'[OPTION]...'),
6445 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6439 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6446 helpbasic=True,
6440 helpbasic=True,
6447 optionalrepo=True,
6441 optionalrepo=True,
6448 )
6442 )
6449 def serve(ui, repo, **opts):
6443 def serve(ui, repo, **opts):
6450 """start stand-alone webserver
6444 """start stand-alone webserver
6451
6445
6452 Start a local HTTP repository browser and pull server. You can use
6446 Start a local HTTP repository browser and pull server. You can use
6453 this for ad-hoc sharing and browsing of repositories. It is
6447 this for ad-hoc sharing and browsing of repositories. It is
6454 recommended to use a real web server to serve a repository for
6448 recommended to use a real web server to serve a repository for
6455 longer periods of time.
6449 longer periods of time.
6456
6450
6457 Please note that the server does not implement access control.
6451 Please note that the server does not implement access control.
6458 This means that, by default, anybody can read from the server and
6452 This means that, by default, anybody can read from the server and
6459 nobody can write to it by default. Set the ``web.allow-push``
6453 nobody can write to it by default. Set the ``web.allow-push``
6460 option to ``*`` to allow everybody to push to the server. You
6454 option to ``*`` to allow everybody to push to the server. You
6461 should use a real web server if you need to authenticate users.
6455 should use a real web server if you need to authenticate users.
6462
6456
6463 By default, the server logs accesses to stdout and errors to
6457 By default, the server logs accesses to stdout and errors to
6464 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6458 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6465 files.
6459 files.
6466
6460
6467 To have the server choose a free port number to listen on, specify
6461 To have the server choose a free port number to listen on, specify
6468 a port number of 0; in this case, the server will print the port
6462 a port number of 0; in this case, the server will print the port
6469 number it uses.
6463 number it uses.
6470
6464
6471 Returns 0 on success.
6465 Returns 0 on success.
6472 """
6466 """
6473
6467
6474 opts = pycompat.byteskwargs(opts)
6468 opts = pycompat.byteskwargs(opts)
6475 if opts[b"stdio"] and opts[b"cmdserver"]:
6469 if opts[b"stdio"] and opts[b"cmdserver"]:
6476 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6470 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6477 if opts[b"print_url"] and ui.verbose:
6471 if opts[b"print_url"] and ui.verbose:
6478 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6472 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6479
6473
6480 if opts[b"stdio"]:
6474 if opts[b"stdio"]:
6481 if repo is None:
6475 if repo is None:
6482 raise error.RepoError(
6476 raise error.RepoError(
6483 _(b"there is no Mercurial repository here (.hg not found)")
6477 _(b"there is no Mercurial repository here (.hg not found)")
6484 )
6478 )
6485 s = wireprotoserver.sshserver(ui, repo)
6479 s = wireprotoserver.sshserver(ui, repo)
6486 s.serve_forever()
6480 s.serve_forever()
6487
6481
6488 service = server.createservice(ui, repo, opts)
6482 service = server.createservice(ui, repo, opts)
6489 return server.runservice(opts, initfn=service.init, runfn=service.run)
6483 return server.runservice(opts, initfn=service.init, runfn=service.run)
6490
6484
6491
6485
6492 @command(
6486 @command(
6493 b'shelve',
6487 b'shelve',
6494 [
6488 [
6495 (
6489 (
6496 b'A',
6490 b'A',
6497 b'addremove',
6491 b'addremove',
6498 None,
6492 None,
6499 _(b'mark new/missing files as added/removed before shelving'),
6493 _(b'mark new/missing files as added/removed before shelving'),
6500 ),
6494 ),
6501 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6495 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6502 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6496 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6503 (
6497 (
6504 b'',
6498 b'',
6505 b'date',
6499 b'date',
6506 b'',
6500 b'',
6507 _(b'shelve with the specified commit date'),
6501 _(b'shelve with the specified commit date'),
6508 _(b'DATE'),
6502 _(b'DATE'),
6509 ),
6503 ),
6510 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6504 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6511 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6505 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6512 (
6506 (
6513 b'k',
6507 b'k',
6514 b'keep',
6508 b'keep',
6515 False,
6509 False,
6516 _(b'shelve, but keep changes in the working directory'),
6510 _(b'shelve, but keep changes in the working directory'),
6517 ),
6511 ),
6518 (b'l', b'list', None, _(b'list current shelves')),
6512 (b'l', b'list', None, _(b'list current shelves')),
6519 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6513 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6520 (
6514 (
6521 b'n',
6515 b'n',
6522 b'name',
6516 b'name',
6523 b'',
6517 b'',
6524 _(b'use the given name for the shelved commit'),
6518 _(b'use the given name for the shelved commit'),
6525 _(b'NAME'),
6519 _(b'NAME'),
6526 ),
6520 ),
6527 (
6521 (
6528 b'p',
6522 b'p',
6529 b'patch',
6523 b'patch',
6530 None,
6524 None,
6531 _(
6525 _(
6532 b'output patches for changes (provide the names of the shelved '
6526 b'output patches for changes (provide the names of the shelved '
6533 b'changes as positional arguments)'
6527 b'changes as positional arguments)'
6534 ),
6528 ),
6535 ),
6529 ),
6536 (b'i', b'interactive', None, _(b'interactive mode')),
6530 (b'i', b'interactive', None, _(b'interactive mode')),
6537 (
6531 (
6538 b'',
6532 b'',
6539 b'stat',
6533 b'stat',
6540 None,
6534 None,
6541 _(
6535 _(
6542 b'output diffstat-style summary of changes (provide the names of '
6536 b'output diffstat-style summary of changes (provide the names of '
6543 b'the shelved changes as positional arguments)'
6537 b'the shelved changes as positional arguments)'
6544 ),
6538 ),
6545 ),
6539 ),
6546 ]
6540 ]
6547 + cmdutil.walkopts,
6541 + cmdutil.walkopts,
6548 _(b'hg shelve [OPTION]... [FILE]...'),
6542 _(b'hg shelve [OPTION]... [FILE]...'),
6549 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6543 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6550 )
6544 )
6551 def shelve(ui, repo, *pats, **opts):
6545 def shelve(ui, repo, *pats, **opts):
6552 '''save and set aside changes from the working directory
6546 '''save and set aside changes from the working directory
6553
6547
6554 Shelving takes files that "hg status" reports as not clean, saves
6548 Shelving takes files that "hg status" reports as not clean, saves
6555 the modifications to a bundle (a shelved change), and reverts the
6549 the modifications to a bundle (a shelved change), and reverts the
6556 files so that their state in the working directory becomes clean.
6550 files so that their state in the working directory becomes clean.
6557
6551
6558 To restore these changes to the working directory, using "hg
6552 To restore these changes to the working directory, using "hg
6559 unshelve"; this will work even if you switch to a different
6553 unshelve"; this will work even if you switch to a different
6560 commit.
6554 commit.
6561
6555
6562 When no files are specified, "hg shelve" saves all not-clean
6556 When no files are specified, "hg shelve" saves all not-clean
6563 files. If specific files or directories are named, only changes to
6557 files. If specific files or directories are named, only changes to
6564 those files are shelved.
6558 those files are shelved.
6565
6559
6566 In bare shelve (when no files are specified, without interactive,
6560 In bare shelve (when no files are specified, without interactive,
6567 include and exclude option), shelving remembers information if the
6561 include and exclude option), shelving remembers information if the
6568 working directory was on newly created branch, in other words working
6562 working directory was on newly created branch, in other words working
6569 directory was on different branch than its first parent. In this
6563 directory was on different branch than its first parent. In this
6570 situation unshelving restores branch information to the working directory.
6564 situation unshelving restores branch information to the working directory.
6571
6565
6572 Each shelved change has a name that makes it easier to find later.
6566 Each shelved change has a name that makes it easier to find later.
6573 The name of a shelved change defaults to being based on the active
6567 The name of a shelved change defaults to being based on the active
6574 bookmark, or if there is no active bookmark, the current named
6568 bookmark, or if there is no active bookmark, the current named
6575 branch. To specify a different name, use ``--name``.
6569 branch. To specify a different name, use ``--name``.
6576
6570
6577 To see a list of existing shelved changes, use the ``--list``
6571 To see a list of existing shelved changes, use the ``--list``
6578 option. For each shelved change, this will print its name, age,
6572 option. For each shelved change, this will print its name, age,
6579 and description; use ``--patch`` or ``--stat`` for more details.
6573 and description; use ``--patch`` or ``--stat`` for more details.
6580
6574
6581 To delete specific shelved changes, use ``--delete``. To delete
6575 To delete specific shelved changes, use ``--delete``. To delete
6582 all shelved changes, use ``--cleanup``.
6576 all shelved changes, use ``--cleanup``.
6583 '''
6577 '''
6584 opts = pycompat.byteskwargs(opts)
6578 opts = pycompat.byteskwargs(opts)
6585 allowables = [
6579 allowables = [
6586 (b'addremove', {b'create'}), # 'create' is pseudo action
6580 (b'addremove', {b'create'}), # 'create' is pseudo action
6587 (b'unknown', {b'create'}),
6581 (b'unknown', {b'create'}),
6588 (b'cleanup', {b'cleanup'}),
6582 (b'cleanup', {b'cleanup'}),
6589 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6583 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6590 (b'delete', {b'delete'}),
6584 (b'delete', {b'delete'}),
6591 (b'edit', {b'create'}),
6585 (b'edit', {b'create'}),
6592 (b'keep', {b'create'}),
6586 (b'keep', {b'create'}),
6593 (b'list', {b'list'}),
6587 (b'list', {b'list'}),
6594 (b'message', {b'create'}),
6588 (b'message', {b'create'}),
6595 (b'name', {b'create'}),
6589 (b'name', {b'create'}),
6596 (b'patch', {b'patch', b'list'}),
6590 (b'patch', {b'patch', b'list'}),
6597 (b'stat', {b'stat', b'list'}),
6591 (b'stat', {b'stat', b'list'}),
6598 ]
6592 ]
6599
6593
6600 def checkopt(opt):
6594 def checkopt(opt):
6601 if opts.get(opt):
6595 if opts.get(opt):
6602 for i, allowable in allowables:
6596 for i, allowable in allowables:
6603 if opts[i] and opt not in allowable:
6597 if opts[i] and opt not in allowable:
6604 raise error.Abort(
6598 raise error.Abort(
6605 _(
6599 _(
6606 b"options '--%s' and '--%s' may not be "
6600 b"options '--%s' and '--%s' may not be "
6607 b"used together"
6601 b"used together"
6608 )
6602 )
6609 % (opt, i)
6603 % (opt, i)
6610 )
6604 )
6611 return True
6605 return True
6612
6606
6613 if checkopt(b'cleanup'):
6607 if checkopt(b'cleanup'):
6614 if pats:
6608 if pats:
6615 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6609 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6616 return shelvemod.cleanupcmd(ui, repo)
6610 return shelvemod.cleanupcmd(ui, repo)
6617 elif checkopt(b'delete'):
6611 elif checkopt(b'delete'):
6618 return shelvemod.deletecmd(ui, repo, pats)
6612 return shelvemod.deletecmd(ui, repo, pats)
6619 elif checkopt(b'list'):
6613 elif checkopt(b'list'):
6620 return shelvemod.listcmd(ui, repo, pats, opts)
6614 return shelvemod.listcmd(ui, repo, pats, opts)
6621 elif checkopt(b'patch') or checkopt(b'stat'):
6615 elif checkopt(b'patch') or checkopt(b'stat'):
6622 return shelvemod.patchcmds(ui, repo, pats, opts)
6616 return shelvemod.patchcmds(ui, repo, pats, opts)
6623 else:
6617 else:
6624 return shelvemod.createcmd(ui, repo, pats, opts)
6618 return shelvemod.createcmd(ui, repo, pats, opts)
6625
6619
6626
6620
6627 _NOTTERSE = b'nothing'
6621 _NOTTERSE = b'nothing'
6628
6622
6629
6623
6630 @command(
6624 @command(
6631 b'status|st',
6625 b'status|st',
6632 [
6626 [
6633 (b'A', b'all', None, _(b'show status of all files')),
6627 (b'A', b'all', None, _(b'show status of all files')),
6634 (b'm', b'modified', None, _(b'show only modified files')),
6628 (b'm', b'modified', None, _(b'show only modified files')),
6635 (b'a', b'added', None, _(b'show only added files')),
6629 (b'a', b'added', None, _(b'show only added files')),
6636 (b'r', b'removed', None, _(b'show only removed files')),
6630 (b'r', b'removed', None, _(b'show only removed files')),
6637 (b'd', b'deleted', None, _(b'show only missing files')),
6631 (b'd', b'deleted', None, _(b'show only missing files')),
6638 (b'c', b'clean', None, _(b'show only files without changes')),
6632 (b'c', b'clean', None, _(b'show only files without changes')),
6639 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6633 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6640 (b'i', b'ignored', None, _(b'show only ignored files')),
6634 (b'i', b'ignored', None, _(b'show only ignored files')),
6641 (b'n', b'no-status', None, _(b'hide status prefix')),
6635 (b'n', b'no-status', None, _(b'hide status prefix')),
6642 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6636 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6643 (
6637 (
6644 b'C',
6638 b'C',
6645 b'copies',
6639 b'copies',
6646 None,
6640 None,
6647 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6641 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6648 ),
6642 ),
6649 (
6643 (
6650 b'0',
6644 b'0',
6651 b'print0',
6645 b'print0',
6652 None,
6646 None,
6653 _(b'end filenames with NUL, for use with xargs'),
6647 _(b'end filenames with NUL, for use with xargs'),
6654 ),
6648 ),
6655 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6649 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6656 (
6650 (
6657 b'',
6651 b'',
6658 b'change',
6652 b'change',
6659 b'',
6653 b'',
6660 _(b'list the changed files of a revision'),
6654 _(b'list the changed files of a revision'),
6661 _(b'REV'),
6655 _(b'REV'),
6662 ),
6656 ),
6663 ]
6657 ]
6664 + walkopts
6658 + walkopts
6665 + subrepoopts
6659 + subrepoopts
6666 + formatteropts,
6660 + formatteropts,
6667 _(b'[OPTION]... [FILE]...'),
6661 _(b'[OPTION]... [FILE]...'),
6668 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6662 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6669 helpbasic=True,
6663 helpbasic=True,
6670 inferrepo=True,
6664 inferrepo=True,
6671 intents={INTENT_READONLY},
6665 intents={INTENT_READONLY},
6672 )
6666 )
6673 def status(ui, repo, *pats, **opts):
6667 def status(ui, repo, *pats, **opts):
6674 """show changed files in the working directory
6668 """show changed files in the working directory
6675
6669
6676 Show status of files in the repository. If names are given, only
6670 Show status of files in the repository. If names are given, only
6677 files that match are shown. Files that are clean or ignored or
6671 files that match are shown. Files that are clean or ignored or
6678 the source of a copy/move operation, are not listed unless
6672 the source of a copy/move operation, are not listed unless
6679 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6673 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6680 Unless options described with "show only ..." are given, the
6674 Unless options described with "show only ..." are given, the
6681 options -mardu are used.
6675 options -mardu are used.
6682
6676
6683 Option -q/--quiet hides untracked (unknown and ignored) files
6677 Option -q/--quiet hides untracked (unknown and ignored) files
6684 unless explicitly requested with -u/--unknown or -i/--ignored.
6678 unless explicitly requested with -u/--unknown or -i/--ignored.
6685
6679
6686 .. note::
6680 .. note::
6687
6681
6688 :hg:`status` may appear to disagree with diff if permissions have
6682 :hg:`status` may appear to disagree with diff if permissions have
6689 changed or a merge has occurred. The standard diff format does
6683 changed or a merge has occurred. The standard diff format does
6690 not report permission changes and diff only reports changes
6684 not report permission changes and diff only reports changes
6691 relative to one merge parent.
6685 relative to one merge parent.
6692
6686
6693 If one revision is given, it is used as the base revision.
6687 If one revision is given, it is used as the base revision.
6694 If two revisions are given, the differences between them are
6688 If two revisions are given, the differences between them are
6695 shown. The --change option can also be used as a shortcut to list
6689 shown. The --change option can also be used as a shortcut to list
6696 the changed files of a revision from its first parent.
6690 the changed files of a revision from its first parent.
6697
6691
6698 The codes used to show the status of files are::
6692 The codes used to show the status of files are::
6699
6693
6700 M = modified
6694 M = modified
6701 A = added
6695 A = added
6702 R = removed
6696 R = removed
6703 C = clean
6697 C = clean
6704 ! = missing (deleted by non-hg command, but still tracked)
6698 ! = missing (deleted by non-hg command, but still tracked)
6705 ? = not tracked
6699 ? = not tracked
6706 I = ignored
6700 I = ignored
6707 = origin of the previous file (with --copies)
6701 = origin of the previous file (with --copies)
6708
6702
6709 .. container:: verbose
6703 .. container:: verbose
6710
6704
6711 The -t/--terse option abbreviates the output by showing only the directory
6705 The -t/--terse option abbreviates the output by showing only the directory
6712 name if all the files in it share the same status. The option takes an
6706 name if all the files in it share the same status. The option takes an
6713 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6707 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6714 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6708 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6715 for 'ignored' and 'c' for clean.
6709 for 'ignored' and 'c' for clean.
6716
6710
6717 It abbreviates only those statuses which are passed. Note that clean and
6711 It abbreviates only those statuses which are passed. Note that clean and
6718 ignored files are not displayed with '--terse ic' unless the -c/--clean
6712 ignored files are not displayed with '--terse ic' unless the -c/--clean
6719 and -i/--ignored options are also used.
6713 and -i/--ignored options are also used.
6720
6714
6721 The -v/--verbose option shows information when the repository is in an
6715 The -v/--verbose option shows information when the repository is in an
6722 unfinished merge, shelve, rebase state etc. You can have this behavior
6716 unfinished merge, shelve, rebase state etc. You can have this behavior
6723 turned on by default by enabling the ``commands.status.verbose`` option.
6717 turned on by default by enabling the ``commands.status.verbose`` option.
6724
6718
6725 You can skip displaying some of these states by setting
6719 You can skip displaying some of these states by setting
6726 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6720 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6727 'histedit', 'merge', 'rebase', or 'unshelve'.
6721 'histedit', 'merge', 'rebase', or 'unshelve'.
6728
6722
6729 Template:
6723 Template:
6730
6724
6731 The following keywords are supported in addition to the common template
6725 The following keywords are supported in addition to the common template
6732 keywords and functions. See also :hg:`help templates`.
6726 keywords and functions. See also :hg:`help templates`.
6733
6727
6734 :path: String. Repository-absolute path of the file.
6728 :path: String. Repository-absolute path of the file.
6735 :source: String. Repository-absolute path of the file originated from.
6729 :source: String. Repository-absolute path of the file originated from.
6736 Available if ``--copies`` is specified.
6730 Available if ``--copies`` is specified.
6737 :status: String. Character denoting file's status.
6731 :status: String. Character denoting file's status.
6738
6732
6739 Examples:
6733 Examples:
6740
6734
6741 - show changes in the working directory relative to a
6735 - show changes in the working directory relative to a
6742 changeset::
6736 changeset::
6743
6737
6744 hg status --rev 9353
6738 hg status --rev 9353
6745
6739
6746 - show changes in the working directory relative to the
6740 - show changes in the working directory relative to the
6747 current directory (see :hg:`help patterns` for more information)::
6741 current directory (see :hg:`help patterns` for more information)::
6748
6742
6749 hg status re:
6743 hg status re:
6750
6744
6751 - show all changes including copies in an existing changeset::
6745 - show all changes including copies in an existing changeset::
6752
6746
6753 hg status --copies --change 9353
6747 hg status --copies --change 9353
6754
6748
6755 - get a NUL separated list of added files, suitable for xargs::
6749 - get a NUL separated list of added files, suitable for xargs::
6756
6750
6757 hg status -an0
6751 hg status -an0
6758
6752
6759 - show more information about the repository status, abbreviating
6753 - show more information about the repository status, abbreviating
6760 added, removed, modified, deleted, and untracked paths::
6754 added, removed, modified, deleted, and untracked paths::
6761
6755
6762 hg status -v -t mardu
6756 hg status -v -t mardu
6763
6757
6764 Returns 0 on success.
6758 Returns 0 on success.
6765
6759
6766 """
6760 """
6767
6761
6768 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6762 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6769 opts = pycompat.byteskwargs(opts)
6763 opts = pycompat.byteskwargs(opts)
6770 revs = opts.get(b'rev')
6764 revs = opts.get(b'rev')
6771 change = opts.get(b'change')
6765 change = opts.get(b'change')
6772 terse = opts.get(b'terse')
6766 terse = opts.get(b'terse')
6773 if terse is _NOTTERSE:
6767 if terse is _NOTTERSE:
6774 if revs:
6768 if revs:
6775 terse = b''
6769 terse = b''
6776 else:
6770 else:
6777 terse = ui.config(b'commands', b'status.terse')
6771 terse = ui.config(b'commands', b'status.terse')
6778
6772
6779 if revs and terse:
6773 if revs and terse:
6780 msg = _(b'cannot use --terse with --rev')
6774 msg = _(b'cannot use --terse with --rev')
6781 raise error.Abort(msg)
6775 raise error.Abort(msg)
6782 elif change:
6776 elif change:
6783 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6777 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6784 ctx2 = scmutil.revsingle(repo, change, None)
6778 ctx2 = scmutil.revsingle(repo, change, None)
6785 ctx1 = ctx2.p1()
6779 ctx1 = ctx2.p1()
6786 else:
6780 else:
6787 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6781 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6788 ctx1, ctx2 = scmutil.revpair(repo, revs)
6782 ctx1, ctx2 = scmutil.revpair(repo, revs)
6789
6783
6790 forcerelativevalue = None
6784 forcerelativevalue = None
6791 if ui.hasconfig(b'commands', b'status.relative'):
6785 if ui.hasconfig(b'commands', b'status.relative'):
6792 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6786 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6793 uipathfn = scmutil.getuipathfn(
6787 uipathfn = scmutil.getuipathfn(
6794 repo,
6788 repo,
6795 legacyrelativevalue=bool(pats),
6789 legacyrelativevalue=bool(pats),
6796 forcerelativevalue=forcerelativevalue,
6790 forcerelativevalue=forcerelativevalue,
6797 )
6791 )
6798
6792
6799 if opts.get(b'print0'):
6793 if opts.get(b'print0'):
6800 end = b'\0'
6794 end = b'\0'
6801 else:
6795 else:
6802 end = b'\n'
6796 end = b'\n'
6803 states = b'modified added removed deleted unknown ignored clean'.split()
6797 states = b'modified added removed deleted unknown ignored clean'.split()
6804 show = [k for k in states if opts.get(k)]
6798 show = [k for k in states if opts.get(k)]
6805 if opts.get(b'all'):
6799 if opts.get(b'all'):
6806 show += ui.quiet and (states[:4] + [b'clean']) or states
6800 show += ui.quiet and (states[:4] + [b'clean']) or states
6807
6801
6808 if not show:
6802 if not show:
6809 if ui.quiet:
6803 if ui.quiet:
6810 show = states[:4]
6804 show = states[:4]
6811 else:
6805 else:
6812 show = states[:5]
6806 show = states[:5]
6813
6807
6814 m = scmutil.match(ctx2, pats, opts)
6808 m = scmutil.match(ctx2, pats, opts)
6815 if terse:
6809 if terse:
6816 # we need to compute clean and unknown to terse
6810 # we need to compute clean and unknown to terse
6817 stat = repo.status(
6811 stat = repo.status(
6818 ctx1.node(),
6812 ctx1.node(),
6819 ctx2.node(),
6813 ctx2.node(),
6820 m,
6814 m,
6821 b'ignored' in show or b'i' in terse,
6815 b'ignored' in show or b'i' in terse,
6822 clean=True,
6816 clean=True,
6823 unknown=True,
6817 unknown=True,
6824 listsubrepos=opts.get(b'subrepos'),
6818 listsubrepos=opts.get(b'subrepos'),
6825 )
6819 )
6826
6820
6827 stat = cmdutil.tersedir(stat, terse)
6821 stat = cmdutil.tersedir(stat, terse)
6828 else:
6822 else:
6829 stat = repo.status(
6823 stat = repo.status(
6830 ctx1.node(),
6824 ctx1.node(),
6831 ctx2.node(),
6825 ctx2.node(),
6832 m,
6826 m,
6833 b'ignored' in show,
6827 b'ignored' in show,
6834 b'clean' in show,
6828 b'clean' in show,
6835 b'unknown' in show,
6829 b'unknown' in show,
6836 opts.get(b'subrepos'),
6830 opts.get(b'subrepos'),
6837 )
6831 )
6838
6832
6839 changestates = zip(
6833 changestates = zip(
6840 states,
6834 states,
6841 pycompat.iterbytestr(b'MAR!?IC'),
6835 pycompat.iterbytestr(b'MAR!?IC'),
6842 [getattr(stat, s.decode('utf8')) for s in states],
6836 [getattr(stat, s.decode('utf8')) for s in states],
6843 )
6837 )
6844
6838
6845 copy = {}
6839 copy = {}
6846 if (
6840 if (
6847 opts.get(b'all')
6841 opts.get(b'all')
6848 or opts.get(b'copies')
6842 or opts.get(b'copies')
6849 or ui.configbool(b'ui', b'statuscopies')
6843 or ui.configbool(b'ui', b'statuscopies')
6850 ) and not opts.get(b'no_status'):
6844 ) and not opts.get(b'no_status'):
6851 copy = copies.pathcopies(ctx1, ctx2, m)
6845 copy = copies.pathcopies(ctx1, ctx2, m)
6852
6846
6853 morestatus = None
6847 morestatus = None
6854 if (
6848 if (
6855 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6849 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6856 ) and not ui.plain():
6850 ) and not ui.plain():
6857 morestatus = cmdutil.readmorestatus(repo)
6851 morestatus = cmdutil.readmorestatus(repo)
6858
6852
6859 ui.pager(b'status')
6853 ui.pager(b'status')
6860 fm = ui.formatter(b'status', opts)
6854 fm = ui.formatter(b'status', opts)
6861 fmt = b'%s' + end
6855 fmt = b'%s' + end
6862 showchar = not opts.get(b'no_status')
6856 showchar = not opts.get(b'no_status')
6863
6857
6864 for state, char, files in changestates:
6858 for state, char, files in changestates:
6865 if state in show:
6859 if state in show:
6866 label = b'status.' + state
6860 label = b'status.' + state
6867 for f in files:
6861 for f in files:
6868 fm.startitem()
6862 fm.startitem()
6869 fm.context(ctx=ctx2)
6863 fm.context(ctx=ctx2)
6870 fm.data(itemtype=b'file', path=f)
6864 fm.data(itemtype=b'file', path=f)
6871 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6865 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6872 fm.plain(fmt % uipathfn(f), label=label)
6866 fm.plain(fmt % uipathfn(f), label=label)
6873 if f in copy:
6867 if f in copy:
6874 fm.data(source=copy[f])
6868 fm.data(source=copy[f])
6875 fm.plain(
6869 fm.plain(
6876 (b' %s' + end) % uipathfn(copy[f]),
6870 (b' %s' + end) % uipathfn(copy[f]),
6877 label=b'status.copied',
6871 label=b'status.copied',
6878 )
6872 )
6879 if morestatus:
6873 if morestatus:
6880 morestatus.formatfile(f, fm)
6874 morestatus.formatfile(f, fm)
6881
6875
6882 if morestatus:
6876 if morestatus:
6883 morestatus.formatfooter(fm)
6877 morestatus.formatfooter(fm)
6884 fm.end()
6878 fm.end()
6885
6879
6886
6880
6887 @command(
6881 @command(
6888 b'summary|sum',
6882 b'summary|sum',
6889 [(b'', b'remote', None, _(b'check for push and pull'))],
6883 [(b'', b'remote', None, _(b'check for push and pull'))],
6890 b'[--remote]',
6884 b'[--remote]',
6891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6885 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6892 helpbasic=True,
6886 helpbasic=True,
6893 intents={INTENT_READONLY},
6887 intents={INTENT_READONLY},
6894 )
6888 )
6895 def summary(ui, repo, **opts):
6889 def summary(ui, repo, **opts):
6896 """summarize working directory state
6890 """summarize working directory state
6897
6891
6898 This generates a brief summary of the working directory state,
6892 This generates a brief summary of the working directory state,
6899 including parents, branch, commit status, phase and available updates.
6893 including parents, branch, commit status, phase and available updates.
6900
6894
6901 With the --remote option, this will check the default paths for
6895 With the --remote option, this will check the default paths for
6902 incoming and outgoing changes. This can be time-consuming.
6896 incoming and outgoing changes. This can be time-consuming.
6903
6897
6904 Returns 0 on success.
6898 Returns 0 on success.
6905 """
6899 """
6906
6900
6907 opts = pycompat.byteskwargs(opts)
6901 opts = pycompat.byteskwargs(opts)
6908 ui.pager(b'summary')
6902 ui.pager(b'summary')
6909 ctx = repo[None]
6903 ctx = repo[None]
6910 parents = ctx.parents()
6904 parents = ctx.parents()
6911 pnode = parents[0].node()
6905 pnode = parents[0].node()
6912 marks = []
6906 marks = []
6913
6907
6914 try:
6908 try:
6915 ms = mergestatemod.mergestate.read(repo)
6909 ms = mergestatemod.mergestate.read(repo)
6916 except error.UnsupportedMergeRecords as e:
6910 except error.UnsupportedMergeRecords as e:
6917 s = b' '.join(e.recordtypes)
6911 s = b' '.join(e.recordtypes)
6918 ui.warn(
6912 ui.warn(
6919 _(b'warning: merge state has unsupported record types: %s\n') % s
6913 _(b'warning: merge state has unsupported record types: %s\n') % s
6920 )
6914 )
6921 unresolved = []
6915 unresolved = []
6922 else:
6916 else:
6923 unresolved = list(ms.unresolved())
6917 unresolved = list(ms.unresolved())
6924
6918
6925 for p in parents:
6919 for p in parents:
6926 # label with log.changeset (instead of log.parent) since this
6920 # label with log.changeset (instead of log.parent) since this
6927 # shows a working directory parent *changeset*:
6921 # shows a working directory parent *changeset*:
6928 # i18n: column positioning for "hg summary"
6922 # i18n: column positioning for "hg summary"
6929 ui.write(
6923 ui.write(
6930 _(b'parent: %d:%s ') % (p.rev(), p),
6924 _(b'parent: %d:%s ') % (p.rev(), p),
6931 label=logcmdutil.changesetlabels(p),
6925 label=logcmdutil.changesetlabels(p),
6932 )
6926 )
6933 ui.write(b' '.join(p.tags()), label=b'log.tag')
6927 ui.write(b' '.join(p.tags()), label=b'log.tag')
6934 if p.bookmarks():
6928 if p.bookmarks():
6935 marks.extend(p.bookmarks())
6929 marks.extend(p.bookmarks())
6936 if p.rev() == -1:
6930 if p.rev() == -1:
6937 if not len(repo):
6931 if not len(repo):
6938 ui.write(_(b' (empty repository)'))
6932 ui.write(_(b' (empty repository)'))
6939 else:
6933 else:
6940 ui.write(_(b' (no revision checked out)'))
6934 ui.write(_(b' (no revision checked out)'))
6941 if p.obsolete():
6935 if p.obsolete():
6942 ui.write(_(b' (obsolete)'))
6936 ui.write(_(b' (obsolete)'))
6943 if p.isunstable():
6937 if p.isunstable():
6944 instabilities = (
6938 instabilities = (
6945 ui.label(instability, b'trouble.%s' % instability)
6939 ui.label(instability, b'trouble.%s' % instability)
6946 for instability in p.instabilities()
6940 for instability in p.instabilities()
6947 )
6941 )
6948 ui.write(b' (' + b', '.join(instabilities) + b')')
6942 ui.write(b' (' + b', '.join(instabilities) + b')')
6949 ui.write(b'\n')
6943 ui.write(b'\n')
6950 if p.description():
6944 if p.description():
6951 ui.status(
6945 ui.status(
6952 b' ' + p.description().splitlines()[0].strip() + b'\n',
6946 b' ' + p.description().splitlines()[0].strip() + b'\n',
6953 label=b'log.summary',
6947 label=b'log.summary',
6954 )
6948 )
6955
6949
6956 branch = ctx.branch()
6950 branch = ctx.branch()
6957 bheads = repo.branchheads(branch)
6951 bheads = repo.branchheads(branch)
6958 # i18n: column positioning for "hg summary"
6952 # i18n: column positioning for "hg summary"
6959 m = _(b'branch: %s\n') % branch
6953 m = _(b'branch: %s\n') % branch
6960 if branch != b'default':
6954 if branch != b'default':
6961 ui.write(m, label=b'log.branch')
6955 ui.write(m, label=b'log.branch')
6962 else:
6956 else:
6963 ui.status(m, label=b'log.branch')
6957 ui.status(m, label=b'log.branch')
6964
6958
6965 if marks:
6959 if marks:
6966 active = repo._activebookmark
6960 active = repo._activebookmark
6967 # i18n: column positioning for "hg summary"
6961 # i18n: column positioning for "hg summary"
6968 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6962 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6969 if active is not None:
6963 if active is not None:
6970 if active in marks:
6964 if active in marks:
6971 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6965 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6972 marks.remove(active)
6966 marks.remove(active)
6973 else:
6967 else:
6974 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6968 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6975 for m in marks:
6969 for m in marks:
6976 ui.write(b' ' + m, label=b'log.bookmark')
6970 ui.write(b' ' + m, label=b'log.bookmark')
6977 ui.write(b'\n', label=b'log.bookmark')
6971 ui.write(b'\n', label=b'log.bookmark')
6978
6972
6979 status = repo.status(unknown=True)
6973 status = repo.status(unknown=True)
6980
6974
6981 c = repo.dirstate.copies()
6975 c = repo.dirstate.copies()
6982 copied, renamed = [], []
6976 copied, renamed = [], []
6983 for d, s in pycompat.iteritems(c):
6977 for d, s in pycompat.iteritems(c):
6984 if s in status.removed:
6978 if s in status.removed:
6985 status.removed.remove(s)
6979 status.removed.remove(s)
6986 renamed.append(d)
6980 renamed.append(d)
6987 else:
6981 else:
6988 copied.append(d)
6982 copied.append(d)
6989 if d in status.added:
6983 if d in status.added:
6990 status.added.remove(d)
6984 status.added.remove(d)
6991
6985
6992 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6986 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6993
6987
6994 labels = [
6988 labels = [
6995 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6989 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6996 (ui.label(_(b'%d added'), b'status.added'), status.added),
6990 (ui.label(_(b'%d added'), b'status.added'), status.added),
6997 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6991 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6998 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6992 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6999 (ui.label(_(b'%d copied'), b'status.copied'), copied),
6993 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7000 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
6994 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7001 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
6995 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7002 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
6996 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7003 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
6997 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7004 ]
6998 ]
7005 t = []
6999 t = []
7006 for l, s in labels:
7000 for l, s in labels:
7007 if s:
7001 if s:
7008 t.append(l % len(s))
7002 t.append(l % len(s))
7009
7003
7010 t = b', '.join(t)
7004 t = b', '.join(t)
7011 cleanworkdir = False
7005 cleanworkdir = False
7012
7006
7013 if repo.vfs.exists(b'graftstate'):
7007 if repo.vfs.exists(b'graftstate'):
7014 t += _(b' (graft in progress)')
7008 t += _(b' (graft in progress)')
7015 if repo.vfs.exists(b'updatestate'):
7009 if repo.vfs.exists(b'updatestate'):
7016 t += _(b' (interrupted update)')
7010 t += _(b' (interrupted update)')
7017 elif len(parents) > 1:
7011 elif len(parents) > 1:
7018 t += _(b' (merge)')
7012 t += _(b' (merge)')
7019 elif branch != parents[0].branch():
7013 elif branch != parents[0].branch():
7020 t += _(b' (new branch)')
7014 t += _(b' (new branch)')
7021 elif parents[0].closesbranch() and pnode in repo.branchheads(
7015 elif parents[0].closesbranch() and pnode in repo.branchheads(
7022 branch, closed=True
7016 branch, closed=True
7023 ):
7017 ):
7024 t += _(b' (head closed)')
7018 t += _(b' (head closed)')
7025 elif not (
7019 elif not (
7026 status.modified
7020 status.modified
7027 or status.added
7021 or status.added
7028 or status.removed
7022 or status.removed
7029 or renamed
7023 or renamed
7030 or copied
7024 or copied
7031 or subs
7025 or subs
7032 ):
7026 ):
7033 t += _(b' (clean)')
7027 t += _(b' (clean)')
7034 cleanworkdir = True
7028 cleanworkdir = True
7035 elif pnode not in bheads:
7029 elif pnode not in bheads:
7036 t += _(b' (new branch head)')
7030 t += _(b' (new branch head)')
7037
7031
7038 if parents:
7032 if parents:
7039 pendingphase = max(p.phase() for p in parents)
7033 pendingphase = max(p.phase() for p in parents)
7040 else:
7034 else:
7041 pendingphase = phases.public
7035 pendingphase = phases.public
7042
7036
7043 if pendingphase > phases.newcommitphase(ui):
7037 if pendingphase > phases.newcommitphase(ui):
7044 t += b' (%s)' % phases.phasenames[pendingphase]
7038 t += b' (%s)' % phases.phasenames[pendingphase]
7045
7039
7046 if cleanworkdir:
7040 if cleanworkdir:
7047 # i18n: column positioning for "hg summary"
7041 # i18n: column positioning for "hg summary"
7048 ui.status(_(b'commit: %s\n') % t.strip())
7042 ui.status(_(b'commit: %s\n') % t.strip())
7049 else:
7043 else:
7050 # i18n: column positioning for "hg summary"
7044 # i18n: column positioning for "hg summary"
7051 ui.write(_(b'commit: %s\n') % t.strip())
7045 ui.write(_(b'commit: %s\n') % t.strip())
7052
7046
7053 # all ancestors of branch heads - all ancestors of parent = new csets
7047 # all ancestors of branch heads - all ancestors of parent = new csets
7054 new = len(
7048 new = len(
7055 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7049 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7056 )
7050 )
7057
7051
7058 if new == 0:
7052 if new == 0:
7059 # i18n: column positioning for "hg summary"
7053 # i18n: column positioning for "hg summary"
7060 ui.status(_(b'update: (current)\n'))
7054 ui.status(_(b'update: (current)\n'))
7061 elif pnode not in bheads:
7055 elif pnode not in bheads:
7062 # i18n: column positioning for "hg summary"
7056 # i18n: column positioning for "hg summary"
7063 ui.write(_(b'update: %d new changesets (update)\n') % new)
7057 ui.write(_(b'update: %d new changesets (update)\n') % new)
7064 else:
7058 else:
7065 # i18n: column positioning for "hg summary"
7059 # i18n: column positioning for "hg summary"
7066 ui.write(
7060 ui.write(
7067 _(b'update: %d new changesets, %d branch heads (merge)\n')
7061 _(b'update: %d new changesets, %d branch heads (merge)\n')
7068 % (new, len(bheads))
7062 % (new, len(bheads))
7069 )
7063 )
7070
7064
7071 t = []
7065 t = []
7072 draft = len(repo.revs(b'draft()'))
7066 draft = len(repo.revs(b'draft()'))
7073 if draft:
7067 if draft:
7074 t.append(_(b'%d draft') % draft)
7068 t.append(_(b'%d draft') % draft)
7075 secret = len(repo.revs(b'secret()'))
7069 secret = len(repo.revs(b'secret()'))
7076 if secret:
7070 if secret:
7077 t.append(_(b'%d secret') % secret)
7071 t.append(_(b'%d secret') % secret)
7078
7072
7079 if draft or secret:
7073 if draft or secret:
7080 ui.status(_(b'phases: %s\n') % b', '.join(t))
7074 ui.status(_(b'phases: %s\n') % b', '.join(t))
7081
7075
7082 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7076 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7083 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7077 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7084 numtrouble = len(repo.revs(trouble + b"()"))
7078 numtrouble = len(repo.revs(trouble + b"()"))
7085 # We write all the possibilities to ease translation
7079 # We write all the possibilities to ease translation
7086 troublemsg = {
7080 troublemsg = {
7087 b"orphan": _(b"orphan: %d changesets"),
7081 b"orphan": _(b"orphan: %d changesets"),
7088 b"contentdivergent": _(b"content-divergent: %d changesets"),
7082 b"contentdivergent": _(b"content-divergent: %d changesets"),
7089 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7083 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7090 }
7084 }
7091 if numtrouble > 0:
7085 if numtrouble > 0:
7092 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7086 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7093
7087
7094 cmdutil.summaryhooks(ui, repo)
7088 cmdutil.summaryhooks(ui, repo)
7095
7089
7096 if opts.get(b'remote'):
7090 if opts.get(b'remote'):
7097 needsincoming, needsoutgoing = True, True
7091 needsincoming, needsoutgoing = True, True
7098 else:
7092 else:
7099 needsincoming, needsoutgoing = False, False
7093 needsincoming, needsoutgoing = False, False
7100 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7094 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7101 if i:
7095 if i:
7102 needsincoming = True
7096 needsincoming = True
7103 if o:
7097 if o:
7104 needsoutgoing = True
7098 needsoutgoing = True
7105 if not needsincoming and not needsoutgoing:
7099 if not needsincoming and not needsoutgoing:
7106 return
7100 return
7107
7101
7108 def getincoming():
7102 def getincoming():
7109 source, branches = hg.parseurl(ui.expandpath(b'default'))
7103 source, branches = hg.parseurl(ui.expandpath(b'default'))
7110 sbranch = branches[0]
7104 sbranch = branches[0]
7111 try:
7105 try:
7112 other = hg.peer(repo, {}, source)
7106 other = hg.peer(repo, {}, source)
7113 except error.RepoError:
7107 except error.RepoError:
7114 if opts.get(b'remote'):
7108 if opts.get(b'remote'):
7115 raise
7109 raise
7116 return source, sbranch, None, None, None
7110 return source, sbranch, None, None, None
7117 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7111 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7118 if revs:
7112 if revs:
7119 revs = [other.lookup(rev) for rev in revs]
7113 revs = [other.lookup(rev) for rev in revs]
7120 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7114 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7121 repo.ui.pushbuffer()
7115 repo.ui.pushbuffer()
7122 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7116 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7123 repo.ui.popbuffer()
7117 repo.ui.popbuffer()
7124 return source, sbranch, other, commoninc, commoninc[1]
7118 return source, sbranch, other, commoninc, commoninc[1]
7125
7119
7126 if needsincoming:
7120 if needsincoming:
7127 source, sbranch, sother, commoninc, incoming = getincoming()
7121 source, sbranch, sother, commoninc, incoming = getincoming()
7128 else:
7122 else:
7129 source = sbranch = sother = commoninc = incoming = None
7123 source = sbranch = sother = commoninc = incoming = None
7130
7124
7131 def getoutgoing():
7125 def getoutgoing():
7132 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7126 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7133 dbranch = branches[0]
7127 dbranch = branches[0]
7134 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7128 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7135 if source != dest:
7129 if source != dest:
7136 try:
7130 try:
7137 dother = hg.peer(repo, {}, dest)
7131 dother = hg.peer(repo, {}, dest)
7138 except error.RepoError:
7132 except error.RepoError:
7139 if opts.get(b'remote'):
7133 if opts.get(b'remote'):
7140 raise
7134 raise
7141 return dest, dbranch, None, None
7135 return dest, dbranch, None, None
7142 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7136 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7143 elif sother is None:
7137 elif sother is None:
7144 # there is no explicit destination peer, but source one is invalid
7138 # there is no explicit destination peer, but source one is invalid
7145 return dest, dbranch, None, None
7139 return dest, dbranch, None, None
7146 else:
7140 else:
7147 dother = sother
7141 dother = sother
7148 if source != dest or (sbranch is not None and sbranch != dbranch):
7142 if source != dest or (sbranch is not None and sbranch != dbranch):
7149 common = None
7143 common = None
7150 else:
7144 else:
7151 common = commoninc
7145 common = commoninc
7152 if revs:
7146 if revs:
7153 revs = [repo.lookup(rev) for rev in revs]
7147 revs = [repo.lookup(rev) for rev in revs]
7154 repo.ui.pushbuffer()
7148 repo.ui.pushbuffer()
7155 outgoing = discovery.findcommonoutgoing(
7149 outgoing = discovery.findcommonoutgoing(
7156 repo, dother, onlyheads=revs, commoninc=common
7150 repo, dother, onlyheads=revs, commoninc=common
7157 )
7151 )
7158 repo.ui.popbuffer()
7152 repo.ui.popbuffer()
7159 return dest, dbranch, dother, outgoing
7153 return dest, dbranch, dother, outgoing
7160
7154
7161 if needsoutgoing:
7155 if needsoutgoing:
7162 dest, dbranch, dother, outgoing = getoutgoing()
7156 dest, dbranch, dother, outgoing = getoutgoing()
7163 else:
7157 else:
7164 dest = dbranch = dother = outgoing = None
7158 dest = dbranch = dother = outgoing = None
7165
7159
7166 if opts.get(b'remote'):
7160 if opts.get(b'remote'):
7167 t = []
7161 t = []
7168 if incoming:
7162 if incoming:
7169 t.append(_(b'1 or more incoming'))
7163 t.append(_(b'1 or more incoming'))
7170 o = outgoing.missing
7164 o = outgoing.missing
7171 if o:
7165 if o:
7172 t.append(_(b'%d outgoing') % len(o))
7166 t.append(_(b'%d outgoing') % len(o))
7173 other = dother or sother
7167 other = dother or sother
7174 if b'bookmarks' in other.listkeys(b'namespaces'):
7168 if b'bookmarks' in other.listkeys(b'namespaces'):
7175 counts = bookmarks.summary(repo, other)
7169 counts = bookmarks.summary(repo, other)
7176 if counts[0] > 0:
7170 if counts[0] > 0:
7177 t.append(_(b'%d incoming bookmarks') % counts[0])
7171 t.append(_(b'%d incoming bookmarks') % counts[0])
7178 if counts[1] > 0:
7172 if counts[1] > 0:
7179 t.append(_(b'%d outgoing bookmarks') % counts[1])
7173 t.append(_(b'%d outgoing bookmarks') % counts[1])
7180
7174
7181 if t:
7175 if t:
7182 # i18n: column positioning for "hg summary"
7176 # i18n: column positioning for "hg summary"
7183 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7177 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7184 else:
7178 else:
7185 # i18n: column positioning for "hg summary"
7179 # i18n: column positioning for "hg summary"
7186 ui.status(_(b'remote: (synced)\n'))
7180 ui.status(_(b'remote: (synced)\n'))
7187
7181
7188 cmdutil.summaryremotehooks(
7182 cmdutil.summaryremotehooks(
7189 ui,
7183 ui,
7190 repo,
7184 repo,
7191 opts,
7185 opts,
7192 (
7186 (
7193 (source, sbranch, sother, commoninc),
7187 (source, sbranch, sother, commoninc),
7194 (dest, dbranch, dother, outgoing),
7188 (dest, dbranch, dother, outgoing),
7195 ),
7189 ),
7196 )
7190 )
7197
7191
7198
7192
7199 @command(
7193 @command(
7200 b'tag',
7194 b'tag',
7201 [
7195 [
7202 (b'f', b'force', None, _(b'force tag')),
7196 (b'f', b'force', None, _(b'force tag')),
7203 (b'l', b'local', None, _(b'make the tag local')),
7197 (b'l', b'local', None, _(b'make the tag local')),
7204 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7198 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7205 (b'', b'remove', None, _(b'remove a tag')),
7199 (b'', b'remove', None, _(b'remove a tag')),
7206 # -l/--local is already there, commitopts cannot be used
7200 # -l/--local is already there, commitopts cannot be used
7207 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7201 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7208 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7202 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7209 ]
7203 ]
7210 + commitopts2,
7204 + commitopts2,
7211 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7205 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7212 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7206 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7213 )
7207 )
7214 def tag(ui, repo, name1, *names, **opts):
7208 def tag(ui, repo, name1, *names, **opts):
7215 """add one or more tags for the current or given revision
7209 """add one or more tags for the current or given revision
7216
7210
7217 Name a particular revision using <name>.
7211 Name a particular revision using <name>.
7218
7212
7219 Tags are used to name particular revisions of the repository and are
7213 Tags are used to name particular revisions of the repository and are
7220 very useful to compare different revisions, to go back to significant
7214 very useful to compare different revisions, to go back to significant
7221 earlier versions or to mark branch points as releases, etc. Changing
7215 earlier versions or to mark branch points as releases, etc. Changing
7222 an existing tag is normally disallowed; use -f/--force to override.
7216 an existing tag is normally disallowed; use -f/--force to override.
7223
7217
7224 If no revision is given, the parent of the working directory is
7218 If no revision is given, the parent of the working directory is
7225 used.
7219 used.
7226
7220
7227 To facilitate version control, distribution, and merging of tags,
7221 To facilitate version control, distribution, and merging of tags,
7228 they are stored as a file named ".hgtags" which is managed similarly
7222 they are stored as a file named ".hgtags" which is managed similarly
7229 to other project files and can be hand-edited if necessary. This
7223 to other project files and can be hand-edited if necessary. This
7230 also means that tagging creates a new commit. The file
7224 also means that tagging creates a new commit. The file
7231 ".hg/localtags" is used for local tags (not shared among
7225 ".hg/localtags" is used for local tags (not shared among
7232 repositories).
7226 repositories).
7233
7227
7234 Tag commits are usually made at the head of a branch. If the parent
7228 Tag commits are usually made at the head of a branch. If the parent
7235 of the working directory is not a branch head, :hg:`tag` aborts; use
7229 of the working directory is not a branch head, :hg:`tag` aborts; use
7236 -f/--force to force the tag commit to be based on a non-head
7230 -f/--force to force the tag commit to be based on a non-head
7237 changeset.
7231 changeset.
7238
7232
7239 See :hg:`help dates` for a list of formats valid for -d/--date.
7233 See :hg:`help dates` for a list of formats valid for -d/--date.
7240
7234
7241 Since tag names have priority over branch names during revision
7235 Since tag names have priority over branch names during revision
7242 lookup, using an existing branch name as a tag name is discouraged.
7236 lookup, using an existing branch name as a tag name is discouraged.
7243
7237
7244 Returns 0 on success.
7238 Returns 0 on success.
7245 """
7239 """
7246 opts = pycompat.byteskwargs(opts)
7240 opts = pycompat.byteskwargs(opts)
7247 with repo.wlock(), repo.lock():
7241 with repo.wlock(), repo.lock():
7248 rev_ = b"."
7242 rev_ = b"."
7249 names = [t.strip() for t in (name1,) + names]
7243 names = [t.strip() for t in (name1,) + names]
7250 if len(names) != len(set(names)):
7244 if len(names) != len(set(names)):
7251 raise error.Abort(_(b'tag names must be unique'))
7245 raise error.Abort(_(b'tag names must be unique'))
7252 for n in names:
7246 for n in names:
7253 scmutil.checknewlabel(repo, n, b'tag')
7247 scmutil.checknewlabel(repo, n, b'tag')
7254 if not n:
7248 if not n:
7255 raise error.Abort(
7249 raise error.Abort(
7256 _(b'tag names cannot consist entirely of whitespace')
7250 _(b'tag names cannot consist entirely of whitespace')
7257 )
7251 )
7258 if opts.get(b'rev') and opts.get(b'remove'):
7252 if opts.get(b'rev') and opts.get(b'remove'):
7259 raise error.Abort(_(b"--rev and --remove are incompatible"))
7253 raise error.Abort(_(b"--rev and --remove are incompatible"))
7260 if opts.get(b'rev'):
7254 if opts.get(b'rev'):
7261 rev_ = opts[b'rev']
7255 rev_ = opts[b'rev']
7262 message = opts.get(b'message')
7256 message = opts.get(b'message')
7263 if opts.get(b'remove'):
7257 if opts.get(b'remove'):
7264 if opts.get(b'local'):
7258 if opts.get(b'local'):
7265 expectedtype = b'local'
7259 expectedtype = b'local'
7266 else:
7260 else:
7267 expectedtype = b'global'
7261 expectedtype = b'global'
7268
7262
7269 for n in names:
7263 for n in names:
7270 if repo.tagtype(n) == b'global':
7264 if repo.tagtype(n) == b'global':
7271 alltags = tagsmod.findglobaltags(ui, repo)
7265 alltags = tagsmod.findglobaltags(ui, repo)
7272 if alltags[n][0] == nullid:
7266 if alltags[n][0] == nullid:
7273 raise error.Abort(_(b"tag '%s' is already removed") % n)
7267 raise error.Abort(_(b"tag '%s' is already removed") % n)
7274 if not repo.tagtype(n):
7268 if not repo.tagtype(n):
7275 raise error.Abort(_(b"tag '%s' does not exist") % n)
7269 raise error.Abort(_(b"tag '%s' does not exist") % n)
7276 if repo.tagtype(n) != expectedtype:
7270 if repo.tagtype(n) != expectedtype:
7277 if expectedtype == b'global':
7271 if expectedtype == b'global':
7278 raise error.Abort(
7272 raise error.Abort(
7279 _(b"tag '%s' is not a global tag") % n
7273 _(b"tag '%s' is not a global tag") % n
7280 )
7274 )
7281 else:
7275 else:
7282 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7276 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7283 rev_ = b'null'
7277 rev_ = b'null'
7284 if not message:
7278 if not message:
7285 # we don't translate commit messages
7279 # we don't translate commit messages
7286 message = b'Removed tag %s' % b', '.join(names)
7280 message = b'Removed tag %s' % b', '.join(names)
7287 elif not opts.get(b'force'):
7281 elif not opts.get(b'force'):
7288 for n in names:
7282 for n in names:
7289 if n in repo.tags():
7283 if n in repo.tags():
7290 raise error.Abort(
7284 raise error.Abort(
7291 _(b"tag '%s' already exists (use -f to force)") % n
7285 _(b"tag '%s' already exists (use -f to force)") % n
7292 )
7286 )
7293 if not opts.get(b'local'):
7287 if not opts.get(b'local'):
7294 p1, p2 = repo.dirstate.parents()
7288 p1, p2 = repo.dirstate.parents()
7295 if p2 != nullid:
7289 if p2 != nullid:
7296 raise error.Abort(_(b'uncommitted merge'))
7290 raise error.Abort(_(b'uncommitted merge'))
7297 bheads = repo.branchheads()
7291 bheads = repo.branchheads()
7298 if not opts.get(b'force') and bheads and p1 not in bheads:
7292 if not opts.get(b'force') and bheads and p1 not in bheads:
7299 raise error.Abort(
7293 raise error.Abort(
7300 _(
7294 _(
7301 b'working directory is not at a branch head '
7295 b'working directory is not at a branch head '
7302 b'(use -f to force)'
7296 b'(use -f to force)'
7303 )
7297 )
7304 )
7298 )
7305 node = scmutil.revsingle(repo, rev_).node()
7299 node = scmutil.revsingle(repo, rev_).node()
7306
7300
7307 if not message:
7301 if not message:
7308 # we don't translate commit messages
7302 # we don't translate commit messages
7309 message = b'Added tag %s for changeset %s' % (
7303 message = b'Added tag %s for changeset %s' % (
7310 b', '.join(names),
7304 b', '.join(names),
7311 short(node),
7305 short(node),
7312 )
7306 )
7313
7307
7314 date = opts.get(b'date')
7308 date = opts.get(b'date')
7315 if date:
7309 if date:
7316 date = dateutil.parsedate(date)
7310 date = dateutil.parsedate(date)
7317
7311
7318 if opts.get(b'remove'):
7312 if opts.get(b'remove'):
7319 editform = b'tag.remove'
7313 editform = b'tag.remove'
7320 else:
7314 else:
7321 editform = b'tag.add'
7315 editform = b'tag.add'
7322 editor = cmdutil.getcommiteditor(
7316 editor = cmdutil.getcommiteditor(
7323 editform=editform, **pycompat.strkwargs(opts)
7317 editform=editform, **pycompat.strkwargs(opts)
7324 )
7318 )
7325
7319
7326 # don't allow tagging the null rev
7320 # don't allow tagging the null rev
7327 if (
7321 if (
7328 not opts.get(b'remove')
7322 not opts.get(b'remove')
7329 and scmutil.revsingle(repo, rev_).rev() == nullrev
7323 and scmutil.revsingle(repo, rev_).rev() == nullrev
7330 ):
7324 ):
7331 raise error.Abort(_(b"cannot tag null revision"))
7325 raise error.Abort(_(b"cannot tag null revision"))
7332
7326
7333 tagsmod.tag(
7327 tagsmod.tag(
7334 repo,
7328 repo,
7335 names,
7329 names,
7336 node,
7330 node,
7337 message,
7331 message,
7338 opts.get(b'local'),
7332 opts.get(b'local'),
7339 opts.get(b'user'),
7333 opts.get(b'user'),
7340 date,
7334 date,
7341 editor=editor,
7335 editor=editor,
7342 )
7336 )
7343
7337
7344
7338
7345 @command(
7339 @command(
7346 b'tags',
7340 b'tags',
7347 formatteropts,
7341 formatteropts,
7348 b'',
7342 b'',
7349 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7343 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7350 intents={INTENT_READONLY},
7344 intents={INTENT_READONLY},
7351 )
7345 )
7352 def tags(ui, repo, **opts):
7346 def tags(ui, repo, **opts):
7353 """list repository tags
7347 """list repository tags
7354
7348
7355 This lists both regular and local tags. When the -v/--verbose
7349 This lists both regular and local tags. When the -v/--verbose
7356 switch is used, a third column "local" is printed for local tags.
7350 switch is used, a third column "local" is printed for local tags.
7357 When the -q/--quiet switch is used, only the tag name is printed.
7351 When the -q/--quiet switch is used, only the tag name is printed.
7358
7352
7359 .. container:: verbose
7353 .. container:: verbose
7360
7354
7361 Template:
7355 Template:
7362
7356
7363 The following keywords are supported in addition to the common template
7357 The following keywords are supported in addition to the common template
7364 keywords and functions such as ``{tag}``. See also
7358 keywords and functions such as ``{tag}``. See also
7365 :hg:`help templates`.
7359 :hg:`help templates`.
7366
7360
7367 :type: String. ``local`` for local tags.
7361 :type: String. ``local`` for local tags.
7368
7362
7369 Returns 0 on success.
7363 Returns 0 on success.
7370 """
7364 """
7371
7365
7372 opts = pycompat.byteskwargs(opts)
7366 opts = pycompat.byteskwargs(opts)
7373 ui.pager(b'tags')
7367 ui.pager(b'tags')
7374 fm = ui.formatter(b'tags', opts)
7368 fm = ui.formatter(b'tags', opts)
7375 hexfunc = fm.hexfunc
7369 hexfunc = fm.hexfunc
7376
7370
7377 for t, n in reversed(repo.tagslist()):
7371 for t, n in reversed(repo.tagslist()):
7378 hn = hexfunc(n)
7372 hn = hexfunc(n)
7379 label = b'tags.normal'
7373 label = b'tags.normal'
7380 tagtype = b''
7374 tagtype = b''
7381 if repo.tagtype(t) == b'local':
7375 if repo.tagtype(t) == b'local':
7382 label = b'tags.local'
7376 label = b'tags.local'
7383 tagtype = b'local'
7377 tagtype = b'local'
7384
7378
7385 fm.startitem()
7379 fm.startitem()
7386 fm.context(repo=repo)
7380 fm.context(repo=repo)
7387 fm.write(b'tag', b'%s', t, label=label)
7381 fm.write(b'tag', b'%s', t, label=label)
7388 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7382 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7389 fm.condwrite(
7383 fm.condwrite(
7390 not ui.quiet,
7384 not ui.quiet,
7391 b'rev node',
7385 b'rev node',
7392 fmt,
7386 fmt,
7393 repo.changelog.rev(n),
7387 repo.changelog.rev(n),
7394 hn,
7388 hn,
7395 label=label,
7389 label=label,
7396 )
7390 )
7397 fm.condwrite(
7391 fm.condwrite(
7398 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7392 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7399 )
7393 )
7400 fm.plain(b'\n')
7394 fm.plain(b'\n')
7401 fm.end()
7395 fm.end()
7402
7396
7403
7397
7404 @command(
7398 @command(
7405 b'tip',
7399 b'tip',
7406 [
7400 [
7407 (b'p', b'patch', None, _(b'show patch')),
7401 (b'p', b'patch', None, _(b'show patch')),
7408 (b'g', b'git', None, _(b'use git extended diff format')),
7402 (b'g', b'git', None, _(b'use git extended diff format')),
7409 ]
7403 ]
7410 + templateopts,
7404 + templateopts,
7411 _(b'[-p] [-g]'),
7405 _(b'[-p] [-g]'),
7412 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7406 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7413 )
7407 )
7414 def tip(ui, repo, **opts):
7408 def tip(ui, repo, **opts):
7415 """show the tip revision (DEPRECATED)
7409 """show the tip revision (DEPRECATED)
7416
7410
7417 The tip revision (usually just called the tip) is the changeset
7411 The tip revision (usually just called the tip) is the changeset
7418 most recently added to the repository (and therefore the most
7412 most recently added to the repository (and therefore the most
7419 recently changed head).
7413 recently changed head).
7420
7414
7421 If you have just made a commit, that commit will be the tip. If
7415 If you have just made a commit, that commit will be the tip. If
7422 you have just pulled changes from another repository, the tip of
7416 you have just pulled changes from another repository, the tip of
7423 that repository becomes the current tip. The "tip" tag is special
7417 that repository becomes the current tip. The "tip" tag is special
7424 and cannot be renamed or assigned to a different changeset.
7418 and cannot be renamed or assigned to a different changeset.
7425
7419
7426 This command is deprecated, please use :hg:`heads` instead.
7420 This command is deprecated, please use :hg:`heads` instead.
7427
7421
7428 Returns 0 on success.
7422 Returns 0 on success.
7429 """
7423 """
7430 opts = pycompat.byteskwargs(opts)
7424 opts = pycompat.byteskwargs(opts)
7431 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7425 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7432 displayer.show(repo[b'tip'])
7426 displayer.show(repo[b'tip'])
7433 displayer.close()
7427 displayer.close()
7434
7428
7435
7429
7436 @command(
7430 @command(
7437 b'unbundle',
7431 b'unbundle',
7438 [
7432 [
7439 (
7433 (
7440 b'u',
7434 b'u',
7441 b'update',
7435 b'update',
7442 None,
7436 None,
7443 _(b'update to new branch head if changesets were unbundled'),
7437 _(b'update to new branch head if changesets were unbundled'),
7444 )
7438 )
7445 ],
7439 ],
7446 _(b'[-u] FILE...'),
7440 _(b'[-u] FILE...'),
7447 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7441 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7448 )
7442 )
7449 def unbundle(ui, repo, fname1, *fnames, **opts):
7443 def unbundle(ui, repo, fname1, *fnames, **opts):
7450 """apply one or more bundle files
7444 """apply one or more bundle files
7451
7445
7452 Apply one or more bundle files generated by :hg:`bundle`.
7446 Apply one or more bundle files generated by :hg:`bundle`.
7453
7447
7454 Returns 0 on success, 1 if an update has unresolved files.
7448 Returns 0 on success, 1 if an update has unresolved files.
7455 """
7449 """
7456 fnames = (fname1,) + fnames
7450 fnames = (fname1,) + fnames
7457
7451
7458 with repo.lock():
7452 with repo.lock():
7459 for fname in fnames:
7453 for fname in fnames:
7460 f = hg.openpath(ui, fname)
7454 f = hg.openpath(ui, fname)
7461 gen = exchange.readbundle(ui, f, fname)
7455 gen = exchange.readbundle(ui, f, fname)
7462 if isinstance(gen, streamclone.streamcloneapplier):
7456 if isinstance(gen, streamclone.streamcloneapplier):
7463 raise error.Abort(
7457 raise error.Abort(
7464 _(
7458 _(
7465 b'packed bundles cannot be applied with '
7459 b'packed bundles cannot be applied with '
7466 b'"hg unbundle"'
7460 b'"hg unbundle"'
7467 ),
7461 ),
7468 hint=_(b'use "hg debugapplystreamclonebundle"'),
7462 hint=_(b'use "hg debugapplystreamclonebundle"'),
7469 )
7463 )
7470 url = b'bundle:' + fname
7464 url = b'bundle:' + fname
7471 try:
7465 try:
7472 txnname = b'unbundle'
7466 txnname = b'unbundle'
7473 if not isinstance(gen, bundle2.unbundle20):
7467 if not isinstance(gen, bundle2.unbundle20):
7474 txnname = b'unbundle\n%s' % util.hidepassword(url)
7468 txnname = b'unbundle\n%s' % util.hidepassword(url)
7475 with repo.transaction(txnname) as tr:
7469 with repo.transaction(txnname) as tr:
7476 op = bundle2.applybundle(
7470 op = bundle2.applybundle(
7477 repo, gen, tr, source=b'unbundle', url=url
7471 repo, gen, tr, source=b'unbundle', url=url
7478 )
7472 )
7479 except error.BundleUnknownFeatureError as exc:
7473 except error.BundleUnknownFeatureError as exc:
7480 raise error.Abort(
7474 raise error.Abort(
7481 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7475 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7482 hint=_(
7476 hint=_(
7483 b"see https://mercurial-scm.org/"
7477 b"see https://mercurial-scm.org/"
7484 b"wiki/BundleFeature for more "
7478 b"wiki/BundleFeature for more "
7485 b"information"
7479 b"information"
7486 ),
7480 ),
7487 )
7481 )
7488 modheads = bundle2.combinechangegroupresults(op)
7482 modheads = bundle2.combinechangegroupresults(op)
7489
7483
7490 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7484 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7491
7485
7492
7486
7493 @command(
7487 @command(
7494 b'unshelve',
7488 b'unshelve',
7495 [
7489 [
7496 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7490 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7497 (
7491 (
7498 b'c',
7492 b'c',
7499 b'continue',
7493 b'continue',
7500 None,
7494 None,
7501 _(b'continue an incomplete unshelve operation'),
7495 _(b'continue an incomplete unshelve operation'),
7502 ),
7496 ),
7503 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7497 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7504 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7498 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7505 (
7499 (
7506 b'n',
7500 b'n',
7507 b'name',
7501 b'name',
7508 b'',
7502 b'',
7509 _(b'restore shelved change with given name'),
7503 _(b'restore shelved change with given name'),
7510 _(b'NAME'),
7504 _(b'NAME'),
7511 ),
7505 ),
7512 (b't', b'tool', b'', _(b'specify merge tool')),
7506 (b't', b'tool', b'', _(b'specify merge tool')),
7513 (
7507 (
7514 b'',
7508 b'',
7515 b'date',
7509 b'date',
7516 b'',
7510 b'',
7517 _(b'set date for temporary commits (DEPRECATED)'),
7511 _(b'set date for temporary commits (DEPRECATED)'),
7518 _(b'DATE'),
7512 _(b'DATE'),
7519 ),
7513 ),
7520 ],
7514 ],
7521 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7515 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7522 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7516 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7523 )
7517 )
7524 def unshelve(ui, repo, *shelved, **opts):
7518 def unshelve(ui, repo, *shelved, **opts):
7525 """restore a shelved change to the working directory
7519 """restore a shelved change to the working directory
7526
7520
7527 This command accepts an optional name of a shelved change to
7521 This command accepts an optional name of a shelved change to
7528 restore. If none is given, the most recent shelved change is used.
7522 restore. If none is given, the most recent shelved change is used.
7529
7523
7530 If a shelved change is applied successfully, the bundle that
7524 If a shelved change is applied successfully, the bundle that
7531 contains the shelved changes is moved to a backup location
7525 contains the shelved changes is moved to a backup location
7532 (.hg/shelve-backup).
7526 (.hg/shelve-backup).
7533
7527
7534 Since you can restore a shelved change on top of an arbitrary
7528 Since you can restore a shelved change on top of an arbitrary
7535 commit, it is possible that unshelving will result in a conflict
7529 commit, it is possible that unshelving will result in a conflict
7536 between your changes and the commits you are unshelving onto. If
7530 between your changes and the commits you are unshelving onto. If
7537 this occurs, you must resolve the conflict, then use
7531 this occurs, you must resolve the conflict, then use
7538 ``--continue`` to complete the unshelve operation. (The bundle
7532 ``--continue`` to complete the unshelve operation. (The bundle
7539 will not be moved until you successfully complete the unshelve.)
7533 will not be moved until you successfully complete the unshelve.)
7540
7534
7541 (Alternatively, you can use ``--abort`` to abandon an unshelve
7535 (Alternatively, you can use ``--abort`` to abandon an unshelve
7542 that causes a conflict. This reverts the unshelved changes, and
7536 that causes a conflict. This reverts the unshelved changes, and
7543 leaves the bundle in place.)
7537 leaves the bundle in place.)
7544
7538
7545 If bare shelved change (without interactive, include and exclude
7539 If bare shelved change (without interactive, include and exclude
7546 option) was done on newly created branch it would restore branch
7540 option) was done on newly created branch it would restore branch
7547 information to the working directory.
7541 information to the working directory.
7548
7542
7549 After a successful unshelve, the shelved changes are stored in a
7543 After a successful unshelve, the shelved changes are stored in a
7550 backup directory. Only the N most recent backups are kept. N
7544 backup directory. Only the N most recent backups are kept. N
7551 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7545 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7552 configuration option.
7546 configuration option.
7553
7547
7554 .. container:: verbose
7548 .. container:: verbose
7555
7549
7556 Timestamp in seconds is used to decide order of backups. More
7550 Timestamp in seconds is used to decide order of backups. More
7557 than ``maxbackups`` backups are kept, if same timestamp
7551 than ``maxbackups`` backups are kept, if same timestamp
7558 prevents from deciding exact order of them, for safety.
7552 prevents from deciding exact order of them, for safety.
7559
7553
7560 Selected changes can be unshelved with ``--interactive`` flag.
7554 Selected changes can be unshelved with ``--interactive`` flag.
7561 The working directory is updated with the selected changes, and
7555 The working directory is updated with the selected changes, and
7562 only the unselected changes remain shelved.
7556 only the unselected changes remain shelved.
7563 Note: The whole shelve is applied to working directory first before
7557 Note: The whole shelve is applied to working directory first before
7564 running interactively. So, this will bring up all the conflicts between
7558 running interactively. So, this will bring up all the conflicts between
7565 working directory and the shelve, irrespective of which changes will be
7559 working directory and the shelve, irrespective of which changes will be
7566 unshelved.
7560 unshelved.
7567 """
7561 """
7568 with repo.wlock():
7562 with repo.wlock():
7569 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7563 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7570
7564
7571
7565
7572 statemod.addunfinished(
7566 statemod.addunfinished(
7573 b'unshelve',
7567 b'unshelve',
7574 fname=b'shelvedstate',
7568 fname=b'shelvedstate',
7575 continueflag=True,
7569 continueflag=True,
7576 abortfunc=shelvemod.hgabortunshelve,
7570 abortfunc=shelvemod.hgabortunshelve,
7577 continuefunc=shelvemod.hgcontinueunshelve,
7571 continuefunc=shelvemod.hgcontinueunshelve,
7578 cmdmsg=_(b'unshelve already in progress'),
7572 cmdmsg=_(b'unshelve already in progress'),
7579 )
7573 )
7580
7574
7581
7575
7582 @command(
7576 @command(
7583 b'update|up|checkout|co',
7577 b'update|up|checkout|co',
7584 [
7578 [
7585 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7579 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7586 (b'c', b'check', None, _(b'require clean working directory')),
7580 (b'c', b'check', None, _(b'require clean working directory')),
7587 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7581 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7588 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7582 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7589 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7583 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7590 ]
7584 ]
7591 + mergetoolopts,
7585 + mergetoolopts,
7592 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7586 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7593 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7587 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7594 helpbasic=True,
7588 helpbasic=True,
7595 )
7589 )
7596 def update(ui, repo, node=None, **opts):
7590 def update(ui, repo, node=None, **opts):
7597 """update working directory (or switch revisions)
7591 """update working directory (or switch revisions)
7598
7592
7599 Update the repository's working directory to the specified
7593 Update the repository's working directory to the specified
7600 changeset. If no changeset is specified, update to the tip of the
7594 changeset. If no changeset is specified, update to the tip of the
7601 current named branch and move the active bookmark (see :hg:`help
7595 current named branch and move the active bookmark (see :hg:`help
7602 bookmarks`).
7596 bookmarks`).
7603
7597
7604 Update sets the working directory's parent revision to the specified
7598 Update sets the working directory's parent revision to the specified
7605 changeset (see :hg:`help parents`).
7599 changeset (see :hg:`help parents`).
7606
7600
7607 If the changeset is not a descendant or ancestor of the working
7601 If the changeset is not a descendant or ancestor of the working
7608 directory's parent and there are uncommitted changes, the update is
7602 directory's parent and there are uncommitted changes, the update is
7609 aborted. With the -c/--check option, the working directory is checked
7603 aborted. With the -c/--check option, the working directory is checked
7610 for uncommitted changes; if none are found, the working directory is
7604 for uncommitted changes; if none are found, the working directory is
7611 updated to the specified changeset.
7605 updated to the specified changeset.
7612
7606
7613 .. container:: verbose
7607 .. container:: verbose
7614
7608
7615 The -C/--clean, -c/--check, and -m/--merge options control what
7609 The -C/--clean, -c/--check, and -m/--merge options control what
7616 happens if the working directory contains uncommitted changes.
7610 happens if the working directory contains uncommitted changes.
7617 At most of one of them can be specified.
7611 At most of one of them can be specified.
7618
7612
7619 1. If no option is specified, and if
7613 1. If no option is specified, and if
7620 the requested changeset is an ancestor or descendant of
7614 the requested changeset is an ancestor or descendant of
7621 the working directory's parent, the uncommitted changes
7615 the working directory's parent, the uncommitted changes
7622 are merged into the requested changeset and the merged
7616 are merged into the requested changeset and the merged
7623 result is left uncommitted. If the requested changeset is
7617 result is left uncommitted. If the requested changeset is
7624 not an ancestor or descendant (that is, it is on another
7618 not an ancestor or descendant (that is, it is on another
7625 branch), the update is aborted and the uncommitted changes
7619 branch), the update is aborted and the uncommitted changes
7626 are preserved.
7620 are preserved.
7627
7621
7628 2. With the -m/--merge option, the update is allowed even if the
7622 2. With the -m/--merge option, the update is allowed even if the
7629 requested changeset is not an ancestor or descendant of
7623 requested changeset is not an ancestor or descendant of
7630 the working directory's parent.
7624 the working directory's parent.
7631
7625
7632 3. With the -c/--check option, the update is aborted and the
7626 3. With the -c/--check option, the update is aborted and the
7633 uncommitted changes are preserved.
7627 uncommitted changes are preserved.
7634
7628
7635 4. With the -C/--clean option, uncommitted changes are discarded and
7629 4. With the -C/--clean option, uncommitted changes are discarded and
7636 the working directory is updated to the requested changeset.
7630 the working directory is updated to the requested changeset.
7637
7631
7638 To cancel an uncommitted merge (and lose your changes), use
7632 To cancel an uncommitted merge (and lose your changes), use
7639 :hg:`merge --abort`.
7633 :hg:`merge --abort`.
7640
7634
7641 Use null as the changeset to remove the working directory (like
7635 Use null as the changeset to remove the working directory (like
7642 :hg:`clone -U`).
7636 :hg:`clone -U`).
7643
7637
7644 If you want to revert just one file to an older revision, use
7638 If you want to revert just one file to an older revision, use
7645 :hg:`revert [-r REV] NAME`.
7639 :hg:`revert [-r REV] NAME`.
7646
7640
7647 See :hg:`help dates` for a list of formats valid for -d/--date.
7641 See :hg:`help dates` for a list of formats valid for -d/--date.
7648
7642
7649 Returns 0 on success, 1 if there are unresolved files.
7643 Returns 0 on success, 1 if there are unresolved files.
7650 """
7644 """
7651 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7645 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7652 rev = opts.get('rev')
7646 rev = opts.get('rev')
7653 date = opts.get('date')
7647 date = opts.get('date')
7654 clean = opts.get('clean')
7648 clean = opts.get('clean')
7655 check = opts.get('check')
7649 check = opts.get('check')
7656 merge = opts.get('merge')
7650 merge = opts.get('merge')
7657 if rev and node:
7651 if rev and node:
7658 raise error.Abort(_(b"please specify just one revision"))
7652 raise error.Abort(_(b"please specify just one revision"))
7659
7653
7660 if ui.configbool(b'commands', b'update.requiredest'):
7654 if ui.configbool(b'commands', b'update.requiredest'):
7661 if not node and not rev and not date:
7655 if not node and not rev and not date:
7662 raise error.Abort(
7656 raise error.Abort(
7663 _(b'you must specify a destination'),
7657 _(b'you must specify a destination'),
7664 hint=_(b'for example: hg update ".::"'),
7658 hint=_(b'for example: hg update ".::"'),
7665 )
7659 )
7666
7660
7667 if rev is None or rev == b'':
7661 if rev is None or rev == b'':
7668 rev = node
7662 rev = node
7669
7663
7670 if date and rev is not None:
7664 if date and rev is not None:
7671 raise error.Abort(_(b"you can't specify a revision and a date"))
7665 raise error.Abort(_(b"you can't specify a revision and a date"))
7672
7666
7673 updatecheck = None
7667 updatecheck = None
7674 if check:
7668 if check:
7675 updatecheck = b'abort'
7669 updatecheck = b'abort'
7676 elif merge:
7670 elif merge:
7677 updatecheck = b'none'
7671 updatecheck = b'none'
7678
7672
7679 with repo.wlock():
7673 with repo.wlock():
7680 cmdutil.clearunfinished(repo)
7674 cmdutil.clearunfinished(repo)
7681 if date:
7675 if date:
7682 rev = cmdutil.finddate(ui, repo, date)
7676 rev = cmdutil.finddate(ui, repo, date)
7683
7677
7684 # if we defined a bookmark, we have to remember the original name
7678 # if we defined a bookmark, we have to remember the original name
7685 brev = rev
7679 brev = rev
7686 if rev:
7680 if rev:
7687 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7681 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7688 ctx = scmutil.revsingle(repo, rev, default=None)
7682 ctx = scmutil.revsingle(repo, rev, default=None)
7689 rev = ctx.rev()
7683 rev = ctx.rev()
7690 hidden = ctx.hidden()
7684 hidden = ctx.hidden()
7691 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7685 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7692 with ui.configoverride(overrides, b'update'):
7686 with ui.configoverride(overrides, b'update'):
7693 ret = hg.updatetotally(
7687 ret = hg.updatetotally(
7694 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7688 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7695 )
7689 )
7696 if hidden:
7690 if hidden:
7697 ctxstr = ctx.hex()[:12]
7691 ctxstr = ctx.hex()[:12]
7698 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7692 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7699
7693
7700 if ctx.obsolete():
7694 if ctx.obsolete():
7701 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7695 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7702 ui.warn(b"(%s)\n" % obsfatemsg)
7696 ui.warn(b"(%s)\n" % obsfatemsg)
7703 return ret
7697 return ret
7704
7698
7705
7699
7706 @command(
7700 @command(
7707 b'verify',
7701 b'verify',
7708 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7702 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7709 helpcategory=command.CATEGORY_MAINTENANCE,
7703 helpcategory=command.CATEGORY_MAINTENANCE,
7710 )
7704 )
7711 def verify(ui, repo, **opts):
7705 def verify(ui, repo, **opts):
7712 """verify the integrity of the repository
7706 """verify the integrity of the repository
7713
7707
7714 Verify the integrity of the current repository.
7708 Verify the integrity of the current repository.
7715
7709
7716 This will perform an extensive check of the repository's
7710 This will perform an extensive check of the repository's
7717 integrity, validating the hashes and checksums of each entry in
7711 integrity, validating the hashes and checksums of each entry in
7718 the changelog, manifest, and tracked files, as well as the
7712 the changelog, manifest, and tracked files, as well as the
7719 integrity of their crosslinks and indices.
7713 integrity of their crosslinks and indices.
7720
7714
7721 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7715 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7722 for more information about recovery from corruption of the
7716 for more information about recovery from corruption of the
7723 repository.
7717 repository.
7724
7718
7725 Returns 0 on success, 1 if errors are encountered.
7719 Returns 0 on success, 1 if errors are encountered.
7726 """
7720 """
7727 opts = pycompat.byteskwargs(opts)
7721 opts = pycompat.byteskwargs(opts)
7728
7722
7729 level = None
7723 level = None
7730 if opts[b'full']:
7724 if opts[b'full']:
7731 level = verifymod.VERIFY_FULL
7725 level = verifymod.VERIFY_FULL
7732 return hg.verify(repo, level)
7726 return hg.verify(repo, level)
7733
7727
7734
7728
7735 @command(
7729 @command(
7736 b'version',
7730 b'version',
7737 [] + formatteropts,
7731 [] + formatteropts,
7738 helpcategory=command.CATEGORY_HELP,
7732 helpcategory=command.CATEGORY_HELP,
7739 norepo=True,
7733 norepo=True,
7740 intents={INTENT_READONLY},
7734 intents={INTENT_READONLY},
7741 )
7735 )
7742 def version_(ui, **opts):
7736 def version_(ui, **opts):
7743 """output version and copyright information
7737 """output version and copyright information
7744
7738
7745 .. container:: verbose
7739 .. container:: verbose
7746
7740
7747 Template:
7741 Template:
7748
7742
7749 The following keywords are supported. See also :hg:`help templates`.
7743 The following keywords are supported. See also :hg:`help templates`.
7750
7744
7751 :extensions: List of extensions.
7745 :extensions: List of extensions.
7752 :ver: String. Version number.
7746 :ver: String. Version number.
7753
7747
7754 And each entry of ``{extensions}`` provides the following sub-keywords
7748 And each entry of ``{extensions}`` provides the following sub-keywords
7755 in addition to ``{ver}``.
7749 in addition to ``{ver}``.
7756
7750
7757 :bundled: Boolean. True if included in the release.
7751 :bundled: Boolean. True if included in the release.
7758 :name: String. Extension name.
7752 :name: String. Extension name.
7759 """
7753 """
7760 opts = pycompat.byteskwargs(opts)
7754 opts = pycompat.byteskwargs(opts)
7761 if ui.verbose:
7755 if ui.verbose:
7762 ui.pager(b'version')
7756 ui.pager(b'version')
7763 fm = ui.formatter(b"version", opts)
7757 fm = ui.formatter(b"version", opts)
7764 fm.startitem()
7758 fm.startitem()
7765 fm.write(
7759 fm.write(
7766 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7760 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7767 )
7761 )
7768 license = _(
7762 license = _(
7769 b"(see https://mercurial-scm.org for more information)\n"
7763 b"(see https://mercurial-scm.org for more information)\n"
7770 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7764 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7771 b"This is free software; see the source for copying conditions. "
7765 b"This is free software; see the source for copying conditions. "
7772 b"There is NO\nwarranty; "
7766 b"There is NO\nwarranty; "
7773 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7767 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7774 )
7768 )
7775 if not ui.quiet:
7769 if not ui.quiet:
7776 fm.plain(license)
7770 fm.plain(license)
7777
7771
7778 if ui.verbose:
7772 if ui.verbose:
7779 fm.plain(_(b"\nEnabled extensions:\n\n"))
7773 fm.plain(_(b"\nEnabled extensions:\n\n"))
7780 # format names and versions into columns
7774 # format names and versions into columns
7781 names = []
7775 names = []
7782 vers = []
7776 vers = []
7783 isinternals = []
7777 isinternals = []
7784 for name, module in sorted(extensions.extensions()):
7778 for name, module in sorted(extensions.extensions()):
7785 names.append(name)
7779 names.append(name)
7786 vers.append(extensions.moduleversion(module) or None)
7780 vers.append(extensions.moduleversion(module) or None)
7787 isinternals.append(extensions.ismoduleinternal(module))
7781 isinternals.append(extensions.ismoduleinternal(module))
7788 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7782 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7789 if names:
7783 if names:
7790 namefmt = b" %%-%ds " % max(len(n) for n in names)
7784 namefmt = b" %%-%ds " % max(len(n) for n in names)
7791 places = [_(b"external"), _(b"internal")]
7785 places = [_(b"external"), _(b"internal")]
7792 for n, v, p in zip(names, vers, isinternals):
7786 for n, v, p in zip(names, vers, isinternals):
7793 fn.startitem()
7787 fn.startitem()
7794 fn.condwrite(ui.verbose, b"name", namefmt, n)
7788 fn.condwrite(ui.verbose, b"name", namefmt, n)
7795 if ui.verbose:
7789 if ui.verbose:
7796 fn.plain(b"%s " % places[p])
7790 fn.plain(b"%s " % places[p])
7797 fn.data(bundled=p)
7791 fn.data(bundled=p)
7798 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7792 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7799 if ui.verbose:
7793 if ui.verbose:
7800 fn.plain(b"\n")
7794 fn.plain(b"\n")
7801 fn.end()
7795 fn.end()
7802 fm.end()
7796 fm.end()
7803
7797
7804
7798
7805 def loadcmdtable(ui, name, cmdtable):
7799 def loadcmdtable(ui, name, cmdtable):
7806 """Load command functions from specified cmdtable
7800 """Load command functions from specified cmdtable
7807 """
7801 """
7808 overrides = [cmd for cmd in cmdtable if cmd in table]
7802 overrides = [cmd for cmd in cmdtable if cmd in table]
7809 if overrides:
7803 if overrides:
7810 ui.warn(
7804 ui.warn(
7811 _(b"extension '%s' overrides commands: %s\n")
7805 _(b"extension '%s' overrides commands: %s\n")
7812 % (name, b" ".join(overrides))
7806 % (name, b" ".join(overrides))
7813 )
7807 )
7814 table.update(cmdtable)
7808 table.update(cmdtable)
@@ -1,2236 +1,2253 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import errno
11 import errno
12 import stat
12 import stat
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 addednodeid,
17 addednodeid,
18 modifiednodeid,
18 modifiednodeid,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 )
21 )
22 from .thirdparty import attr
22 from .thirdparty import attr
23 from . import (
23 from . import (
24 copies,
24 copies,
25 encoding,
25 encoding,
26 error,
26 error,
27 filemerge,
27 filemerge,
28 match as matchmod,
28 match as matchmod,
29 mergestate as mergestatemod,
29 mergestate as mergestatemod,
30 obsutil,
30 obsutil,
31 pathutil,
31 pathutil,
32 pycompat,
32 pycompat,
33 scmutil,
33 scmutil,
34 subrepoutil,
34 subrepoutil,
35 util,
35 util,
36 worker,
36 worker,
37 )
37 )
38
38
39 _pack = struct.pack
39 _pack = struct.pack
40 _unpack = struct.unpack
40 _unpack = struct.unpack
41
41
42
42
43 def _getcheckunknownconfig(repo, section, name):
43 def _getcheckunknownconfig(repo, section, name):
44 config = repo.ui.config(section, name)
44 config = repo.ui.config(section, name)
45 valid = [b'abort', b'ignore', b'warn']
45 valid = [b'abort', b'ignore', b'warn']
46 if config not in valid:
46 if config not in valid:
47 validstr = b', '.join([b"'" + v + b"'" for v in valid])
47 validstr = b', '.join([b"'" + v + b"'" for v in valid])
48 raise error.ConfigError(
48 raise error.ConfigError(
49 _(b"%s.%s not valid ('%s' is none of %s)")
49 _(b"%s.%s not valid ('%s' is none of %s)")
50 % (section, name, config, validstr)
50 % (section, name, config, validstr)
51 )
51 )
52 return config
52 return config
53
53
54
54
55 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
55 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
56 if wctx.isinmemory():
56 if wctx.isinmemory():
57 # Nothing to do in IMM because nothing in the "working copy" can be an
57 # Nothing to do in IMM because nothing in the "working copy" can be an
58 # unknown file.
58 # unknown file.
59 #
59 #
60 # Note that we should bail out here, not in ``_checkunknownfiles()``,
60 # Note that we should bail out here, not in ``_checkunknownfiles()``,
61 # because that function does other useful work.
61 # because that function does other useful work.
62 return False
62 return False
63
63
64 if f2 is None:
64 if f2 is None:
65 f2 = f
65 f2 = f
66 return (
66 return (
67 repo.wvfs.audit.check(f)
67 repo.wvfs.audit.check(f)
68 and repo.wvfs.isfileorlink(f)
68 and repo.wvfs.isfileorlink(f)
69 and repo.dirstate.normalize(f) not in repo.dirstate
69 and repo.dirstate.normalize(f) not in repo.dirstate
70 and mctx[f2].cmp(wctx[f])
70 and mctx[f2].cmp(wctx[f])
71 )
71 )
72
72
73
73
74 class _unknowndirschecker(object):
74 class _unknowndirschecker(object):
75 """
75 """
76 Look for any unknown files or directories that may have a path conflict
76 Look for any unknown files or directories that may have a path conflict
77 with a file. If any path prefix of the file exists as a file or link,
77 with a file. If any path prefix of the file exists as a file or link,
78 then it conflicts. If the file itself is a directory that contains any
78 then it conflicts. If the file itself is a directory that contains any
79 file that is not tracked, then it conflicts.
79 file that is not tracked, then it conflicts.
80
80
81 Returns the shortest path at which a conflict occurs, or None if there is
81 Returns the shortest path at which a conflict occurs, or None if there is
82 no conflict.
82 no conflict.
83 """
83 """
84
84
85 def __init__(self):
85 def __init__(self):
86 # A set of paths known to be good. This prevents repeated checking of
86 # A set of paths known to be good. This prevents repeated checking of
87 # dirs. It will be updated with any new dirs that are checked and found
87 # dirs. It will be updated with any new dirs that are checked and found
88 # to be safe.
88 # to be safe.
89 self._unknowndircache = set()
89 self._unknowndircache = set()
90
90
91 # A set of paths that are known to be absent. This prevents repeated
91 # A set of paths that are known to be absent. This prevents repeated
92 # checking of subdirectories that are known not to exist. It will be
92 # checking of subdirectories that are known not to exist. It will be
93 # updated with any new dirs that are checked and found to be absent.
93 # updated with any new dirs that are checked and found to be absent.
94 self._missingdircache = set()
94 self._missingdircache = set()
95
95
96 def __call__(self, repo, wctx, f):
96 def __call__(self, repo, wctx, f):
97 if wctx.isinmemory():
97 if wctx.isinmemory():
98 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
98 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
99 return False
99 return False
100
100
101 # Check for path prefixes that exist as unknown files.
101 # Check for path prefixes that exist as unknown files.
102 for p in reversed(list(pathutil.finddirs(f))):
102 for p in reversed(list(pathutil.finddirs(f))):
103 if p in self._missingdircache:
103 if p in self._missingdircache:
104 return
104 return
105 if p in self._unknowndircache:
105 if p in self._unknowndircache:
106 continue
106 continue
107 if repo.wvfs.audit.check(p):
107 if repo.wvfs.audit.check(p):
108 if (
108 if (
109 repo.wvfs.isfileorlink(p)
109 repo.wvfs.isfileorlink(p)
110 and repo.dirstate.normalize(p) not in repo.dirstate
110 and repo.dirstate.normalize(p) not in repo.dirstate
111 ):
111 ):
112 return p
112 return p
113 if not repo.wvfs.lexists(p):
113 if not repo.wvfs.lexists(p):
114 self._missingdircache.add(p)
114 self._missingdircache.add(p)
115 return
115 return
116 self._unknowndircache.add(p)
116 self._unknowndircache.add(p)
117
117
118 # Check if the file conflicts with a directory containing unknown files.
118 # Check if the file conflicts with a directory containing unknown files.
119 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
119 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
120 # Does the directory contain any files that are not in the dirstate?
120 # Does the directory contain any files that are not in the dirstate?
121 for p, dirs, files in repo.wvfs.walk(f):
121 for p, dirs, files in repo.wvfs.walk(f):
122 for fn in files:
122 for fn in files:
123 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
123 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
124 relf = repo.dirstate.normalize(relf, isknown=True)
124 relf = repo.dirstate.normalize(relf, isknown=True)
125 if relf not in repo.dirstate:
125 if relf not in repo.dirstate:
126 return f
126 return f
127 return None
127 return None
128
128
129
129
130 def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce):
130 def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce):
131 """
131 """
132 Considers any actions that care about the presence of conflicting unknown
132 Considers any actions that care about the presence of conflicting unknown
133 files. For some actions, the result is to abort; for others, it is to
133 files. For some actions, the result is to abort; for others, it is to
134 choose a different action.
134 choose a different action.
135 """
135 """
136 fileconflicts = set()
136 fileconflicts = set()
137 pathconflicts = set()
137 pathconflicts = set()
138 warnconflicts = set()
138 warnconflicts = set()
139 abortconflicts = set()
139 abortconflicts = set()
140 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
140 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
141 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
141 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
142 pathconfig = repo.ui.configbool(
142 pathconfig = repo.ui.configbool(
143 b'experimental', b'merge.checkpathconflicts'
143 b'experimental', b'merge.checkpathconflicts'
144 )
144 )
145 if not force:
145 if not force:
146
146
147 def collectconflicts(conflicts, config):
147 def collectconflicts(conflicts, config):
148 if config == b'abort':
148 if config == b'abort':
149 abortconflicts.update(conflicts)
149 abortconflicts.update(conflicts)
150 elif config == b'warn':
150 elif config == b'warn':
151 warnconflicts.update(conflicts)
151 warnconflicts.update(conflicts)
152
152
153 checkunknowndirs = _unknowndirschecker()
153 checkunknowndirs = _unknowndirschecker()
154 for f in mresult.files(
154 for f in mresult.files(
155 (
155 (
156 mergestatemod.ACTION_CREATED,
156 mergestatemod.ACTION_CREATED,
157 mergestatemod.ACTION_DELETED_CHANGED,
157 mergestatemod.ACTION_DELETED_CHANGED,
158 )
158 )
159 ):
159 ):
160 if _checkunknownfile(repo, wctx, mctx, f):
160 if _checkunknownfile(repo, wctx, mctx, f):
161 fileconflicts.add(f)
161 fileconflicts.add(f)
162 elif pathconfig and f not in wctx:
162 elif pathconfig and f not in wctx:
163 path = checkunknowndirs(repo, wctx, f)
163 path = checkunknowndirs(repo, wctx, f)
164 if path is not None:
164 if path is not None:
165 pathconflicts.add(path)
165 pathconflicts.add(path)
166 for f, args, msg in mresult.getactions(
166 for f, args, msg in mresult.getactions(
167 [mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]
167 [mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]
168 ):
168 ):
169 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
169 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
170 fileconflicts.add(f)
170 fileconflicts.add(f)
171
171
172 allconflicts = fileconflicts | pathconflicts
172 allconflicts = fileconflicts | pathconflicts
173 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
173 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
174 unknownconflicts = allconflicts - ignoredconflicts
174 unknownconflicts = allconflicts - ignoredconflicts
175 collectconflicts(ignoredconflicts, ignoredconfig)
175 collectconflicts(ignoredconflicts, ignoredconfig)
176 collectconflicts(unknownconflicts, unknownconfig)
176 collectconflicts(unknownconflicts, unknownconfig)
177 else:
177 else:
178 for f, args, msg in list(
178 for f, args, msg in list(
179 mresult.getactions([mergestatemod.ACTION_CREATED_MERGE])
179 mresult.getactions([mergestatemod.ACTION_CREATED_MERGE])
180 ):
180 ):
181 fl2, anc = args
181 fl2, anc = args
182 different = _checkunknownfile(repo, wctx, mctx, f)
182 different = _checkunknownfile(repo, wctx, mctx, f)
183 if repo.dirstate._ignore(f):
183 if repo.dirstate._ignore(f):
184 config = ignoredconfig
184 config = ignoredconfig
185 else:
185 else:
186 config = unknownconfig
186 config = unknownconfig
187
187
188 # The behavior when force is True is described by this table:
188 # The behavior when force is True is described by this table:
189 # config different mergeforce | action backup
189 # config different mergeforce | action backup
190 # * n * | get n
190 # * n * | get n
191 # * y y | merge -
191 # * y y | merge -
192 # abort y n | merge - (1)
192 # abort y n | merge - (1)
193 # warn y n | warn + get y
193 # warn y n | warn + get y
194 # ignore y n | get y
194 # ignore y n | get y
195 #
195 #
196 # (1) this is probably the wrong behavior here -- we should
196 # (1) this is probably the wrong behavior here -- we should
197 # probably abort, but some actions like rebases currently
197 # probably abort, but some actions like rebases currently
198 # don't like an abort happening in the middle of
198 # don't like an abort happening in the middle of
199 # merge.update.
199 # merge.update.
200 if not different:
200 if not different:
201 mresult.addfile(
201 mresult.addfile(
202 f,
202 f,
203 mergestatemod.ACTION_GET,
203 mergestatemod.ACTION_GET,
204 (fl2, False),
204 (fl2, False),
205 b'remote created',
205 b'remote created',
206 )
206 )
207 elif mergeforce or config == b'abort':
207 elif mergeforce or config == b'abort':
208 mresult.addfile(
208 mresult.addfile(
209 f,
209 f,
210 mergestatemod.ACTION_MERGE,
210 mergestatemod.ACTION_MERGE,
211 (f, f, None, False, anc),
211 (f, f, None, False, anc),
212 b'remote differs from untracked local',
212 b'remote differs from untracked local',
213 )
213 )
214 elif config == b'abort':
214 elif config == b'abort':
215 abortconflicts.add(f)
215 abortconflicts.add(f)
216 else:
216 else:
217 if config == b'warn':
217 if config == b'warn':
218 warnconflicts.add(f)
218 warnconflicts.add(f)
219 mresult.addfile(
219 mresult.addfile(
220 f, mergestatemod.ACTION_GET, (fl2, True), b'remote created',
220 f, mergestatemod.ACTION_GET, (fl2, True), b'remote created',
221 )
221 )
222
222
223 for f in sorted(abortconflicts):
223 for f in sorted(abortconflicts):
224 warn = repo.ui.warn
224 warn = repo.ui.warn
225 if f in pathconflicts:
225 if f in pathconflicts:
226 if repo.wvfs.isfileorlink(f):
226 if repo.wvfs.isfileorlink(f):
227 warn(_(b"%s: untracked file conflicts with directory\n") % f)
227 warn(_(b"%s: untracked file conflicts with directory\n") % f)
228 else:
228 else:
229 warn(_(b"%s: untracked directory conflicts with file\n") % f)
229 warn(_(b"%s: untracked directory conflicts with file\n") % f)
230 else:
230 else:
231 warn(_(b"%s: untracked file differs\n") % f)
231 warn(_(b"%s: untracked file differs\n") % f)
232 if abortconflicts:
232 if abortconflicts:
233 raise error.Abort(
233 raise error.Abort(
234 _(
234 _(
235 b"untracked files in working directory "
235 b"untracked files in working directory "
236 b"differ from files in requested revision"
236 b"differ from files in requested revision"
237 )
237 )
238 )
238 )
239
239
240 for f in sorted(warnconflicts):
240 for f in sorted(warnconflicts):
241 if repo.wvfs.isfileorlink(f):
241 if repo.wvfs.isfileorlink(f):
242 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
242 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
243 else:
243 else:
244 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
244 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
245
245
246 for f, args, msg in list(
246 for f, args, msg in list(
247 mresult.getactions([mergestatemod.ACTION_CREATED])
247 mresult.getactions([mergestatemod.ACTION_CREATED])
248 ):
248 ):
249 backup = (
249 backup = (
250 f in fileconflicts
250 f in fileconflicts
251 or f in pathconflicts
251 or f in pathconflicts
252 or any(p in pathconflicts for p in pathutil.finddirs(f))
252 or any(p in pathconflicts for p in pathutil.finddirs(f))
253 )
253 )
254 (flags,) = args
254 (flags,) = args
255 mresult.addfile(f, mergestatemod.ACTION_GET, (flags, backup), msg)
255 mresult.addfile(f, mergestatemod.ACTION_GET, (flags, backup), msg)
256
256
257
257
258 def _forgetremoved(wctx, mctx, branchmerge, mresult):
258 def _forgetremoved(wctx, mctx, branchmerge, mresult):
259 """
259 """
260 Forget removed files
260 Forget removed files
261
261
262 If we're jumping between revisions (as opposed to merging), and if
262 If we're jumping between revisions (as opposed to merging), and if
263 neither the working directory nor the target rev has the file,
263 neither the working directory nor the target rev has the file,
264 then we need to remove it from the dirstate, to prevent the
264 then we need to remove it from the dirstate, to prevent the
265 dirstate from listing the file when it is no longer in the
265 dirstate from listing the file when it is no longer in the
266 manifest.
266 manifest.
267
267
268 If we're merging, and the other revision has removed a file
268 If we're merging, and the other revision has removed a file
269 that is not present in the working directory, we need to mark it
269 that is not present in the working directory, we need to mark it
270 as removed.
270 as removed.
271 """
271 """
272
272
273 m = mergestatemod.ACTION_FORGET
273 m = mergestatemod.ACTION_FORGET
274 if branchmerge:
274 if branchmerge:
275 m = mergestatemod.ACTION_REMOVE
275 m = mergestatemod.ACTION_REMOVE
276 for f in wctx.deleted():
276 for f in wctx.deleted():
277 if f not in mctx:
277 if f not in mctx:
278 mresult.addfile(f, m, None, b"forget deleted")
278 mresult.addfile(f, m, None, b"forget deleted")
279
279
280 if not branchmerge:
280 if not branchmerge:
281 for f in wctx.removed():
281 for f in wctx.removed():
282 if f not in mctx:
282 if f not in mctx:
283 mresult.addfile(
283 mresult.addfile(
284 f, mergestatemod.ACTION_FORGET, None, b"forget removed",
284 f, mergestatemod.ACTION_FORGET, None, b"forget removed",
285 )
285 )
286
286
287
287
288 def _checkcollision(repo, wmf, mresult):
288 def _checkcollision(repo, wmf, mresult):
289 """
289 """
290 Check for case-folding collisions.
290 Check for case-folding collisions.
291 """
291 """
292 # If the repo is narrowed, filter out files outside the narrowspec.
292 # If the repo is narrowed, filter out files outside the narrowspec.
293 narrowmatch = repo.narrowmatch()
293 narrowmatch = repo.narrowmatch()
294 if not narrowmatch.always():
294 if not narrowmatch.always():
295 pmmf = set(wmf.walk(narrowmatch))
295 pmmf = set(wmf.walk(narrowmatch))
296 if mresult:
296 if mresult:
297 for f in list(mresult.files()):
297 for f in list(mresult.files()):
298 if not narrowmatch(f):
298 if not narrowmatch(f):
299 mresult.removefile(f)
299 mresult.removefile(f)
300 else:
300 else:
301 # build provisional merged manifest up
301 # build provisional merged manifest up
302 pmmf = set(wmf)
302 pmmf = set(wmf)
303
303
304 if mresult:
304 if mresult:
305 # KEEP and EXEC are no-op
305 # KEEP and EXEC are no-op
306 for f in mresult.files(
306 for f in mresult.files(
307 (
307 (
308 mergestatemod.ACTION_ADD,
308 mergestatemod.ACTION_ADD,
309 mergestatemod.ACTION_ADD_MODIFIED,
309 mergestatemod.ACTION_ADD_MODIFIED,
310 mergestatemod.ACTION_FORGET,
310 mergestatemod.ACTION_FORGET,
311 mergestatemod.ACTION_GET,
311 mergestatemod.ACTION_GET,
312 mergestatemod.ACTION_CHANGED_DELETED,
312 mergestatemod.ACTION_CHANGED_DELETED,
313 mergestatemod.ACTION_DELETED_CHANGED,
313 mergestatemod.ACTION_DELETED_CHANGED,
314 )
314 )
315 ):
315 ):
316 pmmf.add(f)
316 pmmf.add(f)
317 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
317 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
318 pmmf.discard(f)
318 pmmf.discard(f)
319 for f, args, msg in mresult.getactions(
319 for f, args, msg in mresult.getactions(
320 [mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]
320 [mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]
321 ):
321 ):
322 f2, flags = args
322 f2, flags = args
323 pmmf.discard(f2)
323 pmmf.discard(f2)
324 pmmf.add(f)
324 pmmf.add(f)
325 for f in mresult.files((mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,)):
325 for f in mresult.files((mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,)):
326 pmmf.add(f)
326 pmmf.add(f)
327 for f, args, msg in mresult.getactions([mergestatemod.ACTION_MERGE]):
327 for f, args, msg in mresult.getactions([mergestatemod.ACTION_MERGE]):
328 f1, f2, fa, move, anc = args
328 f1, f2, fa, move, anc = args
329 if move:
329 if move:
330 pmmf.discard(f1)
330 pmmf.discard(f1)
331 pmmf.add(f)
331 pmmf.add(f)
332
332
333 # check case-folding collision in provisional merged manifest
333 # check case-folding collision in provisional merged manifest
334 foldmap = {}
334 foldmap = {}
335 for f in pmmf:
335 for f in pmmf:
336 fold = util.normcase(f)
336 fold = util.normcase(f)
337 if fold in foldmap:
337 if fold in foldmap:
338 raise error.Abort(
338 raise error.Abort(
339 _(b"case-folding collision between %s and %s")
339 _(b"case-folding collision between %s and %s")
340 % (f, foldmap[fold])
340 % (f, foldmap[fold])
341 )
341 )
342 foldmap[fold] = f
342 foldmap[fold] = f
343
343
344 # check case-folding of directories
344 # check case-folding of directories
345 foldprefix = unfoldprefix = lastfull = b''
345 foldprefix = unfoldprefix = lastfull = b''
346 for fold, f in sorted(foldmap.items()):
346 for fold, f in sorted(foldmap.items()):
347 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
347 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
348 # the folded prefix matches but actual casing is different
348 # the folded prefix matches but actual casing is different
349 raise error.Abort(
349 raise error.Abort(
350 _(b"case-folding collision between %s and directory of %s")
350 _(b"case-folding collision between %s and directory of %s")
351 % (lastfull, f)
351 % (lastfull, f)
352 )
352 )
353 foldprefix = fold + b'/'
353 foldprefix = fold + b'/'
354 unfoldprefix = f + b'/'
354 unfoldprefix = f + b'/'
355 lastfull = f
355 lastfull = f
356
356
357
357
358 def _filesindirs(repo, manifest, dirs):
358 def _filesindirs(repo, manifest, dirs):
359 """
359 """
360 Generator that yields pairs of all the files in the manifest that are found
360 Generator that yields pairs of all the files in the manifest that are found
361 inside the directories listed in dirs, and which directory they are found
361 inside the directories listed in dirs, and which directory they are found
362 in.
362 in.
363 """
363 """
364 for f in manifest:
364 for f in manifest:
365 for p in pathutil.finddirs(f):
365 for p in pathutil.finddirs(f):
366 if p in dirs:
366 if p in dirs:
367 yield f, p
367 yield f, p
368 break
368 break
369
369
370
370
371 def checkpathconflicts(repo, wctx, mctx, mresult):
371 def checkpathconflicts(repo, wctx, mctx, mresult):
372 """
372 """
373 Check if any actions introduce path conflicts in the repository, updating
373 Check if any actions introduce path conflicts in the repository, updating
374 actions to record or handle the path conflict accordingly.
374 actions to record or handle the path conflict accordingly.
375 """
375 """
376 mf = wctx.manifest()
376 mf = wctx.manifest()
377
377
378 # The set of local files that conflict with a remote directory.
378 # The set of local files that conflict with a remote directory.
379 localconflicts = set()
379 localconflicts = set()
380
380
381 # The set of directories that conflict with a remote file, and so may cause
381 # The set of directories that conflict with a remote file, and so may cause
382 # conflicts if they still contain any files after the merge.
382 # conflicts if they still contain any files after the merge.
383 remoteconflicts = set()
383 remoteconflicts = set()
384
384
385 # The set of directories that appear as both a file and a directory in the
385 # The set of directories that appear as both a file and a directory in the
386 # remote manifest. These indicate an invalid remote manifest, which
386 # remote manifest. These indicate an invalid remote manifest, which
387 # can't be updated to cleanly.
387 # can't be updated to cleanly.
388 invalidconflicts = set()
388 invalidconflicts = set()
389
389
390 # The set of directories that contain files that are being created.
390 # The set of directories that contain files that are being created.
391 createdfiledirs = set()
391 createdfiledirs = set()
392
392
393 # The set of files deleted by all the actions.
393 # The set of files deleted by all the actions.
394 deletedfiles = set()
394 deletedfiles = set()
395
395
396 for f in mresult.files(
396 for f in mresult.files(
397 (
397 (
398 mergestatemod.ACTION_CREATED,
398 mergestatemod.ACTION_CREATED,
399 mergestatemod.ACTION_DELETED_CHANGED,
399 mergestatemod.ACTION_DELETED_CHANGED,
400 mergestatemod.ACTION_MERGE,
400 mergestatemod.ACTION_MERGE,
401 mergestatemod.ACTION_CREATED_MERGE,
401 mergestatemod.ACTION_CREATED_MERGE,
402 )
402 )
403 ):
403 ):
404 # This action may create a new local file.
404 # This action may create a new local file.
405 createdfiledirs.update(pathutil.finddirs(f))
405 createdfiledirs.update(pathutil.finddirs(f))
406 if mf.hasdir(f):
406 if mf.hasdir(f):
407 # The file aliases a local directory. This might be ok if all
407 # The file aliases a local directory. This might be ok if all
408 # the files in the local directory are being deleted. This
408 # the files in the local directory are being deleted. This
409 # will be checked once we know what all the deleted files are.
409 # will be checked once we know what all the deleted files are.
410 remoteconflicts.add(f)
410 remoteconflicts.add(f)
411 # Track the names of all deleted files.
411 # Track the names of all deleted files.
412 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
412 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
413 deletedfiles.add(f)
413 deletedfiles.add(f)
414 for (f, args, msg) in mresult.getactions((mergestatemod.ACTION_MERGE,)):
414 for (f, args, msg) in mresult.getactions((mergestatemod.ACTION_MERGE,)):
415 f1, f2, fa, move, anc = args
415 f1, f2, fa, move, anc = args
416 if move:
416 if move:
417 deletedfiles.add(f1)
417 deletedfiles.add(f1)
418 for (f, args, msg) in mresult.getactions(
418 for (f, args, msg) in mresult.getactions(
419 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,)
419 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,)
420 ):
420 ):
421 f2, flags = args
421 f2, flags = args
422 deletedfiles.add(f2)
422 deletedfiles.add(f2)
423
423
424 # Check all directories that contain created files for path conflicts.
424 # Check all directories that contain created files for path conflicts.
425 for p in createdfiledirs:
425 for p in createdfiledirs:
426 if p in mf:
426 if p in mf:
427 if p in mctx:
427 if p in mctx:
428 # A file is in a directory which aliases both a local
428 # A file is in a directory which aliases both a local
429 # and a remote file. This is an internal inconsistency
429 # and a remote file. This is an internal inconsistency
430 # within the remote manifest.
430 # within the remote manifest.
431 invalidconflicts.add(p)
431 invalidconflicts.add(p)
432 else:
432 else:
433 # A file is in a directory which aliases a local file.
433 # A file is in a directory which aliases a local file.
434 # We will need to rename the local file.
434 # We will need to rename the local file.
435 localconflicts.add(p)
435 localconflicts.add(p)
436 pd = mresult.getfile(p)
436 pd = mresult.getfile(p)
437 if pd and pd[0] in (
437 if pd and pd[0] in (
438 mergestatemod.ACTION_CREATED,
438 mergestatemod.ACTION_CREATED,
439 mergestatemod.ACTION_DELETED_CHANGED,
439 mergestatemod.ACTION_DELETED_CHANGED,
440 mergestatemod.ACTION_MERGE,
440 mergestatemod.ACTION_MERGE,
441 mergestatemod.ACTION_CREATED_MERGE,
441 mergestatemod.ACTION_CREATED_MERGE,
442 ):
442 ):
443 # The file is in a directory which aliases a remote file.
443 # The file is in a directory which aliases a remote file.
444 # This is an internal inconsistency within the remote
444 # This is an internal inconsistency within the remote
445 # manifest.
445 # manifest.
446 invalidconflicts.add(p)
446 invalidconflicts.add(p)
447
447
448 # Rename all local conflicting files that have not been deleted.
448 # Rename all local conflicting files that have not been deleted.
449 for p in localconflicts:
449 for p in localconflicts:
450 if p not in deletedfiles:
450 if p not in deletedfiles:
451 ctxname = bytes(wctx).rstrip(b'+')
451 ctxname = bytes(wctx).rstrip(b'+')
452 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
452 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
453 porig = wctx[p].copysource() or p
453 porig = wctx[p].copysource() or p
454 mresult.addfile(
454 mresult.addfile(
455 pnew,
455 pnew,
456 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
456 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
457 (p, porig),
457 (p, porig),
458 b'local path conflict',
458 b'local path conflict',
459 )
459 )
460 mresult.addfile(
460 mresult.addfile(
461 p,
461 p,
462 mergestatemod.ACTION_PATH_CONFLICT,
462 mergestatemod.ACTION_PATH_CONFLICT,
463 (pnew, b'l'),
463 (pnew, b'l'),
464 b'path conflict',
464 b'path conflict',
465 )
465 )
466
466
467 if remoteconflicts:
467 if remoteconflicts:
468 # Check if all files in the conflicting directories have been removed.
468 # Check if all files in the conflicting directories have been removed.
469 ctxname = bytes(mctx).rstrip(b'+')
469 ctxname = bytes(mctx).rstrip(b'+')
470 for f, p in _filesindirs(repo, mf, remoteconflicts):
470 for f, p in _filesindirs(repo, mf, remoteconflicts):
471 if f not in deletedfiles:
471 if f not in deletedfiles:
472 m, args, msg = mresult.getfile(p)
472 m, args, msg = mresult.getfile(p)
473 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
473 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
474 if m in (
474 if m in (
475 mergestatemod.ACTION_DELETED_CHANGED,
475 mergestatemod.ACTION_DELETED_CHANGED,
476 mergestatemod.ACTION_MERGE,
476 mergestatemod.ACTION_MERGE,
477 ):
477 ):
478 # Action was merge, just update target.
478 # Action was merge, just update target.
479 mresult.addfile(pnew, m, args, msg)
479 mresult.addfile(pnew, m, args, msg)
480 else:
480 else:
481 # Action was create, change to renamed get action.
481 # Action was create, change to renamed get action.
482 fl = args[0]
482 fl = args[0]
483 mresult.addfile(
483 mresult.addfile(
484 pnew,
484 pnew,
485 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
485 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
486 (p, fl),
486 (p, fl),
487 b'remote path conflict',
487 b'remote path conflict',
488 )
488 )
489 mresult.addfile(
489 mresult.addfile(
490 p,
490 p,
491 mergestatemod.ACTION_PATH_CONFLICT,
491 mergestatemod.ACTION_PATH_CONFLICT,
492 (pnew, mergestatemod.ACTION_REMOVE),
492 (pnew, mergestatemod.ACTION_REMOVE),
493 b'path conflict',
493 b'path conflict',
494 )
494 )
495 remoteconflicts.remove(p)
495 remoteconflicts.remove(p)
496 break
496 break
497
497
498 if invalidconflicts:
498 if invalidconflicts:
499 for p in invalidconflicts:
499 for p in invalidconflicts:
500 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
500 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
501 raise error.Abort(_(b"destination manifest contains path conflicts"))
501 raise error.Abort(_(b"destination manifest contains path conflicts"))
502
502
503
503
504 def _filternarrowactions(narrowmatch, branchmerge, mresult):
504 def _filternarrowactions(narrowmatch, branchmerge, mresult):
505 """
505 """
506 Filters out actions that can ignored because the repo is narrowed.
506 Filters out actions that can ignored because the repo is narrowed.
507
507
508 Raise an exception if the merge cannot be completed because the repo is
508 Raise an exception if the merge cannot be completed because the repo is
509 narrowed.
509 narrowed.
510 """
510 """
511 # TODO: handle with nonconflicttypes
511 # TODO: handle with nonconflicttypes
512 nonconflicttypes = {
512 nonconflicttypes = {
513 mergestatemod.ACTION_ADD,
513 mergestatemod.ACTION_ADD,
514 mergestatemod.ACTION_ADD_MODIFIED,
514 mergestatemod.ACTION_ADD_MODIFIED,
515 mergestatemod.ACTION_CREATED,
515 mergestatemod.ACTION_CREATED,
516 mergestatemod.ACTION_CREATED_MERGE,
516 mergestatemod.ACTION_CREATED_MERGE,
517 mergestatemod.ACTION_FORGET,
517 mergestatemod.ACTION_FORGET,
518 mergestatemod.ACTION_GET,
518 mergestatemod.ACTION_GET,
519 mergestatemod.ACTION_REMOVE,
519 mergestatemod.ACTION_REMOVE,
520 mergestatemod.ACTION_EXEC,
520 mergestatemod.ACTION_EXEC,
521 }
521 }
522 # We mutate the items in the dict during iteration, so iterate
522 # We mutate the items in the dict during iteration, so iterate
523 # over a copy.
523 # over a copy.
524 for f, action in mresult.filemap():
524 for f, action in mresult.filemap():
525 if narrowmatch(f):
525 if narrowmatch(f):
526 pass
526 pass
527 elif not branchmerge:
527 elif not branchmerge:
528 mresult.removefile(f) # just updating, ignore changes outside clone
528 mresult.removefile(f) # just updating, ignore changes outside clone
529 elif action[0] in mergestatemod.NO_OP_ACTIONS:
529 elif action[0] in mergestatemod.NO_OP_ACTIONS:
530 mresult.removefile(f) # merge does not affect file
530 mresult.removefile(f) # merge does not affect file
531 elif action[0] in nonconflicttypes:
531 elif action[0] in nonconflicttypes:
532 raise error.Abort(
532 raise error.Abort(
533 _(
533 _(
534 b'merge affects file \'%s\' outside narrow, '
534 b'merge affects file \'%s\' outside narrow, '
535 b'which is not yet supported'
535 b'which is not yet supported'
536 )
536 )
537 % f,
537 % f,
538 hint=_(b'merging in the other direction may work'),
538 hint=_(b'merging in the other direction may work'),
539 )
539 )
540 else:
540 else:
541 raise error.Abort(
541 raise error.Abort(
542 _(b'conflict in file \'%s\' is outside narrow clone') % f
542 _(b'conflict in file \'%s\' is outside narrow clone') % f
543 )
543 )
544
544
545
545
546 class mergeresult(object):
546 class mergeresult(object):
547 ''''An object representing result of merging manifests.
547 ''''An object representing result of merging manifests.
548
548
549 It has information about what actions need to be performed on dirstate
549 It has information about what actions need to be performed on dirstate
550 mapping of divergent renames and other such cases. '''
550 mapping of divergent renames and other such cases. '''
551
551
552 def __init__(self):
552 def __init__(self):
553 """
553 """
554 filemapping: dict of filename as keys and action related info as values
554 filemapping: dict of filename as keys and action related info as values
555 diverge: mapping of source name -> list of dest name for
555 diverge: mapping of source name -> list of dest name for
556 divergent renames
556 divergent renames
557 renamedelete: mapping of source name -> list of destinations for files
557 renamedelete: mapping of source name -> list of destinations for files
558 deleted on one side and renamed on other.
558 deleted on one side and renamed on other.
559 commitinfo: dict containing data which should be used on commit
559 commitinfo: dict containing data which should be used on commit
560 contains a filename -> info mapping
560 contains a filename -> info mapping
561 actionmapping: dict of action names as keys and values are dict of
561 actionmapping: dict of action names as keys and values are dict of
562 filename as key and related data as values
562 filename as key and related data as values
563 """
563 """
564 self._filemapping = {}
564 self._filemapping = {}
565 self._diverge = {}
565 self._diverge = {}
566 self._renamedelete = {}
566 self._renamedelete = {}
567 self._commitinfo = collections.defaultdict(dict)
567 self._commitinfo = collections.defaultdict(dict)
568 self._actionmapping = collections.defaultdict(dict)
568 self._actionmapping = collections.defaultdict(dict)
569
569
570 def updatevalues(self, diverge, renamedelete):
570 def updatevalues(self, diverge, renamedelete):
571 self._diverge = diverge
571 self._diverge = diverge
572 self._renamedelete = renamedelete
572 self._renamedelete = renamedelete
573
573
574 def addfile(self, filename, action, data, message):
574 def addfile(self, filename, action, data, message):
575 """ adds a new file to the mergeresult object
575 """ adds a new file to the mergeresult object
576
576
577 filename: file which we are adding
577 filename: file which we are adding
578 action: one of mergestatemod.ACTION_*
578 action: one of mergestatemod.ACTION_*
579 data: a tuple of information like fctx and ctx related to this merge
579 data: a tuple of information like fctx and ctx related to this merge
580 message: a message about the merge
580 message: a message about the merge
581 """
581 """
582 # if the file already existed, we need to delete it's old
582 # if the file already existed, we need to delete it's old
583 # entry form _actionmapping too
583 # entry form _actionmapping too
584 if filename in self._filemapping:
584 if filename in self._filemapping:
585 a, d, m = self._filemapping[filename]
585 a, d, m = self._filemapping[filename]
586 del self._actionmapping[a][filename]
586 del self._actionmapping[a][filename]
587
587
588 self._filemapping[filename] = (action, data, message)
588 self._filemapping[filename] = (action, data, message)
589 self._actionmapping[action][filename] = (data, message)
589 self._actionmapping[action][filename] = (data, message)
590
590
591 def getfile(self, filename, default_return=None):
591 def getfile(self, filename, default_return=None):
592 """ returns (action, args, msg) about this file
592 """ returns (action, args, msg) about this file
593
593
594 returns default_return if the file is not present """
594 returns default_return if the file is not present """
595 if filename in self._filemapping:
595 if filename in self._filemapping:
596 return self._filemapping[filename]
596 return self._filemapping[filename]
597 return default_return
597 return default_return
598
598
599 def files(self, actions=None):
599 def files(self, actions=None):
600 """ returns files on which provided action needs to perfromed
600 """ returns files on which provided action needs to perfromed
601
601
602 If actions is None, all files are returned
602 If actions is None, all files are returned
603 """
603 """
604 # TODO: think whether we should return renamedelete and
604 # TODO: think whether we should return renamedelete and
605 # diverge filenames also
605 # diverge filenames also
606 if actions is None:
606 if actions is None:
607 for f in self._filemapping:
607 for f in self._filemapping:
608 yield f
608 yield f
609
609
610 else:
610 else:
611 for a in actions:
611 for a in actions:
612 for f in self._actionmapping[a]:
612 for f in self._actionmapping[a]:
613 yield f
613 yield f
614
614
615 def removefile(self, filename):
615 def removefile(self, filename):
616 """ removes a file from the mergeresult object as the file might
616 """ removes a file from the mergeresult object as the file might
617 not merging anymore """
617 not merging anymore """
618 action, data, message = self._filemapping[filename]
618 action, data, message = self._filemapping[filename]
619 del self._filemapping[filename]
619 del self._filemapping[filename]
620 del self._actionmapping[action][filename]
620 del self._actionmapping[action][filename]
621
621
622 def getactions(self, actions, sort=False):
622 def getactions(self, actions, sort=False):
623 """ get list of files which are marked with these actions
623 """ get list of files which are marked with these actions
624 if sort is true, files for each action is sorted and then added
624 if sort is true, files for each action is sorted and then added
625
625
626 Returns a list of tuple of form (filename, data, message)
626 Returns a list of tuple of form (filename, data, message)
627 """
627 """
628 for a in actions:
628 for a in actions:
629 if sort:
629 if sort:
630 for f in sorted(self._actionmapping[a]):
630 for f in sorted(self._actionmapping[a]):
631 args, msg = self._actionmapping[a][f]
631 args, msg = self._actionmapping[a][f]
632 yield f, args, msg
632 yield f, args, msg
633 else:
633 else:
634 for f, (args, msg) in pycompat.iteritems(
634 for f, (args, msg) in pycompat.iteritems(
635 self._actionmapping[a]
635 self._actionmapping[a]
636 ):
636 ):
637 yield f, args, msg
637 yield f, args, msg
638
638
639 def len(self, actions=None):
639 def len(self, actions=None):
640 """ returns number of files which needs actions
640 """ returns number of files which needs actions
641
641
642 if actions is passed, total of number of files in that action
642 if actions is passed, total of number of files in that action
643 only is returned """
643 only is returned """
644
644
645 if actions is None:
645 if actions is None:
646 return len(self._filemapping)
646 return len(self._filemapping)
647
647
648 return sum(len(self._actionmapping[a]) for a in actions)
648 return sum(len(self._actionmapping[a]) for a in actions)
649
649
650 def filemap(self, sort=False):
650 def filemap(self, sort=False):
651 if sorted:
651 if sorted:
652 for key, val in sorted(pycompat.iteritems(self._filemapping)):
652 for key, val in sorted(pycompat.iteritems(self._filemapping)):
653 yield key, val
653 yield key, val
654 else:
654 else:
655 for key, val in pycompat.iteritems(self._filemapping):
655 for key, val in pycompat.iteritems(self._filemapping):
656 yield key, val
656 yield key, val
657
657
658 def addcommitinfo(self, filename, key, value):
658 def addcommitinfo(self, filename, key, value):
659 """ adds key-value information about filename which will be required
659 """ adds key-value information about filename which will be required
660 while committing this merge """
660 while committing this merge """
661 self._commitinfo[filename][key] = value
661 self._commitinfo[filename][key] = value
662
662
663 @property
663 @property
664 def diverge(self):
664 def diverge(self):
665 return self._diverge
665 return self._diverge
666
666
667 @property
667 @property
668 def renamedelete(self):
668 def renamedelete(self):
669 return self._renamedelete
669 return self._renamedelete
670
670
671 @property
671 @property
672 def commitinfo(self):
672 def commitinfo(self):
673 return self._commitinfo
673 return self._commitinfo
674
674
675 @property
675 @property
676 def actionsdict(self):
676 def actionsdict(self):
677 """ returns a dictionary of actions to be perfomed with action as key
677 """ returns a dictionary of actions to be perfomed with action as key
678 and a list of files and related arguments as values """
678 and a list of files and related arguments as values """
679 res = collections.defaultdict(list)
679 res = collections.defaultdict(list)
680 for a, d in pycompat.iteritems(self._actionmapping):
680 for a, d in pycompat.iteritems(self._actionmapping):
681 for f, (args, msg) in pycompat.iteritems(d):
681 for f, (args, msg) in pycompat.iteritems(d):
682 res[a].append((f, args, msg))
682 res[a].append((f, args, msg))
683 return res
683 return res
684
684
685 def setactions(self, actions):
685 def setactions(self, actions):
686 self._filemapping = actions
686 self._filemapping = actions
687 self._actionmapping = collections.defaultdict(dict)
687 self._actionmapping = collections.defaultdict(dict)
688 for f, (act, data, msg) in pycompat.iteritems(self._filemapping):
688 for f, (act, data, msg) in pycompat.iteritems(self._filemapping):
689 self._actionmapping[act][f] = data, msg
689 self._actionmapping[act][f] = data, msg
690
690
691 def hasconflicts(self):
691 def hasconflicts(self):
692 """ tells whether this merge resulted in some actions which can
692 """ tells whether this merge resulted in some actions which can
693 result in conflicts or not """
693 result in conflicts or not """
694 for a in self._actionmapping.keys():
694 for a in self._actionmapping.keys():
695 if (
695 if (
696 a
696 a
697 not in (
697 not in (
698 mergestatemod.ACTION_GET,
698 mergestatemod.ACTION_GET,
699 mergestatemod.ACTION_EXEC,
699 mergestatemod.ACTION_EXEC,
700 mergestatemod.ACTION_REMOVE,
700 mergestatemod.ACTION_REMOVE,
701 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
701 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
702 )
702 )
703 and self._actionmapping[a]
703 and self._actionmapping[a]
704 and a not in mergestatemod.NO_OP_ACTIONS
704 and a not in mergestatemod.NO_OP_ACTIONS
705 ):
705 ):
706 return True
706 return True
707
707
708 return False
708 return False
709
709
710
710
711 def manifestmerge(
711 def manifestmerge(
712 repo,
712 repo,
713 wctx,
713 wctx,
714 p2,
714 p2,
715 pa,
715 pa,
716 branchmerge,
716 branchmerge,
717 force,
717 force,
718 matcher,
718 matcher,
719 acceptremote,
719 acceptremote,
720 followcopies,
720 followcopies,
721 forcefulldiff=False,
721 forcefulldiff=False,
722 ):
722 ):
723 """
723 """
724 Merge wctx and p2 with ancestor pa and generate merge action list
724 Merge wctx and p2 with ancestor pa and generate merge action list
725
725
726 branchmerge and force are as passed in to update
726 branchmerge and force are as passed in to update
727 matcher = matcher to filter file lists
727 matcher = matcher to filter file lists
728 acceptremote = accept the incoming changes without prompting
728 acceptremote = accept the incoming changes without prompting
729
729
730 Returns an object of mergeresult class
730 Returns an object of mergeresult class
731 """
731 """
732 mresult = mergeresult()
732 mresult = mergeresult()
733 if matcher is not None and matcher.always():
733 if matcher is not None and matcher.always():
734 matcher = None
734 matcher = None
735
735
736 # manifests fetched in order are going to be faster, so prime the caches
736 # manifests fetched in order are going to be faster, so prime the caches
737 [
737 [
738 x.manifest()
738 x.manifest()
739 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
739 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
740 ]
740 ]
741
741
742 branch_copies1 = copies.branch_copies()
742 branch_copies1 = copies.branch_copies()
743 branch_copies2 = copies.branch_copies()
743 branch_copies2 = copies.branch_copies()
744 diverge = {}
744 diverge = {}
745 # information from merge which is needed at commit time
745 # information from merge which is needed at commit time
746 # for example choosing filelog of which parent to commit
746 # for example choosing filelog of which parent to commit
747 # TODO: use specific constants in future for this mapping
747 # TODO: use specific constants in future for this mapping
748 if followcopies:
748 if followcopies:
749 branch_copies1, branch_copies2, diverge = copies.mergecopies(
749 branch_copies1, branch_copies2, diverge = copies.mergecopies(
750 repo, wctx, p2, pa
750 repo, wctx, p2, pa
751 )
751 )
752
752
753 boolbm = pycompat.bytestr(bool(branchmerge))
753 boolbm = pycompat.bytestr(bool(branchmerge))
754 boolf = pycompat.bytestr(bool(force))
754 boolf = pycompat.bytestr(bool(force))
755 boolm = pycompat.bytestr(bool(matcher))
755 boolm = pycompat.bytestr(bool(matcher))
756 repo.ui.note(_(b"resolving manifests\n"))
756 repo.ui.note(_(b"resolving manifests\n"))
757 repo.ui.debug(
757 repo.ui.debug(
758 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
758 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
759 )
759 )
760 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
760 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
761
761
762 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
762 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
763 copied1 = set(branch_copies1.copy.values())
763 copied1 = set(branch_copies1.copy.values())
764 copied1.update(branch_copies1.movewithdir.values())
764 copied1.update(branch_copies1.movewithdir.values())
765 copied2 = set(branch_copies2.copy.values())
765 copied2 = set(branch_copies2.copy.values())
766 copied2.update(branch_copies2.movewithdir.values())
766 copied2.update(branch_copies2.movewithdir.values())
767
767
768 if b'.hgsubstate' in m1 and wctx.rev() is None:
768 if b'.hgsubstate' in m1 and wctx.rev() is None:
769 # Check whether sub state is modified, and overwrite the manifest
769 # Check whether sub state is modified, and overwrite the manifest
770 # to flag the change. If wctx is a committed revision, we shouldn't
770 # to flag the change. If wctx is a committed revision, we shouldn't
771 # care for the dirty state of the working directory.
771 # care for the dirty state of the working directory.
772 if any(wctx.sub(s).dirty() for s in wctx.substate):
772 if any(wctx.sub(s).dirty() for s in wctx.substate):
773 m1[b'.hgsubstate'] = modifiednodeid
773 m1[b'.hgsubstate'] = modifiednodeid
774
774
775 # Don't use m2-vs-ma optimization if:
775 # Don't use m2-vs-ma optimization if:
776 # - ma is the same as m1 or m2, which we're just going to diff again later
776 # - ma is the same as m1 or m2, which we're just going to diff again later
777 # - The caller specifically asks for a full diff, which is useful during bid
777 # - The caller specifically asks for a full diff, which is useful during bid
778 # merge.
778 # merge.
779 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
779 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
780 # Identify which files are relevant to the merge, so we can limit the
780 # Identify which files are relevant to the merge, so we can limit the
781 # total m1-vs-m2 diff to just those files. This has significant
781 # total m1-vs-m2 diff to just those files. This has significant
782 # performance benefits in large repositories.
782 # performance benefits in large repositories.
783 relevantfiles = set(ma.diff(m2).keys())
783 relevantfiles = set(ma.diff(m2).keys())
784
784
785 # For copied and moved files, we need to add the source file too.
785 # For copied and moved files, we need to add the source file too.
786 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
786 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
787 if copyvalue in relevantfiles:
787 if copyvalue in relevantfiles:
788 relevantfiles.add(copykey)
788 relevantfiles.add(copykey)
789 for movedirkey in branch_copies1.movewithdir:
789 for movedirkey in branch_copies1.movewithdir:
790 relevantfiles.add(movedirkey)
790 relevantfiles.add(movedirkey)
791 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
791 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
792 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
792 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
793
793
794 diff = m1.diff(m2, match=matcher)
794 diff = m1.diff(m2, match=matcher)
795
795
796 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
796 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
797 if n1 and n2: # file exists on both local and remote side
797 if n1 and n2: # file exists on both local and remote side
798 if f not in ma:
798 if f not in ma:
799 # TODO: what if they're renamed from different sources?
799 # TODO: what if they're renamed from different sources?
800 fa = branch_copies1.copy.get(
800 fa = branch_copies1.copy.get(
801 f, None
801 f, None
802 ) or branch_copies2.copy.get(f, None)
802 ) or branch_copies2.copy.get(f, None)
803 args, msg = None, None
803 args, msg = None, None
804 if fa is not None:
804 if fa is not None:
805 args = (f, f, fa, False, pa.node())
805 args = (f, f, fa, False, pa.node())
806 msg = b'both renamed from %s' % fa
806 msg = b'both renamed from %s' % fa
807 else:
807 else:
808 args = (f, f, None, False, pa.node())
808 args = (f, f, None, False, pa.node())
809 msg = b'both created'
809 msg = b'both created'
810 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
810 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
811 else:
811 else:
812 a = ma[f]
812 a = ma[f]
813 fla = ma.flags(f)
813 fla = ma.flags(f)
814 nol = b'l' not in fl1 + fl2 + fla
814 nol = b'l' not in fl1 + fl2 + fla
815 if n2 == a and fl2 == fla:
815 if n2 == a and fl2 == fla:
816 mresult.addfile(
816 mresult.addfile(
817 f, mergestatemod.ACTION_KEEP, (), b'remote unchanged',
817 f, mergestatemod.ACTION_KEEP, (), b'remote unchanged',
818 )
818 )
819 elif n1 == a and fl1 == fla: # local unchanged - use remote
819 elif n1 == a and fl1 == fla: # local unchanged - use remote
820 if n1 == n2: # optimization: keep local content
820 if n1 == n2: # optimization: keep local content
821 mresult.addfile(
821 mresult.addfile(
822 f,
822 f,
823 mergestatemod.ACTION_EXEC,
823 mergestatemod.ACTION_EXEC,
824 (fl2,),
824 (fl2,),
825 b'update permissions',
825 b'update permissions',
826 )
826 )
827 else:
827 else:
828 mresult.addfile(
828 mresult.addfile(
829 f,
829 f,
830 mergestatemod.ACTION_GET,
830 mergestatemod.ACTION_GET,
831 (fl2, False),
831 (fl2, False),
832 b'remote is newer',
832 b'remote is newer',
833 )
833 )
834 if branchmerge:
834 if branchmerge:
835 mresult.addcommitinfo(
835 mresult.addcommitinfo(
836 f, b'filenode-source', b'other'
836 f, b'filenode-source', b'other'
837 )
837 )
838 elif nol and n2 == a: # remote only changed 'x'
838 elif nol and n2 == a: # remote only changed 'x'
839 mresult.addfile(
839 mresult.addfile(
840 f,
840 f,
841 mergestatemod.ACTION_EXEC,
841 mergestatemod.ACTION_EXEC,
842 (fl2,),
842 (fl2,),
843 b'update permissions',
843 b'update permissions',
844 )
844 )
845 elif nol and n1 == a: # local only changed 'x'
845 elif nol and n1 == a: # local only changed 'x'
846 mresult.addfile(
846 mresult.addfile(
847 f,
847 f,
848 mergestatemod.ACTION_GET,
848 mergestatemod.ACTION_GET,
849 (fl1, False),
849 (fl1, False),
850 b'remote is newer',
850 b'remote is newer',
851 )
851 )
852 if branchmerge:
852 if branchmerge:
853 mresult.addcommitinfo(f, b'filenode-source', b'other')
853 mresult.addcommitinfo(f, b'filenode-source', b'other')
854 else: # both changed something
854 else: # both changed something
855 mresult.addfile(
855 mresult.addfile(
856 f,
856 f,
857 mergestatemod.ACTION_MERGE,
857 mergestatemod.ACTION_MERGE,
858 (f, f, f, False, pa.node()),
858 (f, f, f, False, pa.node()),
859 b'versions differ',
859 b'versions differ',
860 )
860 )
861 elif n1: # file exists only on local side
861 elif n1: # file exists only on local side
862 if f in copied2:
862 if f in copied2:
863 pass # we'll deal with it on m2 side
863 pass # we'll deal with it on m2 side
864 elif (
864 elif (
865 f in branch_copies1.movewithdir
865 f in branch_copies1.movewithdir
866 ): # directory rename, move local
866 ): # directory rename, move local
867 f2 = branch_copies1.movewithdir[f]
867 f2 = branch_copies1.movewithdir[f]
868 if f2 in m2:
868 if f2 in m2:
869 mresult.addfile(
869 mresult.addfile(
870 f2,
870 f2,
871 mergestatemod.ACTION_MERGE,
871 mergestatemod.ACTION_MERGE,
872 (f, f2, None, True, pa.node()),
872 (f, f2, None, True, pa.node()),
873 b'remote directory rename, both created',
873 b'remote directory rename, both created',
874 )
874 )
875 else:
875 else:
876 mresult.addfile(
876 mresult.addfile(
877 f2,
877 f2,
878 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
878 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
879 (f, fl1),
879 (f, fl1),
880 b'remote directory rename - move from %s' % f,
880 b'remote directory rename - move from %s' % f,
881 )
881 )
882 elif f in branch_copies1.copy:
882 elif f in branch_copies1.copy:
883 f2 = branch_copies1.copy[f]
883 f2 = branch_copies1.copy[f]
884 mresult.addfile(
884 mresult.addfile(
885 f,
885 f,
886 mergestatemod.ACTION_MERGE,
886 mergestatemod.ACTION_MERGE,
887 (f, f2, f2, False, pa.node()),
887 (f, f2, f2, False, pa.node()),
888 b'local copied/moved from %s' % f2,
888 b'local copied/moved from %s' % f2,
889 )
889 )
890 elif f in ma: # clean, a different, no remote
890 elif f in ma: # clean, a different, no remote
891 if n1 != ma[f]:
891 if n1 != ma[f]:
892 if acceptremote:
892 if acceptremote:
893 mresult.addfile(
893 mresult.addfile(
894 f,
894 f,
895 mergestatemod.ACTION_REMOVE,
895 mergestatemod.ACTION_REMOVE,
896 None,
896 None,
897 b'remote delete',
897 b'remote delete',
898 )
898 )
899 else:
899 else:
900 mresult.addfile(
900 mresult.addfile(
901 f,
901 f,
902 mergestatemod.ACTION_CHANGED_DELETED,
902 mergestatemod.ACTION_CHANGED_DELETED,
903 (f, None, f, False, pa.node()),
903 (f, None, f, False, pa.node()),
904 b'prompt changed/deleted',
904 b'prompt changed/deleted',
905 )
905 )
906 elif n1 == addednodeid:
906 elif n1 == addednodeid:
907 # This file was locally added. We should forget it instead of
907 # This file was locally added. We should forget it instead of
908 # deleting it.
908 # deleting it.
909 mresult.addfile(
909 mresult.addfile(
910 f, mergestatemod.ACTION_FORGET, None, b'remote deleted',
910 f, mergestatemod.ACTION_FORGET, None, b'remote deleted',
911 )
911 )
912 else:
912 else:
913 mresult.addfile(
913 mresult.addfile(
914 f, mergestatemod.ACTION_REMOVE, None, b'other deleted',
914 f, mergestatemod.ACTION_REMOVE, None, b'other deleted',
915 )
915 )
916 else: # file not in ancestor, not in remote
916 else: # file not in ancestor, not in remote
917 mresult.addfile(
917 mresult.addfile(
918 f,
918 f,
919 mergestatemod.ACTION_KEEP_NEW,
919 mergestatemod.ACTION_KEEP_NEW,
920 None,
920 None,
921 b'ancestor missing, remote missing',
921 b'ancestor missing, remote missing',
922 )
922 )
923
923
924 elif n2: # file exists only on remote side
924 elif n2: # file exists only on remote side
925 if f in copied1:
925 if f in copied1:
926 pass # we'll deal with it on m1 side
926 pass # we'll deal with it on m1 side
927 elif f in branch_copies2.movewithdir:
927 elif f in branch_copies2.movewithdir:
928 f2 = branch_copies2.movewithdir[f]
928 f2 = branch_copies2.movewithdir[f]
929 if f2 in m1:
929 if f2 in m1:
930 mresult.addfile(
930 mresult.addfile(
931 f2,
931 f2,
932 mergestatemod.ACTION_MERGE,
932 mergestatemod.ACTION_MERGE,
933 (f2, f, None, False, pa.node()),
933 (f2, f, None, False, pa.node()),
934 b'local directory rename, both created',
934 b'local directory rename, both created',
935 )
935 )
936 else:
936 else:
937 mresult.addfile(
937 mresult.addfile(
938 f2,
938 f2,
939 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
939 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
940 (f, fl2),
940 (f, fl2),
941 b'local directory rename - get from %s' % f,
941 b'local directory rename - get from %s' % f,
942 )
942 )
943 elif f in branch_copies2.copy:
943 elif f in branch_copies2.copy:
944 f2 = branch_copies2.copy[f]
944 f2 = branch_copies2.copy[f]
945 msg, args = None, None
945 msg, args = None, None
946 if f2 in m2:
946 if f2 in m2:
947 args = (f2, f, f2, False, pa.node())
947 args = (f2, f, f2, False, pa.node())
948 msg = b'remote copied from %s' % f2
948 msg = b'remote copied from %s' % f2
949 else:
949 else:
950 args = (f2, f, f2, True, pa.node())
950 args = (f2, f, f2, True, pa.node())
951 msg = b'remote moved from %s' % f2
951 msg = b'remote moved from %s' % f2
952 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
952 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
953 elif f not in ma:
953 elif f not in ma:
954 # local unknown, remote created: the logic is described by the
954 # local unknown, remote created: the logic is described by the
955 # following table:
955 # following table:
956 #
956 #
957 # force branchmerge different | action
957 # force branchmerge different | action
958 # n * * | create
958 # n * * | create
959 # y n * | create
959 # y n * | create
960 # y y n | create
960 # y y n | create
961 # y y y | merge
961 # y y y | merge
962 #
962 #
963 # Checking whether the files are different is expensive, so we
963 # Checking whether the files are different is expensive, so we
964 # don't do that when we can avoid it.
964 # don't do that when we can avoid it.
965 if not force:
965 if not force:
966 mresult.addfile(
966 mresult.addfile(
967 f,
967 f,
968 mergestatemod.ACTION_CREATED,
968 mergestatemod.ACTION_CREATED,
969 (fl2,),
969 (fl2,),
970 b'remote created',
970 b'remote created',
971 )
971 )
972 elif not branchmerge:
972 elif not branchmerge:
973 mresult.addfile(
973 mresult.addfile(
974 f,
974 f,
975 mergestatemod.ACTION_CREATED,
975 mergestatemod.ACTION_CREATED,
976 (fl2,),
976 (fl2,),
977 b'remote created',
977 b'remote created',
978 )
978 )
979 else:
979 else:
980 mresult.addfile(
980 mresult.addfile(
981 f,
981 f,
982 mergestatemod.ACTION_CREATED_MERGE,
982 mergestatemod.ACTION_CREATED_MERGE,
983 (fl2, pa.node()),
983 (fl2, pa.node()),
984 b'remote created, get or merge',
984 b'remote created, get or merge',
985 )
985 )
986 elif n2 != ma[f]:
986 elif n2 != ma[f]:
987 df = None
987 df = None
988 for d in branch_copies1.dirmove:
988 for d in branch_copies1.dirmove:
989 if f.startswith(d):
989 if f.startswith(d):
990 # new file added in a directory that was moved
990 # new file added in a directory that was moved
991 df = branch_copies1.dirmove[d] + f[len(d) :]
991 df = branch_copies1.dirmove[d] + f[len(d) :]
992 break
992 break
993 if df is not None and df in m1:
993 if df is not None and df in m1:
994 mresult.addfile(
994 mresult.addfile(
995 df,
995 df,
996 mergestatemod.ACTION_MERGE,
996 mergestatemod.ACTION_MERGE,
997 (df, f, f, False, pa.node()),
997 (df, f, f, False, pa.node()),
998 b'local directory rename - respect move '
998 b'local directory rename - respect move '
999 b'from %s' % f,
999 b'from %s' % f,
1000 )
1000 )
1001 elif acceptremote:
1001 elif acceptremote:
1002 mresult.addfile(
1002 mresult.addfile(
1003 f,
1003 f,
1004 mergestatemod.ACTION_CREATED,
1004 mergestatemod.ACTION_CREATED,
1005 (fl2,),
1005 (fl2,),
1006 b'remote recreating',
1006 b'remote recreating',
1007 )
1007 )
1008 else:
1008 else:
1009 mresult.addfile(
1009 mresult.addfile(
1010 f,
1010 f,
1011 mergestatemod.ACTION_DELETED_CHANGED,
1011 mergestatemod.ACTION_DELETED_CHANGED,
1012 (None, f, f, False, pa.node()),
1012 (None, f, f, False, pa.node()),
1013 b'prompt deleted/changed',
1013 b'prompt deleted/changed',
1014 )
1014 )
1015 else:
1015 else:
1016 mresult.addfile(
1016 mresult.addfile(
1017 f,
1017 f,
1018 mergestatemod.ACTION_KEEP_ABSENT,
1018 mergestatemod.ACTION_KEEP_ABSENT,
1019 None,
1019 None,
1020 b'local not present, remote unchanged',
1020 b'local not present, remote unchanged',
1021 )
1021 )
1022
1022
1023 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1023 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1024 # If we are merging, look for path conflicts.
1024 # If we are merging, look for path conflicts.
1025 checkpathconflicts(repo, wctx, p2, mresult)
1025 checkpathconflicts(repo, wctx, p2, mresult)
1026
1026
1027 narrowmatch = repo.narrowmatch()
1027 narrowmatch = repo.narrowmatch()
1028 if not narrowmatch.always():
1028 if not narrowmatch.always():
1029 # Updates "actions" in place
1029 # Updates "actions" in place
1030 _filternarrowactions(narrowmatch, branchmerge, mresult)
1030 _filternarrowactions(narrowmatch, branchmerge, mresult)
1031
1031
1032 renamedelete = branch_copies1.renamedelete
1032 renamedelete = branch_copies1.renamedelete
1033 renamedelete.update(branch_copies2.renamedelete)
1033 renamedelete.update(branch_copies2.renamedelete)
1034
1034
1035 mresult.updatevalues(diverge, renamedelete)
1035 mresult.updatevalues(diverge, renamedelete)
1036 return mresult
1036 return mresult
1037
1037
1038
1038
1039 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult):
1039 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult):
1040 """Resolves false conflicts where the nodeid changed but the content
1040 """Resolves false conflicts where the nodeid changed but the content
1041 remained the same."""
1041 remained the same."""
1042 # We force a copy of actions.items() because we're going to mutate
1042 # We force a copy of actions.items() because we're going to mutate
1043 # actions as we resolve trivial conflicts.
1043 # actions as we resolve trivial conflicts.
1044 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))):
1044 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))):
1045 if f in ancestor and not wctx[f].cmp(ancestor[f]):
1045 if f in ancestor and not wctx[f].cmp(ancestor[f]):
1046 # local did change but ended up with same content
1046 # local did change but ended up with same content
1047 mresult.addfile(
1047 mresult.addfile(
1048 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
1048 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
1049 )
1049 )
1050
1050
1051 for f in list(mresult.files((mergestatemod.ACTION_DELETED_CHANGED,))):
1051 for f in list(mresult.files((mergestatemod.ACTION_DELETED_CHANGED,))):
1052 if f in ancestor and not mctx[f].cmp(ancestor[f]):
1052 if f in ancestor and not mctx[f].cmp(ancestor[f]):
1053 # remote did change but ended up with same content
1053 # remote did change but ended up with same content
1054 mresult.removefile(f) # don't get = keep local deleted
1054 mresult.removefile(f) # don't get = keep local deleted
1055
1055
1056
1056
1057 def calculateupdates(
1057 def calculateupdates(
1058 repo,
1058 repo,
1059 wctx,
1059 wctx,
1060 mctx,
1060 mctx,
1061 ancestors,
1061 ancestors,
1062 branchmerge,
1062 branchmerge,
1063 force,
1063 force,
1064 acceptremote,
1064 acceptremote,
1065 followcopies,
1065 followcopies,
1066 matcher=None,
1066 matcher=None,
1067 mergeforce=False,
1067 mergeforce=False,
1068 ):
1068 ):
1069 """
1069 """
1070 Calculate the actions needed to merge mctx into wctx using ancestors
1070 Calculate the actions needed to merge mctx into wctx using ancestors
1071
1071
1072 Uses manifestmerge() to merge manifest and get list of actions required to
1072 Uses manifestmerge() to merge manifest and get list of actions required to
1073 perform for merging two manifests. If there are multiple ancestors, uses bid
1073 perform for merging two manifests. If there are multiple ancestors, uses bid
1074 merge if enabled.
1074 merge if enabled.
1075
1075
1076 Also filters out actions which are unrequired if repository is sparse.
1076 Also filters out actions which are unrequired if repository is sparse.
1077
1077
1078 Returns mergeresult object same as manifestmerge().
1078 Returns mergeresult object same as manifestmerge().
1079 """
1079 """
1080 # Avoid cycle.
1080 # Avoid cycle.
1081 from . import sparse
1081 from . import sparse
1082
1082
1083 mresult = None
1083 mresult = None
1084 if len(ancestors) == 1: # default
1084 if len(ancestors) == 1: # default
1085 mresult = manifestmerge(
1085 mresult = manifestmerge(
1086 repo,
1086 repo,
1087 wctx,
1087 wctx,
1088 mctx,
1088 mctx,
1089 ancestors[0],
1089 ancestors[0],
1090 branchmerge,
1090 branchmerge,
1091 force,
1091 force,
1092 matcher,
1092 matcher,
1093 acceptremote,
1093 acceptremote,
1094 followcopies,
1094 followcopies,
1095 )
1095 )
1096 _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce)
1096 _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce)
1097
1097
1098 else: # only when merge.preferancestor=* - the default
1098 else: # only when merge.preferancestor=* - the default
1099 repo.ui.note(
1099 repo.ui.note(
1100 _(b"note: merging %s and %s using bids from ancestors %s\n")
1100 _(b"note: merging %s and %s using bids from ancestors %s\n")
1101 % (
1101 % (
1102 wctx,
1102 wctx,
1103 mctx,
1103 mctx,
1104 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1104 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1105 )
1105 )
1106 )
1106 )
1107
1107
1108 # mapping filename to bids (action method to list af actions)
1108 # mapping filename to bids (action method to list af actions)
1109 # {FILENAME1 : BID1, FILENAME2 : BID2}
1109 # {FILENAME1 : BID1, FILENAME2 : BID2}
1110 # BID is another dictionary which contains
1110 # BID is another dictionary which contains
1111 # mapping of following form:
1111 # mapping of following form:
1112 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1112 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1113 fbids = {}
1113 fbids = {}
1114 mresult = mergeresult()
1114 mresult = mergeresult()
1115 diverge, renamedelete = None, None
1115 diverge, renamedelete = None, None
1116 for ancestor in ancestors:
1116 for ancestor in ancestors:
1117 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1117 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1118 mresult1 = manifestmerge(
1118 mresult1 = manifestmerge(
1119 repo,
1119 repo,
1120 wctx,
1120 wctx,
1121 mctx,
1121 mctx,
1122 ancestor,
1122 ancestor,
1123 branchmerge,
1123 branchmerge,
1124 force,
1124 force,
1125 matcher,
1125 matcher,
1126 acceptremote,
1126 acceptremote,
1127 followcopies,
1127 followcopies,
1128 forcefulldiff=True,
1128 forcefulldiff=True,
1129 )
1129 )
1130 _checkunknownfiles(repo, wctx, mctx, force, mresult1, mergeforce)
1130 _checkunknownfiles(repo, wctx, mctx, force, mresult1, mergeforce)
1131
1131
1132 # Track the shortest set of warning on the theory that bid
1132 # Track the shortest set of warning on the theory that bid
1133 # merge will correctly incorporate more information
1133 # merge will correctly incorporate more information
1134 if diverge is None or len(mresult1.diverge) < len(diverge):
1134 if diverge is None or len(mresult1.diverge) < len(diverge):
1135 diverge = mresult1.diverge
1135 diverge = mresult1.diverge
1136 if renamedelete is None or len(renamedelete) < len(
1136 if renamedelete is None or len(renamedelete) < len(
1137 mresult1.renamedelete
1137 mresult1.renamedelete
1138 ):
1138 ):
1139 renamedelete = mresult1.renamedelete
1139 renamedelete = mresult1.renamedelete
1140
1140
1141 # blindly update final mergeresult commitinfo with what we get
1141 # blindly update final mergeresult commitinfo with what we get
1142 # from mergeresult object for each ancestor
1142 # from mergeresult object for each ancestor
1143 # TODO: some commitinfo depends on what bid merge choose and hence
1143 # TODO: some commitinfo depends on what bid merge choose and hence
1144 # we will need to make commitinfo also depend on bid merge logic
1144 # we will need to make commitinfo also depend on bid merge logic
1145 mresult._commitinfo.update(mresult1._commitinfo)
1145 mresult._commitinfo.update(mresult1._commitinfo)
1146
1146
1147 for f, a in mresult1.filemap(sort=True):
1147 for f, a in mresult1.filemap(sort=True):
1148 m, args, msg = a
1148 m, args, msg = a
1149 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1149 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1150 if f in fbids:
1150 if f in fbids:
1151 d = fbids[f]
1151 d = fbids[f]
1152 if m in d:
1152 if m in d:
1153 d[m].append(a)
1153 d[m].append(a)
1154 else:
1154 else:
1155 d[m] = [a]
1155 d[m] = [a]
1156 else:
1156 else:
1157 fbids[f] = {m: [a]}
1157 fbids[f] = {m: [a]}
1158
1158
1159 # Call for bids
1159 # Call for bids
1160 # Pick the best bid for each file
1160 # Pick the best bid for each file
1161 repo.ui.note(
1161 repo.ui.note(
1162 _(b'\nauction for merging merge bids (%d ancestors)\n')
1162 _(b'\nauction for merging merge bids (%d ancestors)\n')
1163 % len(ancestors)
1163 % len(ancestors)
1164 )
1164 )
1165 for f, bids in sorted(fbids.items()):
1165 for f, bids in sorted(fbids.items()):
1166 if repo.ui.debugflag:
1166 if repo.ui.debugflag:
1167 repo.ui.debug(b" list of bids for %s:\n" % f)
1167 repo.ui.debug(b" list of bids for %s:\n" % f)
1168 for m, l in sorted(bids.items()):
1168 for m, l in sorted(bids.items()):
1169 for _f, args, msg in l:
1169 for _f, args, msg in l:
1170 repo.ui.debug(b' %s -> %s\n' % (msg, m))
1170 repo.ui.debug(b' %s -> %s\n' % (msg, m))
1171 # bids is a mapping from action method to list af actions
1171 # bids is a mapping from action method to list af actions
1172 # Consensus?
1172 # Consensus?
1173 if len(bids) == 1: # all bids are the same kind of method
1173 if len(bids) == 1: # all bids are the same kind of method
1174 m, l = list(bids.items())[0]
1174 m, l = list(bids.items())[0]
1175 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1175 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1176 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1176 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1177 mresult.addfile(f, *l[0])
1177 mresult.addfile(f, *l[0])
1178 continue
1178 continue
1179 # If keep is an option, just do it.
1179 # If keep is an option, just do it.
1180 if mergestatemod.ACTION_KEEP in bids:
1180 if mergestatemod.ACTION_KEEP in bids:
1181 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1181 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1182 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1182 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1183 continue
1183 continue
1184 # If keep absent is an option, just do that
1184 # If keep absent is an option, just do that
1185 if mergestatemod.ACTION_KEEP_ABSENT in bids:
1185 if mergestatemod.ACTION_KEEP_ABSENT in bids:
1186 repo.ui.note(_(b" %s: picking 'keep absent' action\n") % f)
1186 repo.ui.note(_(b" %s: picking 'keep absent' action\n") % f)
1187 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_ABSENT][0])
1187 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_ABSENT][0])
1188 continue
1188 continue
1189 # If keep new is an option, let's just do that
1189 # If keep new is an option, let's just do that
1190 if mergestatemod.ACTION_KEEP_NEW in bids:
1190 if mergestatemod.ACTION_KEEP_NEW in bids:
1191 repo.ui.note(_(b" %s: picking 'keep new' action\n") % f)
1191 repo.ui.note(_(b" %s: picking 'keep new' action\n") % f)
1192 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_NEW][0])
1192 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_NEW][0])
1193 continue
1193 continue
1194 # If there are gets and they all agree [how could they not?], do it.
1194 # If there are gets and they all agree [how could they not?], do it.
1195 if mergestatemod.ACTION_GET in bids:
1195 if mergestatemod.ACTION_GET in bids:
1196 ga0 = bids[mergestatemod.ACTION_GET][0]
1196 ga0 = bids[mergestatemod.ACTION_GET][0]
1197 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1197 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1198 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1198 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1199 mresult.addfile(f, *ga0)
1199 mresult.addfile(f, *ga0)
1200 continue
1200 continue
1201 # TODO: Consider other simple actions such as mode changes
1201 # TODO: Consider other simple actions such as mode changes
1202 # Handle inefficient democrazy.
1202 # Handle inefficient democrazy.
1203 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1203 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1204 for m, l in sorted(bids.items()):
1204 for m, l in sorted(bids.items()):
1205 for _f, args, msg in l:
1205 for _f, args, msg in l:
1206 repo.ui.note(b' %s -> %s\n' % (msg, m))
1206 repo.ui.note(b' %s -> %s\n' % (msg, m))
1207 # Pick random action. TODO: Instead, prompt user when resolving
1207 # Pick random action. TODO: Instead, prompt user when resolving
1208 m, l = list(bids.items())[0]
1208 m, l = list(bids.items())[0]
1209 repo.ui.warn(
1209 repo.ui.warn(
1210 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1210 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1211 )
1211 )
1212 mresult.addfile(f, *l[0])
1212 mresult.addfile(f, *l[0])
1213 continue
1213 continue
1214 repo.ui.note(_(b'end of auction\n\n'))
1214 repo.ui.note(_(b'end of auction\n\n'))
1215 mresult.updatevalues(diverge, renamedelete)
1215 mresult.updatevalues(diverge, renamedelete)
1216
1216
1217 if wctx.rev() is None:
1217 if wctx.rev() is None:
1218 _forgetremoved(wctx, mctx, branchmerge, mresult)
1218 _forgetremoved(wctx, mctx, branchmerge, mresult)
1219
1219
1220 sparse.filterupdatesactions(repo, wctx, mctx, branchmerge, mresult)
1220 sparse.filterupdatesactions(repo, wctx, mctx, branchmerge, mresult)
1221 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1221 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1222
1222
1223 return mresult
1223 return mresult
1224
1224
1225
1225
1226 def _getcwd():
1226 def _getcwd():
1227 try:
1227 try:
1228 return encoding.getcwd()
1228 return encoding.getcwd()
1229 except OSError as err:
1229 except OSError as err:
1230 if err.errno == errno.ENOENT:
1230 if err.errno == errno.ENOENT:
1231 return None
1231 return None
1232 raise
1232 raise
1233
1233
1234
1234
1235 def batchremove(repo, wctx, actions):
1235 def batchremove(repo, wctx, actions):
1236 """apply removes to the working directory
1236 """apply removes to the working directory
1237
1237
1238 yields tuples for progress updates
1238 yields tuples for progress updates
1239 """
1239 """
1240 verbose = repo.ui.verbose
1240 verbose = repo.ui.verbose
1241 cwd = _getcwd()
1241 cwd = _getcwd()
1242 i = 0
1242 i = 0
1243 for f, args, msg in actions:
1243 for f, args, msg in actions:
1244 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1244 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1245 if verbose:
1245 if verbose:
1246 repo.ui.note(_(b"removing %s\n") % f)
1246 repo.ui.note(_(b"removing %s\n") % f)
1247 wctx[f].audit()
1247 wctx[f].audit()
1248 try:
1248 try:
1249 wctx[f].remove(ignoremissing=True)
1249 wctx[f].remove(ignoremissing=True)
1250 except OSError as inst:
1250 except OSError as inst:
1251 repo.ui.warn(
1251 repo.ui.warn(
1252 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1252 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1253 )
1253 )
1254 if i == 100:
1254 if i == 100:
1255 yield i, f
1255 yield i, f
1256 i = 0
1256 i = 0
1257 i += 1
1257 i += 1
1258 if i > 0:
1258 if i > 0:
1259 yield i, f
1259 yield i, f
1260
1260
1261 if cwd and not _getcwd():
1261 if cwd and not _getcwd():
1262 # cwd was removed in the course of removing files; print a helpful
1262 # cwd was removed in the course of removing files; print a helpful
1263 # warning.
1263 # warning.
1264 repo.ui.warn(
1264 repo.ui.warn(
1265 _(
1265 _(
1266 b"current directory was removed\n"
1266 b"current directory was removed\n"
1267 b"(consider changing to repo root: %s)\n"
1267 b"(consider changing to repo root: %s)\n"
1268 )
1268 )
1269 % repo.root
1269 % repo.root
1270 )
1270 )
1271
1271
1272
1272
1273 def batchget(repo, mctx, wctx, wantfiledata, actions):
1273 def batchget(repo, mctx, wctx, wantfiledata, actions):
1274 """apply gets to the working directory
1274 """apply gets to the working directory
1275
1275
1276 mctx is the context to get from
1276 mctx is the context to get from
1277
1277
1278 Yields arbitrarily many (False, tuple) for progress updates, followed by
1278 Yields arbitrarily many (False, tuple) for progress updates, followed by
1279 exactly one (True, filedata). When wantfiledata is false, filedata is an
1279 exactly one (True, filedata). When wantfiledata is false, filedata is an
1280 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1280 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1281 mtime) of the file f written for each action.
1281 mtime) of the file f written for each action.
1282 """
1282 """
1283 filedata = {}
1283 filedata = {}
1284 verbose = repo.ui.verbose
1284 verbose = repo.ui.verbose
1285 fctx = mctx.filectx
1285 fctx = mctx.filectx
1286 ui = repo.ui
1286 ui = repo.ui
1287 i = 0
1287 i = 0
1288 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1288 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1289 for f, (flags, backup), msg in actions:
1289 for f, (flags, backup), msg in actions:
1290 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1290 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1291 if verbose:
1291 if verbose:
1292 repo.ui.note(_(b"getting %s\n") % f)
1292 repo.ui.note(_(b"getting %s\n") % f)
1293
1293
1294 if backup:
1294 if backup:
1295 # If a file or directory exists with the same name, back that
1295 # If a file or directory exists with the same name, back that
1296 # up. Otherwise, look to see if there is a file that conflicts
1296 # up. Otherwise, look to see if there is a file that conflicts
1297 # with a directory this file is in, and if so, back that up.
1297 # with a directory this file is in, and if so, back that up.
1298 conflicting = f
1298 conflicting = f
1299 if not repo.wvfs.lexists(f):
1299 if not repo.wvfs.lexists(f):
1300 for p in pathutil.finddirs(f):
1300 for p in pathutil.finddirs(f):
1301 if repo.wvfs.isfileorlink(p):
1301 if repo.wvfs.isfileorlink(p):
1302 conflicting = p
1302 conflicting = p
1303 break
1303 break
1304 if repo.wvfs.lexists(conflicting):
1304 if repo.wvfs.lexists(conflicting):
1305 orig = scmutil.backuppath(ui, repo, conflicting)
1305 orig = scmutil.backuppath(ui, repo, conflicting)
1306 util.rename(repo.wjoin(conflicting), orig)
1306 util.rename(repo.wjoin(conflicting), orig)
1307 wfctx = wctx[f]
1307 wfctx = wctx[f]
1308 wfctx.clearunknown()
1308 wfctx.clearunknown()
1309 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1309 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1310 size = wfctx.write(
1310 size = wfctx.write(
1311 fctx(f).data(),
1311 fctx(f).data(),
1312 flags,
1312 flags,
1313 backgroundclose=True,
1313 backgroundclose=True,
1314 atomictemp=atomictemp,
1314 atomictemp=atomictemp,
1315 )
1315 )
1316 if wantfiledata:
1316 if wantfiledata:
1317 s = wfctx.lstat()
1317 s = wfctx.lstat()
1318 mode = s.st_mode
1318 mode = s.st_mode
1319 mtime = s[stat.ST_MTIME]
1319 mtime = s[stat.ST_MTIME]
1320 filedata[f] = (mode, size, mtime) # for dirstate.normal
1320 filedata[f] = (mode, size, mtime) # for dirstate.normal
1321 if i == 100:
1321 if i == 100:
1322 yield False, (i, f)
1322 yield False, (i, f)
1323 i = 0
1323 i = 0
1324 i += 1
1324 i += 1
1325 if i > 0:
1325 if i > 0:
1326 yield False, (i, f)
1326 yield False, (i, f)
1327 yield True, filedata
1327 yield True, filedata
1328
1328
1329
1329
1330 def _prefetchfiles(repo, ctx, mresult):
1330 def _prefetchfiles(repo, ctx, mresult):
1331 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1331 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1332 of merge actions. ``ctx`` is the context being merged in."""
1332 of merge actions. ``ctx`` is the context being merged in."""
1333
1333
1334 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1334 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1335 # don't touch the context to be merged in. 'cd' is skipped, because
1335 # don't touch the context to be merged in. 'cd' is skipped, because
1336 # changed/deleted never resolves to something from the remote side.
1336 # changed/deleted never resolves to something from the remote side.
1337 files = mresult.files(
1337 files = mresult.files(
1338 [
1338 [
1339 mergestatemod.ACTION_GET,
1339 mergestatemod.ACTION_GET,
1340 mergestatemod.ACTION_DELETED_CHANGED,
1340 mergestatemod.ACTION_DELETED_CHANGED,
1341 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1341 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1342 mergestatemod.ACTION_MERGE,
1342 mergestatemod.ACTION_MERGE,
1343 ]
1343 ]
1344 )
1344 )
1345
1345
1346 prefetch = scmutil.prefetchfiles
1346 prefetch = scmutil.prefetchfiles
1347 matchfiles = scmutil.matchfiles
1347 matchfiles = scmutil.matchfiles
1348 prefetch(
1348 prefetch(
1349 repo, [(ctx.rev(), matchfiles(repo, files),)],
1349 repo, [(ctx.rev(), matchfiles(repo, files),)],
1350 )
1350 )
1351
1351
1352
1352
1353 @attr.s(frozen=True)
1353 @attr.s(frozen=True)
1354 class updateresult(object):
1354 class updateresult(object):
1355 updatedcount = attr.ib()
1355 updatedcount = attr.ib()
1356 mergedcount = attr.ib()
1356 mergedcount = attr.ib()
1357 removedcount = attr.ib()
1357 removedcount = attr.ib()
1358 unresolvedcount = attr.ib()
1358 unresolvedcount = attr.ib()
1359
1359
1360 def isempty(self):
1360 def isempty(self):
1361 return not (
1361 return not (
1362 self.updatedcount
1362 self.updatedcount
1363 or self.mergedcount
1363 or self.mergedcount
1364 or self.removedcount
1364 or self.removedcount
1365 or self.unresolvedcount
1365 or self.unresolvedcount
1366 )
1366 )
1367
1367
1368
1368
1369 def applyupdates(
1369 def applyupdates(
1370 repo, mresult, wctx, mctx, overwrite, wantfiledata, labels=None,
1370 repo, mresult, wctx, mctx, overwrite, wantfiledata, labels=None,
1371 ):
1371 ):
1372 """apply the merge action list to the working directory
1372 """apply the merge action list to the working directory
1373
1373
1374 mresult is a mergeresult object representing result of the merge
1374 mresult is a mergeresult object representing result of the merge
1375 wctx is the working copy context
1375 wctx is the working copy context
1376 mctx is the context to be merged into the working copy
1376 mctx is the context to be merged into the working copy
1377
1377
1378 Return a tuple of (counts, filedata), where counts is a tuple
1378 Return a tuple of (counts, filedata), where counts is a tuple
1379 (updated, merged, removed, unresolved) that describes how many
1379 (updated, merged, removed, unresolved) that describes how many
1380 files were affected by the update, and filedata is as described in
1380 files were affected by the update, and filedata is as described in
1381 batchget.
1381 batchget.
1382 """
1382 """
1383
1383
1384 _prefetchfiles(repo, mctx, mresult)
1384 _prefetchfiles(repo, mctx, mresult)
1385
1385
1386 updated, merged, removed = 0, 0, 0
1386 updated, merged, removed = 0, 0, 0
1387 ms = wctx.mergestate(clean=True)
1387 ms = wctx.mergestate(clean=True)
1388 ms.start(wctx.p1().node(), mctx.node(), labels)
1388 ms.start(wctx.p1().node(), mctx.node(), labels)
1389
1389
1390 for f, op in pycompat.iteritems(mresult.commitinfo):
1390 for f, op in pycompat.iteritems(mresult.commitinfo):
1391 # the other side of filenode was choosen while merging, store this in
1391 # the other side of filenode was choosen while merging, store this in
1392 # mergestate so that it can be reused on commit
1392 # mergestate so that it can be reused on commit
1393 ms.addcommitinfo(f, op)
1393 ms.addcommitinfo(f, op)
1394
1394
1395 numupdates = mresult.len() - mresult.len(mergestatemod.NO_OP_ACTIONS)
1395 numupdates = mresult.len() - mresult.len(mergestatemod.NO_OP_ACTIONS)
1396 progress = repo.ui.makeprogress(
1396 progress = repo.ui.makeprogress(
1397 _(b'updating'), unit=_(b'files'), total=numupdates
1397 _(b'updating'), unit=_(b'files'), total=numupdates
1398 )
1398 )
1399
1399
1400 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_REMOVE]:
1400 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_REMOVE]:
1401 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1401 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1402
1402
1403 # record path conflicts
1403 # record path conflicts
1404 for f, args, msg in mresult.getactions(
1404 for f, args, msg in mresult.getactions(
1405 [mergestatemod.ACTION_PATH_CONFLICT], sort=True
1405 [mergestatemod.ACTION_PATH_CONFLICT], sort=True
1406 ):
1406 ):
1407 f1, fo = args
1407 f1, fo = args
1408 s = repo.ui.status
1408 s = repo.ui.status
1409 s(
1409 s(
1410 _(
1410 _(
1411 b"%s: path conflict - a file or link has the same name as a "
1411 b"%s: path conflict - a file or link has the same name as a "
1412 b"directory\n"
1412 b"directory\n"
1413 )
1413 )
1414 % f
1414 % f
1415 )
1415 )
1416 if fo == b'l':
1416 if fo == b'l':
1417 s(_(b"the local file has been renamed to %s\n") % f1)
1417 s(_(b"the local file has been renamed to %s\n") % f1)
1418 else:
1418 else:
1419 s(_(b"the remote file has been renamed to %s\n") % f1)
1419 s(_(b"the remote file has been renamed to %s\n") % f1)
1420 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1420 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1421 ms.addpathconflict(f, f1, fo)
1421 ms.addpathconflict(f, f1, fo)
1422 progress.increment(item=f)
1422 progress.increment(item=f)
1423
1423
1424 # When merging in-memory, we can't support worker processes, so set the
1424 # When merging in-memory, we can't support worker processes, so set the
1425 # per-item cost at 0 in that case.
1425 # per-item cost at 0 in that case.
1426 cost = 0 if wctx.isinmemory() else 0.001
1426 cost = 0 if wctx.isinmemory() else 0.001
1427
1427
1428 # remove in parallel (must come before resolving path conflicts and getting)
1428 # remove in parallel (must come before resolving path conflicts and getting)
1429 prog = worker.worker(
1429 prog = worker.worker(
1430 repo.ui,
1430 repo.ui,
1431 cost,
1431 cost,
1432 batchremove,
1432 batchremove,
1433 (repo, wctx),
1433 (repo, wctx),
1434 list(mresult.getactions([mergestatemod.ACTION_REMOVE], sort=True)),
1434 list(mresult.getactions([mergestatemod.ACTION_REMOVE], sort=True)),
1435 )
1435 )
1436 for i, item in prog:
1436 for i, item in prog:
1437 progress.increment(step=i, item=item)
1437 progress.increment(step=i, item=item)
1438 removed = mresult.len((mergestatemod.ACTION_REMOVE,))
1438 removed = mresult.len((mergestatemod.ACTION_REMOVE,))
1439
1439
1440 # resolve path conflicts (must come before getting)
1440 # resolve path conflicts (must come before getting)
1441 for f, args, msg in mresult.getactions(
1441 for f, args, msg in mresult.getactions(
1442 [mergestatemod.ACTION_PATH_CONFLICT_RESOLVE], sort=True
1442 [mergestatemod.ACTION_PATH_CONFLICT_RESOLVE], sort=True
1443 ):
1443 ):
1444 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1444 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1445 (f0, origf0) = args
1445 (f0, origf0) = args
1446 if wctx[f0].lexists():
1446 if wctx[f0].lexists():
1447 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1447 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1448 wctx[f].audit()
1448 wctx[f].audit()
1449 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1449 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1450 wctx[f0].remove()
1450 wctx[f0].remove()
1451 progress.increment(item=f)
1451 progress.increment(item=f)
1452
1452
1453 # get in parallel.
1453 # get in parallel.
1454 threadsafe = repo.ui.configbool(
1454 threadsafe = repo.ui.configbool(
1455 b'experimental', b'worker.wdir-get-thread-safe'
1455 b'experimental', b'worker.wdir-get-thread-safe'
1456 )
1456 )
1457 prog = worker.worker(
1457 prog = worker.worker(
1458 repo.ui,
1458 repo.ui,
1459 cost,
1459 cost,
1460 batchget,
1460 batchget,
1461 (repo, mctx, wctx, wantfiledata),
1461 (repo, mctx, wctx, wantfiledata),
1462 list(mresult.getactions([mergestatemod.ACTION_GET], sort=True)),
1462 list(mresult.getactions([mergestatemod.ACTION_GET], sort=True)),
1463 threadsafe=threadsafe,
1463 threadsafe=threadsafe,
1464 hasretval=True,
1464 hasretval=True,
1465 )
1465 )
1466 getfiledata = {}
1466 getfiledata = {}
1467 for final, res in prog:
1467 for final, res in prog:
1468 if final:
1468 if final:
1469 getfiledata = res
1469 getfiledata = res
1470 else:
1470 else:
1471 i, item = res
1471 i, item = res
1472 progress.increment(step=i, item=item)
1472 progress.increment(step=i, item=item)
1473
1473
1474 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_GET]:
1474 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_GET]:
1475 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1475 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1476
1476
1477 # forget (manifest only, just log it) (must come first)
1477 # forget (manifest only, just log it) (must come first)
1478 for f, args, msg in mresult.getactions(
1478 for f, args, msg in mresult.getactions(
1479 (mergestatemod.ACTION_FORGET,), sort=True
1479 (mergestatemod.ACTION_FORGET,), sort=True
1480 ):
1480 ):
1481 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1481 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1482 progress.increment(item=f)
1482 progress.increment(item=f)
1483
1483
1484 # re-add (manifest only, just log it)
1484 # re-add (manifest only, just log it)
1485 for f, args, msg in mresult.getactions(
1485 for f, args, msg in mresult.getactions(
1486 (mergestatemod.ACTION_ADD,), sort=True
1486 (mergestatemod.ACTION_ADD,), sort=True
1487 ):
1487 ):
1488 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1488 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1489 progress.increment(item=f)
1489 progress.increment(item=f)
1490
1490
1491 # re-add/mark as modified (manifest only, just log it)
1491 # re-add/mark as modified (manifest only, just log it)
1492 for f, args, msg in mresult.getactions(
1492 for f, args, msg in mresult.getactions(
1493 (mergestatemod.ACTION_ADD_MODIFIED,), sort=True
1493 (mergestatemod.ACTION_ADD_MODIFIED,), sort=True
1494 ):
1494 ):
1495 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1495 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1496 progress.increment(item=f)
1496 progress.increment(item=f)
1497
1497
1498 # keep (noop, just log it)
1498 # keep (noop, just log it)
1499 for a in mergestatemod.NO_OP_ACTIONS:
1499 for a in mergestatemod.NO_OP_ACTIONS:
1500 for f, args, msg in mresult.getactions((a,), sort=True):
1500 for f, args, msg in mresult.getactions((a,), sort=True):
1501 repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a))
1501 repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a))
1502 # no progress
1502 # no progress
1503
1503
1504 # directory rename, move local
1504 # directory rename, move local
1505 for f, args, msg in mresult.getactions(
1505 for f, args, msg in mresult.getactions(
1506 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,), sort=True
1506 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,), sort=True
1507 ):
1507 ):
1508 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1508 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1509 progress.increment(item=f)
1509 progress.increment(item=f)
1510 f0, flags = args
1510 f0, flags = args
1511 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1511 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1512 wctx[f].audit()
1512 wctx[f].audit()
1513 wctx[f].write(wctx.filectx(f0).data(), flags)
1513 wctx[f].write(wctx.filectx(f0).data(), flags)
1514 wctx[f0].remove()
1514 wctx[f0].remove()
1515
1515
1516 # local directory rename, get
1516 # local directory rename, get
1517 for f, args, msg in mresult.getactions(
1517 for f, args, msg in mresult.getactions(
1518 (mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,), sort=True
1518 (mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,), sort=True
1519 ):
1519 ):
1520 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1520 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1521 progress.increment(item=f)
1521 progress.increment(item=f)
1522 f0, flags = args
1522 f0, flags = args
1523 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1523 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1524 wctx[f].write(mctx.filectx(f0).data(), flags)
1524 wctx[f].write(mctx.filectx(f0).data(), flags)
1525
1525
1526 # exec
1526 # exec
1527 for f, args, msg in mresult.getactions(
1527 for f, args, msg in mresult.getactions(
1528 (mergestatemod.ACTION_EXEC,), sort=True
1528 (mergestatemod.ACTION_EXEC,), sort=True
1529 ):
1529 ):
1530 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1530 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1531 progress.increment(item=f)
1531 progress.increment(item=f)
1532 (flags,) = args
1532 (flags,) = args
1533 wctx[f].audit()
1533 wctx[f].audit()
1534 wctx[f].setflags(b'l' in flags, b'x' in flags)
1534 wctx[f].setflags(b'l' in flags, b'x' in flags)
1535
1535
1536 moves = []
1536 moves = []
1537
1537
1538 # 'cd' and 'dc' actions are treated like other merge conflicts
1538 # 'cd' and 'dc' actions are treated like other merge conflicts
1539 mergeactions = list(
1539 mergeactions = list(
1540 mresult.getactions(
1540 mresult.getactions(
1541 [
1541 [
1542 mergestatemod.ACTION_CHANGED_DELETED,
1542 mergestatemod.ACTION_CHANGED_DELETED,
1543 mergestatemod.ACTION_DELETED_CHANGED,
1543 mergestatemod.ACTION_DELETED_CHANGED,
1544 mergestatemod.ACTION_MERGE,
1544 mergestatemod.ACTION_MERGE,
1545 ],
1545 ],
1546 sort=True,
1546 sort=True,
1547 )
1547 )
1548 )
1548 )
1549 for f, args, msg in mergeactions:
1549 for f, args, msg in mergeactions:
1550 f1, f2, fa, move, anc = args
1550 f1, f2, fa, move, anc = args
1551 if f == b'.hgsubstate': # merged internally
1551 if f == b'.hgsubstate': # merged internally
1552 continue
1552 continue
1553 if f1 is None:
1553 if f1 is None:
1554 fcl = filemerge.absentfilectx(wctx, fa)
1554 fcl = filemerge.absentfilectx(wctx, fa)
1555 else:
1555 else:
1556 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1556 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1557 fcl = wctx[f1]
1557 fcl = wctx[f1]
1558 if f2 is None:
1558 if f2 is None:
1559 fco = filemerge.absentfilectx(mctx, fa)
1559 fco = filemerge.absentfilectx(mctx, fa)
1560 else:
1560 else:
1561 fco = mctx[f2]
1561 fco = mctx[f2]
1562 actx = repo[anc]
1562 actx = repo[anc]
1563 if fa in actx:
1563 if fa in actx:
1564 fca = actx[fa]
1564 fca = actx[fa]
1565 else:
1565 else:
1566 # TODO: move to absentfilectx
1566 # TODO: move to absentfilectx
1567 fca = repo.filectx(f1, fileid=nullrev)
1567 fca = repo.filectx(f1, fileid=nullrev)
1568 ms.add(fcl, fco, fca, f)
1568 ms.add(fcl, fco, fca, f)
1569 if f1 != f and move:
1569 if f1 != f and move:
1570 moves.append(f1)
1570 moves.append(f1)
1571
1571
1572 # remove renamed files after safely stored
1572 # remove renamed files after safely stored
1573 for f in moves:
1573 for f in moves:
1574 if wctx[f].lexists():
1574 if wctx[f].lexists():
1575 repo.ui.debug(b"removing %s\n" % f)
1575 repo.ui.debug(b"removing %s\n" % f)
1576 wctx[f].audit()
1576 wctx[f].audit()
1577 wctx[f].remove()
1577 wctx[f].remove()
1578
1578
1579 # these actions updates the file
1579 # these actions updates the file
1580 updated = mresult.len(
1580 updated = mresult.len(
1581 (
1581 (
1582 mergestatemod.ACTION_GET,
1582 mergestatemod.ACTION_GET,
1583 mergestatemod.ACTION_EXEC,
1583 mergestatemod.ACTION_EXEC,
1584 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1584 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1585 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1585 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1586 )
1586 )
1587 )
1587 )
1588
1588
1589 try:
1589 try:
1590 # premerge
1590 # premerge
1591 tocomplete = []
1591 tocomplete = []
1592 for f, args, msg in mergeactions:
1592 for f, args, msg in mergeactions:
1593 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1593 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1594 progress.increment(item=f)
1594 progress.increment(item=f)
1595 if f == b'.hgsubstate': # subrepo states need updating
1595 if f == b'.hgsubstate': # subrepo states need updating
1596 subrepoutil.submerge(
1596 subrepoutil.submerge(
1597 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1597 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1598 )
1598 )
1599 continue
1599 continue
1600 wctx[f].audit()
1600 wctx[f].audit()
1601 complete, r = ms.preresolve(f, wctx)
1601 complete, r = ms.preresolve(f, wctx)
1602 if not complete:
1602 if not complete:
1603 numupdates += 1
1603 numupdates += 1
1604 tocomplete.append((f, args, msg))
1604 tocomplete.append((f, args, msg))
1605
1605
1606 # merge
1606 # merge
1607 for f, args, msg in tocomplete:
1607 for f, args, msg in tocomplete:
1608 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1608 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1609 progress.increment(item=f, total=numupdates)
1609 progress.increment(item=f, total=numupdates)
1610 ms.resolve(f, wctx)
1610 ms.resolve(f, wctx)
1611
1611
1612 finally:
1612 finally:
1613 ms.commit()
1613 ms.commit()
1614
1614
1615 unresolved = ms.unresolvedcount()
1615 unresolved = ms.unresolvedcount()
1616
1616
1617 msupdated, msmerged, msremoved = ms.counts()
1617 msupdated, msmerged, msremoved = ms.counts()
1618 updated += msupdated
1618 updated += msupdated
1619 merged += msmerged
1619 merged += msmerged
1620 removed += msremoved
1620 removed += msremoved
1621
1621
1622 extraactions = ms.actions()
1622 extraactions = ms.actions()
1623 if extraactions:
1623 if extraactions:
1624 for k, acts in pycompat.iteritems(extraactions):
1624 for k, acts in pycompat.iteritems(extraactions):
1625 for a in acts:
1625 for a in acts:
1626 mresult.addfile(a[0], k, *a[1:])
1626 mresult.addfile(a[0], k, *a[1:])
1627 if k == mergestatemod.ACTION_GET and wantfiledata:
1627 if k == mergestatemod.ACTION_GET and wantfiledata:
1628 # no filedata until mergestate is updated to provide it
1628 # no filedata until mergestate is updated to provide it
1629 for a in acts:
1629 for a in acts:
1630 getfiledata[a[0]] = None
1630 getfiledata[a[0]] = None
1631
1631
1632 progress.complete()
1632 progress.complete()
1633 assert len(getfiledata) == (
1633 assert len(getfiledata) == (
1634 mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0
1634 mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0
1635 )
1635 )
1636 return updateresult(updated, merged, removed, unresolved), getfiledata
1636 return updateresult(updated, merged, removed, unresolved), getfiledata
1637
1637
1638
1638
1639 def _advertisefsmonitor(repo, num_gets, p1node):
1639 def _advertisefsmonitor(repo, num_gets, p1node):
1640 # Advertise fsmonitor when its presence could be useful.
1640 # Advertise fsmonitor when its presence could be useful.
1641 #
1641 #
1642 # We only advertise when performing an update from an empty working
1642 # We only advertise when performing an update from an empty working
1643 # directory. This typically only occurs during initial clone.
1643 # directory. This typically only occurs during initial clone.
1644 #
1644 #
1645 # We give users a mechanism to disable the warning in case it is
1645 # We give users a mechanism to disable the warning in case it is
1646 # annoying.
1646 # annoying.
1647 #
1647 #
1648 # We only allow on Linux and MacOS because that's where fsmonitor is
1648 # We only allow on Linux and MacOS because that's where fsmonitor is
1649 # considered stable.
1649 # considered stable.
1650 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1650 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1651 fsmonitorthreshold = repo.ui.configint(
1651 fsmonitorthreshold = repo.ui.configint(
1652 b'fsmonitor', b'warn_update_file_count'
1652 b'fsmonitor', b'warn_update_file_count'
1653 )
1653 )
1654 # avoid cycle dirstate -> sparse -> merge -> dirstate
1654 # avoid cycle dirstate -> sparse -> merge -> dirstate
1655 from . import dirstate
1655 from . import dirstate
1656
1656
1657 if dirstate.rustmod is not None:
1657 if dirstate.rustmod is not None:
1658 # When using rust status, fsmonitor becomes necessary at higher sizes
1658 # When using rust status, fsmonitor becomes necessary at higher sizes
1659 fsmonitorthreshold = repo.ui.configint(
1659 fsmonitorthreshold = repo.ui.configint(
1660 b'fsmonitor', b'warn_update_file_count_rust',
1660 b'fsmonitor', b'warn_update_file_count_rust',
1661 )
1661 )
1662
1662
1663 try:
1663 try:
1664 # avoid cycle: extensions -> cmdutil -> merge
1664 # avoid cycle: extensions -> cmdutil -> merge
1665 from . import extensions
1665 from . import extensions
1666
1666
1667 extensions.find(b'fsmonitor')
1667 extensions.find(b'fsmonitor')
1668 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1668 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1669 # We intentionally don't look at whether fsmonitor has disabled
1669 # We intentionally don't look at whether fsmonitor has disabled
1670 # itself because a) fsmonitor may have already printed a warning
1670 # itself because a) fsmonitor may have already printed a warning
1671 # b) we only care about the config state here.
1671 # b) we only care about the config state here.
1672 except KeyError:
1672 except KeyError:
1673 fsmonitorenabled = False
1673 fsmonitorenabled = False
1674
1674
1675 if (
1675 if (
1676 fsmonitorwarning
1676 fsmonitorwarning
1677 and not fsmonitorenabled
1677 and not fsmonitorenabled
1678 and p1node == nullid
1678 and p1node == nullid
1679 and num_gets >= fsmonitorthreshold
1679 and num_gets >= fsmonitorthreshold
1680 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1680 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1681 ):
1681 ):
1682 repo.ui.warn(
1682 repo.ui.warn(
1683 _(
1683 _(
1684 b'(warning: large working directory being used without '
1684 b'(warning: large working directory being used without '
1685 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1685 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1686 b'see "hg help -e fsmonitor")\n'
1686 b'see "hg help -e fsmonitor")\n'
1687 )
1687 )
1688 )
1688 )
1689
1689
1690
1690
1691 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1691 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1692 UPDATECHECK_NONE = b'none'
1692 UPDATECHECK_NONE = b'none'
1693 UPDATECHECK_LINEAR = b'linear'
1693 UPDATECHECK_LINEAR = b'linear'
1694 UPDATECHECK_NO_CONFLICT = b'noconflict'
1694 UPDATECHECK_NO_CONFLICT = b'noconflict'
1695
1695
1696
1696
1697 def update(
1697 def update(
1698 repo,
1698 repo,
1699 node,
1699 node,
1700 branchmerge,
1700 branchmerge,
1701 force,
1701 force,
1702 ancestor=None,
1702 ancestor=None,
1703 mergeancestor=False,
1703 mergeancestor=False,
1704 labels=None,
1704 labels=None,
1705 matcher=None,
1705 matcher=None,
1706 mergeforce=False,
1706 mergeforce=False,
1707 updatedirstate=True,
1707 updatedirstate=True,
1708 updatecheck=None,
1708 updatecheck=None,
1709 wc=None,
1709 wc=None,
1710 ):
1710 ):
1711 """
1711 """
1712 Perform a merge between the working directory and the given node
1712 Perform a merge between the working directory and the given node
1713
1713
1714 node = the node to update to
1714 node = the node to update to
1715 branchmerge = whether to merge between branches
1715 branchmerge = whether to merge between branches
1716 force = whether to force branch merging or file overwriting
1716 force = whether to force branch merging or file overwriting
1717 matcher = a matcher to filter file lists (dirstate not updated)
1717 matcher = a matcher to filter file lists (dirstate not updated)
1718 mergeancestor = whether it is merging with an ancestor. If true,
1718 mergeancestor = whether it is merging with an ancestor. If true,
1719 we should accept the incoming changes for any prompts that occur.
1719 we should accept the incoming changes for any prompts that occur.
1720 If false, merging with an ancestor (fast-forward) is only allowed
1720 If false, merging with an ancestor (fast-forward) is only allowed
1721 between different named branches. This flag is used by rebase extension
1721 between different named branches. This flag is used by rebase extension
1722 as a temporary fix and should be avoided in general.
1722 as a temporary fix and should be avoided in general.
1723 labels = labels to use for base, local and other
1723 labels = labels to use for base, local and other
1724 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1724 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1725 this is True, then 'force' should be True as well.
1725 this is True, then 'force' should be True as well.
1726
1726
1727 The table below shows all the behaviors of the update command given the
1727 The table below shows all the behaviors of the update command given the
1728 -c/--check and -C/--clean or no options, whether the working directory is
1728 -c/--check and -C/--clean or no options, whether the working directory is
1729 dirty, whether a revision is specified, and the relationship of the parent
1729 dirty, whether a revision is specified, and the relationship of the parent
1730 rev to the target rev (linear or not). Match from top first. The -n
1730 rev to the target rev (linear or not). Match from top first. The -n
1731 option doesn't exist on the command line, but represents the
1731 option doesn't exist on the command line, but represents the
1732 experimental.updatecheck=noconflict option.
1732 experimental.updatecheck=noconflict option.
1733
1733
1734 This logic is tested by test-update-branches.t.
1734 This logic is tested by test-update-branches.t.
1735
1735
1736 -c -C -n -m dirty rev linear | result
1736 -c -C -n -m dirty rev linear | result
1737 y y * * * * * | (1)
1737 y y * * * * * | (1)
1738 y * y * * * * | (1)
1738 y * y * * * * | (1)
1739 y * * y * * * | (1)
1739 y * * y * * * | (1)
1740 * y y * * * * | (1)
1740 * y y * * * * | (1)
1741 * y * y * * * | (1)
1741 * y * y * * * | (1)
1742 * * y y * * * | (1)
1742 * * y y * * * | (1)
1743 * * * * * n n | x
1743 * * * * * n n | x
1744 * * * * n * * | ok
1744 * * * * n * * | ok
1745 n n n n y * y | merge
1745 n n n n y * y | merge
1746 n n n n y y n | (2)
1746 n n n n y y n | (2)
1747 n n n y y * * | merge
1747 n n n y y * * | merge
1748 n n y n y * * | merge if no conflict
1748 n n y n y * * | merge if no conflict
1749 n y n n y * * | discard
1749 n y n n y * * | discard
1750 y n n n y * * | (3)
1750 y n n n y * * | (3)
1751
1751
1752 x = can't happen
1752 x = can't happen
1753 * = don't-care
1753 * = don't-care
1754 1 = incompatible options (checked in commands.py)
1754 1 = incompatible options (checked in commands.py)
1755 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1755 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1756 3 = abort: uncommitted changes (checked in commands.py)
1756 3 = abort: uncommitted changes (checked in commands.py)
1757
1757
1758 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1758 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1759 to repo[None] if None is passed.
1759 to repo[None] if None is passed.
1760
1760
1761 Return the same tuple as applyupdates().
1761 Return the same tuple as applyupdates().
1762 """
1762 """
1763 # Avoid cycle.
1763 # Avoid cycle.
1764 from . import sparse
1764 from . import sparse
1765
1765
1766 # This function used to find the default destination if node was None, but
1766 # This function used to find the default destination if node was None, but
1767 # that's now in destutil.py.
1767 # that's now in destutil.py.
1768 assert node is not None
1768 assert node is not None
1769 if not branchmerge and not force:
1769 if not branchmerge and not force:
1770 # TODO: remove the default once all callers that pass branchmerge=False
1770 # TODO: remove the default once all callers that pass branchmerge=False
1771 # and force=False pass a value for updatecheck. We may want to allow
1771 # and force=False pass a value for updatecheck. We may want to allow
1772 # updatecheck='abort' to better suppport some of these callers.
1772 # updatecheck='abort' to better suppport some of these callers.
1773 if updatecheck is None:
1773 if updatecheck is None:
1774 updatecheck = UPDATECHECK_LINEAR
1774 updatecheck = UPDATECHECK_LINEAR
1775 if updatecheck not in (
1775 if updatecheck not in (
1776 UPDATECHECK_NONE,
1776 UPDATECHECK_NONE,
1777 UPDATECHECK_LINEAR,
1777 UPDATECHECK_LINEAR,
1778 UPDATECHECK_NO_CONFLICT,
1778 UPDATECHECK_NO_CONFLICT,
1779 ):
1779 ):
1780 raise ValueError(
1780 raise ValueError(
1781 r'Invalid updatecheck %r (can accept %r)'
1781 r'Invalid updatecheck %r (can accept %r)'
1782 % (
1782 % (
1783 updatecheck,
1783 updatecheck,
1784 (
1784 (
1785 UPDATECHECK_NONE,
1785 UPDATECHECK_NONE,
1786 UPDATECHECK_LINEAR,
1786 UPDATECHECK_LINEAR,
1787 UPDATECHECK_NO_CONFLICT,
1787 UPDATECHECK_NO_CONFLICT,
1788 ),
1788 ),
1789 )
1789 )
1790 )
1790 )
1791 if wc is not None and wc.isinmemory():
1791 if wc is not None and wc.isinmemory():
1792 maybe_wlock = util.nullcontextmanager()
1792 maybe_wlock = util.nullcontextmanager()
1793 else:
1793 else:
1794 maybe_wlock = repo.wlock()
1794 maybe_wlock = repo.wlock()
1795 with maybe_wlock:
1795 with maybe_wlock:
1796 if wc is None:
1796 if wc is None:
1797 wc = repo[None]
1797 wc = repo[None]
1798 pl = wc.parents()
1798 pl = wc.parents()
1799 p1 = pl[0]
1799 p1 = pl[0]
1800 p2 = repo[node]
1800 p2 = repo[node]
1801 if ancestor is not None:
1801 if ancestor is not None:
1802 pas = [repo[ancestor]]
1802 pas = [repo[ancestor]]
1803 else:
1803 else:
1804 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1804 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1805 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1805 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1806 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1806 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1807 else:
1807 else:
1808 pas = [p1.ancestor(p2, warn=branchmerge)]
1808 pas = [p1.ancestor(p2, warn=branchmerge)]
1809
1809
1810 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1810 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1811
1811
1812 overwrite = force and not branchmerge
1812 overwrite = force and not branchmerge
1813 ### check phase
1813 ### check phase
1814 if not overwrite:
1814 if not overwrite:
1815 if len(pl) > 1:
1815 if len(pl) > 1:
1816 raise error.Abort(_(b"outstanding uncommitted merge"))
1816 raise error.Abort(_(b"outstanding uncommitted merge"))
1817 ms = wc.mergestate()
1817 ms = wc.mergestate()
1818 if list(ms.unresolved()):
1818 if list(ms.unresolved()):
1819 raise error.Abort(
1819 raise error.Abort(
1820 _(b"outstanding merge conflicts"),
1820 _(b"outstanding merge conflicts"),
1821 hint=_(b"use 'hg resolve' to resolve"),
1821 hint=_(b"use 'hg resolve' to resolve"),
1822 )
1822 )
1823 if branchmerge:
1823 if branchmerge:
1824 if pas == [p2]:
1824 if pas == [p2]:
1825 raise error.Abort(
1825 raise error.Abort(
1826 _(
1826 _(
1827 b"merging with a working directory ancestor"
1827 b"merging with a working directory ancestor"
1828 b" has no effect"
1828 b" has no effect"
1829 )
1829 )
1830 )
1830 )
1831 elif pas == [p1]:
1831 elif pas == [p1]:
1832 if not mergeancestor and wc.branch() == p2.branch():
1832 if not mergeancestor and wc.branch() == p2.branch():
1833 raise error.Abort(
1833 raise error.Abort(
1834 _(b"nothing to merge"),
1834 _(b"nothing to merge"),
1835 hint=_(b"use 'hg update' or check 'hg heads'"),
1835 hint=_(b"use 'hg update' or check 'hg heads'"),
1836 )
1836 )
1837 if not force and (wc.files() or wc.deleted()):
1837 if not force and (wc.files() or wc.deleted()):
1838 raise error.Abort(
1838 raise error.Abort(
1839 _(b"uncommitted changes"),
1839 _(b"uncommitted changes"),
1840 hint=_(b"use 'hg status' to list changes"),
1840 hint=_(b"use 'hg status' to list changes"),
1841 )
1841 )
1842 if not wc.isinmemory():
1842 if not wc.isinmemory():
1843 for s in sorted(wc.substate):
1843 for s in sorted(wc.substate):
1844 wc.sub(s).bailifchanged()
1844 wc.sub(s).bailifchanged()
1845
1845
1846 elif not overwrite:
1846 elif not overwrite:
1847 if p1 == p2: # no-op update
1847 if p1 == p2: # no-op update
1848 # call the hooks and exit early
1848 # call the hooks and exit early
1849 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1849 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1850 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1850 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1851 return updateresult(0, 0, 0, 0)
1851 return updateresult(0, 0, 0, 0)
1852
1852
1853 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1853 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1854 [p1],
1854 [p1],
1855 [p2],
1855 [p2],
1856 ): # nonlinear
1856 ): # nonlinear
1857 dirty = wc.dirty(missing=True)
1857 dirty = wc.dirty(missing=True)
1858 if dirty:
1858 if dirty:
1859 # Branching is a bit strange to ensure we do the minimal
1859 # Branching is a bit strange to ensure we do the minimal
1860 # amount of call to obsutil.foreground.
1860 # amount of call to obsutil.foreground.
1861 foreground = obsutil.foreground(repo, [p1.node()])
1861 foreground = obsutil.foreground(repo, [p1.node()])
1862 # note: the <node> variable contains a random identifier
1862 # note: the <node> variable contains a random identifier
1863 if repo[node].node() in foreground:
1863 if repo[node].node() in foreground:
1864 pass # allow updating to successors
1864 pass # allow updating to successors
1865 else:
1865 else:
1866 msg = _(b"uncommitted changes")
1866 msg = _(b"uncommitted changes")
1867 hint = _(b"commit or update --clean to discard changes")
1867 hint = _(b"commit or update --clean to discard changes")
1868 raise error.UpdateAbort(msg, hint=hint)
1868 raise error.UpdateAbort(msg, hint=hint)
1869 else:
1869 else:
1870 # Allow jumping branches if clean and specific rev given
1870 # Allow jumping branches if clean and specific rev given
1871 pass
1871 pass
1872
1872
1873 if overwrite:
1873 if overwrite:
1874 pas = [wc]
1874 pas = [wc]
1875 elif not branchmerge:
1875 elif not branchmerge:
1876 pas = [p1]
1876 pas = [p1]
1877
1877
1878 # deprecated config: merge.followcopies
1878 # deprecated config: merge.followcopies
1879 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1879 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1880 if overwrite:
1880 if overwrite:
1881 followcopies = False
1881 followcopies = False
1882 elif not pas[0]:
1882 elif not pas[0]:
1883 followcopies = False
1883 followcopies = False
1884 if not branchmerge and not wc.dirty(missing=True):
1884 if not branchmerge and not wc.dirty(missing=True):
1885 followcopies = False
1885 followcopies = False
1886
1886
1887 ### calculate phase
1887 ### calculate phase
1888 mresult = calculateupdates(
1888 mresult = calculateupdates(
1889 repo,
1889 repo,
1890 wc,
1890 wc,
1891 p2,
1891 p2,
1892 pas,
1892 pas,
1893 branchmerge,
1893 branchmerge,
1894 force,
1894 force,
1895 mergeancestor,
1895 mergeancestor,
1896 followcopies,
1896 followcopies,
1897 matcher=matcher,
1897 matcher=matcher,
1898 mergeforce=mergeforce,
1898 mergeforce=mergeforce,
1899 )
1899 )
1900
1900
1901 if updatecheck == UPDATECHECK_NO_CONFLICT:
1901 if updatecheck == UPDATECHECK_NO_CONFLICT:
1902 if mresult.hasconflicts():
1902 if mresult.hasconflicts():
1903 msg = _(b"conflicting changes")
1903 msg = _(b"conflicting changes")
1904 hint = _(b"commit or update --clean to discard changes")
1904 hint = _(b"commit or update --clean to discard changes")
1905 raise error.Abort(msg, hint=hint)
1905 raise error.Abort(msg, hint=hint)
1906
1906
1907 # Prompt and create actions. Most of this is in the resolve phase
1907 # Prompt and create actions. Most of this is in the resolve phase
1908 # already, but we can't handle .hgsubstate in filemerge or
1908 # already, but we can't handle .hgsubstate in filemerge or
1909 # subrepoutil.submerge yet so we have to keep prompting for it.
1909 # subrepoutil.submerge yet so we have to keep prompting for it.
1910 vals = mresult.getfile(b'.hgsubstate')
1910 vals = mresult.getfile(b'.hgsubstate')
1911 if vals:
1911 if vals:
1912 f = b'.hgsubstate'
1912 f = b'.hgsubstate'
1913 m, args, msg = vals
1913 m, args, msg = vals
1914 prompts = filemerge.partextras(labels)
1914 prompts = filemerge.partextras(labels)
1915 prompts[b'f'] = f
1915 prompts[b'f'] = f
1916 if m == mergestatemod.ACTION_CHANGED_DELETED:
1916 if m == mergestatemod.ACTION_CHANGED_DELETED:
1917 if repo.ui.promptchoice(
1917 if repo.ui.promptchoice(
1918 _(
1918 _(
1919 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1919 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1920 b"use (c)hanged version or (d)elete?"
1920 b"use (c)hanged version or (d)elete?"
1921 b"$$ &Changed $$ &Delete"
1921 b"$$ &Changed $$ &Delete"
1922 )
1922 )
1923 % prompts,
1923 % prompts,
1924 0,
1924 0,
1925 ):
1925 ):
1926 mresult.addfile(
1926 mresult.addfile(
1927 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
1927 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
1928 )
1928 )
1929 elif f in p1:
1929 elif f in p1:
1930 mresult.addfile(
1930 mresult.addfile(
1931 f,
1931 f,
1932 mergestatemod.ACTION_ADD_MODIFIED,
1932 mergestatemod.ACTION_ADD_MODIFIED,
1933 None,
1933 None,
1934 b'prompt keep',
1934 b'prompt keep',
1935 )
1935 )
1936 else:
1936 else:
1937 mresult.addfile(
1937 mresult.addfile(
1938 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
1938 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
1939 )
1939 )
1940 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1940 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1941 f1, f2, fa, move, anc = args
1941 f1, f2, fa, move, anc = args
1942 flags = p2[f2].flags()
1942 flags = p2[f2].flags()
1943 if (
1943 if (
1944 repo.ui.promptchoice(
1944 repo.ui.promptchoice(
1945 _(
1945 _(
1946 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1946 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1947 b"use (c)hanged version or leave (d)eleted?"
1947 b"use (c)hanged version or leave (d)eleted?"
1948 b"$$ &Changed $$ &Deleted"
1948 b"$$ &Changed $$ &Deleted"
1949 )
1949 )
1950 % prompts,
1950 % prompts,
1951 0,
1951 0,
1952 )
1952 )
1953 == 0
1953 == 0
1954 ):
1954 ):
1955 mresult.addfile(
1955 mresult.addfile(
1956 f,
1956 f,
1957 mergestatemod.ACTION_GET,
1957 mergestatemod.ACTION_GET,
1958 (flags, False),
1958 (flags, False),
1959 b'prompt recreating',
1959 b'prompt recreating',
1960 )
1960 )
1961 else:
1961 else:
1962 mresult.removefile(f)
1962 mresult.removefile(f)
1963
1963
1964 if not util.fscasesensitive(repo.path):
1964 if not util.fscasesensitive(repo.path):
1965 # check collision between files only in p2 for clean update
1965 # check collision between files only in p2 for clean update
1966 if not branchmerge and (
1966 if not branchmerge and (
1967 force or not wc.dirty(missing=True, branch=False)
1967 force or not wc.dirty(missing=True, branch=False)
1968 ):
1968 ):
1969 _checkcollision(repo, p2.manifest(), None)
1969 _checkcollision(repo, p2.manifest(), None)
1970 else:
1970 else:
1971 _checkcollision(repo, wc.manifest(), mresult)
1971 _checkcollision(repo, wc.manifest(), mresult)
1972
1972
1973 # divergent renames
1973 # divergent renames
1974 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
1974 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
1975 repo.ui.warn(
1975 repo.ui.warn(
1976 _(
1976 _(
1977 b"note: possible conflict - %s was renamed "
1977 b"note: possible conflict - %s was renamed "
1978 b"multiple times to:\n"
1978 b"multiple times to:\n"
1979 )
1979 )
1980 % f
1980 % f
1981 )
1981 )
1982 for nf in sorted(fl):
1982 for nf in sorted(fl):
1983 repo.ui.warn(b" %s\n" % nf)
1983 repo.ui.warn(b" %s\n" % nf)
1984
1984
1985 # rename and delete
1985 # rename and delete
1986 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
1986 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
1987 repo.ui.warn(
1987 repo.ui.warn(
1988 _(
1988 _(
1989 b"note: possible conflict - %s was deleted "
1989 b"note: possible conflict - %s was deleted "
1990 b"and renamed to:\n"
1990 b"and renamed to:\n"
1991 )
1991 )
1992 % f
1992 % f
1993 )
1993 )
1994 for nf in sorted(fl):
1994 for nf in sorted(fl):
1995 repo.ui.warn(b" %s\n" % nf)
1995 repo.ui.warn(b" %s\n" % nf)
1996
1996
1997 ### apply phase
1997 ### apply phase
1998 if not branchmerge: # just jump to the new rev
1998 if not branchmerge: # just jump to the new rev
1999 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1999 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2000 # If we're doing a partial update, we need to skip updating
2000 # If we're doing a partial update, we need to skip updating
2001 # the dirstate.
2001 # the dirstate.
2002 always = matcher is None or matcher.always()
2002 always = matcher is None or matcher.always()
2003 updatedirstate = updatedirstate and always and not wc.isinmemory()
2003 updatedirstate = updatedirstate and always and not wc.isinmemory()
2004 if updatedirstate:
2004 if updatedirstate:
2005 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2005 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2006 # note that we're in the middle of an update
2006 # note that we're in the middle of an update
2007 repo.vfs.write(b'updatestate', p2.hex())
2007 repo.vfs.write(b'updatestate', p2.hex())
2008
2008
2009 _advertisefsmonitor(
2009 _advertisefsmonitor(
2010 repo, mresult.len((mergestatemod.ACTION_GET,)), p1.node()
2010 repo, mresult.len((mergestatemod.ACTION_GET,)), p1.node()
2011 )
2011 )
2012
2012
2013 wantfiledata = updatedirstate and not branchmerge
2013 wantfiledata = updatedirstate and not branchmerge
2014 stats, getfiledata = applyupdates(
2014 stats, getfiledata = applyupdates(
2015 repo, mresult, wc, p2, overwrite, wantfiledata, labels=labels,
2015 repo, mresult, wc, p2, overwrite, wantfiledata, labels=labels,
2016 )
2016 )
2017
2017
2018 if updatedirstate:
2018 if updatedirstate:
2019 with repo.dirstate.parentchange():
2019 with repo.dirstate.parentchange():
2020 repo.setparents(fp1, fp2)
2020 repo.setparents(fp1, fp2)
2021 mergestatemod.recordupdates(
2021 mergestatemod.recordupdates(
2022 repo, mresult.actionsdict, branchmerge, getfiledata
2022 repo, mresult.actionsdict, branchmerge, getfiledata
2023 )
2023 )
2024 # update completed, clear state
2024 # update completed, clear state
2025 util.unlink(repo.vfs.join(b'updatestate'))
2025 util.unlink(repo.vfs.join(b'updatestate'))
2026
2026
2027 if not branchmerge:
2027 if not branchmerge:
2028 repo.dirstate.setbranch(p2.branch())
2028 repo.dirstate.setbranch(p2.branch())
2029
2029
2030 # If we're updating to a location, clean up any stale temporary includes
2030 # If we're updating to a location, clean up any stale temporary includes
2031 # (ex: this happens during hg rebase --abort).
2031 # (ex: this happens during hg rebase --abort).
2032 if not branchmerge:
2032 if not branchmerge:
2033 sparse.prunetemporaryincludes(repo)
2033 sparse.prunetemporaryincludes(repo)
2034
2034
2035 if updatedirstate:
2035 if updatedirstate:
2036 repo.hook(
2036 repo.hook(
2037 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2037 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2038 )
2038 )
2039 return stats
2039 return stats
2040
2040
2041
2041
2042 def merge(ctx, labels=None, force=False, wc=None):
2042 def merge(ctx, labels=None, force=False, wc=None):
2043 """Merge another topological branch into the working copy.
2043 """Merge another topological branch into the working copy.
2044
2044
2045 force = whether the merge was run with 'merge --force' (deprecated)
2045 force = whether the merge was run with 'merge --force' (deprecated)
2046 """
2046 """
2047
2047
2048 return update(
2048 return update(
2049 ctx.repo(),
2049 ctx.repo(),
2050 ctx.rev(),
2050 ctx.rev(),
2051 labels=labels,
2051 labels=labels,
2052 branchmerge=True,
2052 branchmerge=True,
2053 force=force,
2053 force=force,
2054 mergeforce=force,
2054 mergeforce=force,
2055 wc=wc,
2055 wc=wc,
2056 )
2056 )
2057
2057
2058
2058
2059 def clean_update(ctx, wc=None):
2059 def clean_update(ctx, wc=None):
2060 """Do a clean update to the given commit.
2060 """Do a clean update to the given commit.
2061
2061
2062 This involves updating to the commit and discarding any changes in the
2062 This involves updating to the commit and discarding any changes in the
2063 working copy.
2063 working copy.
2064 """
2064 """
2065 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2065 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2066
2066
2067
2067
2068 def revert_to(ctx, matcher=None, wc=None):
2068 def revert_to(ctx, matcher=None, wc=None):
2069 """Revert the working copy to the given commit.
2069 """Revert the working copy to the given commit.
2070
2070
2071 The working copy will keep its current parent(s) but its content will
2071 The working copy will keep its current parent(s) but its content will
2072 be the same as in the given commit.
2072 be the same as in the given commit.
2073 """
2073 """
2074
2074
2075 return update(
2075 return update(
2076 ctx.repo(),
2076 ctx.repo(),
2077 ctx.rev(),
2077 ctx.rev(),
2078 branchmerge=False,
2078 branchmerge=False,
2079 force=True,
2079 force=True,
2080 updatedirstate=False,
2080 updatedirstate=False,
2081 matcher=matcher,
2081 matcher=matcher,
2082 wc=wc,
2082 wc=wc,
2083 )
2083 )
2084
2084
2085
2085
2086 def graft(
2086 def graft(
2087 repo,
2087 repo,
2088 ctx,
2088 ctx,
2089 base=None,
2089 base=None,
2090 labels=None,
2090 labels=None,
2091 keepparent=False,
2091 keepparent=False,
2092 keepconflictparent=False,
2092 keepconflictparent=False,
2093 wctx=None,
2093 wctx=None,
2094 ):
2094 ):
2095 """Do a graft-like merge.
2095 """Do a graft-like merge.
2096
2096
2097 This is a merge where the merge ancestor is chosen such that one
2097 This is a merge where the merge ancestor is chosen such that one
2098 or more changesets are grafted onto the current changeset. In
2098 or more changesets are grafted onto the current changeset. In
2099 addition to the merge, this fixes up the dirstate to include only
2099 addition to the merge, this fixes up the dirstate to include only
2100 a single parent (if keepparent is False) and tries to duplicate any
2100 a single parent (if keepparent is False) and tries to duplicate any
2101 renames/copies appropriately.
2101 renames/copies appropriately.
2102
2102
2103 ctx - changeset to rebase
2103 ctx - changeset to rebase
2104 base - merge base, or ctx.p1() if not specified
2104 base - merge base, or ctx.p1() if not specified
2105 labels - merge labels eg ['local', 'graft']
2105 labels - merge labels eg ['local', 'graft']
2106 keepparent - keep second parent if any
2106 keepparent - keep second parent if any
2107 keepconflictparent - if unresolved, keep parent used for the merge
2107 keepconflictparent - if unresolved, keep parent used for the merge
2108
2108
2109 """
2109 """
2110 # If we're grafting a descendant onto an ancestor, be sure to pass
2110 # If we're grafting a descendant onto an ancestor, be sure to pass
2111 # mergeancestor=True to update. This does two things: 1) allows the merge if
2111 # mergeancestor=True to update. This does two things: 1) allows the merge if
2112 # the destination is the same as the parent of the ctx (so we can use graft
2112 # the destination is the same as the parent of the ctx (so we can use graft
2113 # to copy commits), and 2) informs update that the incoming changes are
2113 # to copy commits), and 2) informs update that the incoming changes are
2114 # newer than the destination so it doesn't prompt about "remote changed foo
2114 # newer than the destination so it doesn't prompt about "remote changed foo
2115 # which local deleted".
2115 # which local deleted".
2116 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2116 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2117 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2117 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2118 wctx = wctx or repo[None]
2118 wctx = wctx or repo[None]
2119 pctx = wctx.p1()
2119 pctx = wctx.p1()
2120 base = base or ctx.p1()
2120 base = base or ctx.p1()
2121 mergeancestor = (
2121 mergeancestor = (
2122 repo.changelog.isancestor(pctx.node(), ctx.node())
2122 repo.changelog.isancestor(pctx.node(), ctx.node())
2123 or pctx.rev() == base.rev()
2123 or pctx.rev() == base.rev()
2124 )
2124 )
2125
2125
2126 stats = update(
2126 stats = update(
2127 repo,
2127 repo,
2128 ctx.node(),
2128 ctx.node(),
2129 True,
2129 True,
2130 True,
2130 True,
2131 base.node(),
2131 base.node(),
2132 mergeancestor=mergeancestor,
2132 mergeancestor=mergeancestor,
2133 labels=labels,
2133 labels=labels,
2134 wc=wctx,
2134 wc=wctx,
2135 )
2135 )
2136
2136
2137 if keepconflictparent and stats.unresolvedcount:
2137 if keepconflictparent and stats.unresolvedcount:
2138 pother = ctx.node()
2138 pother = ctx.node()
2139 else:
2139 else:
2140 pother = nullid
2140 pother = nullid
2141 parents = ctx.parents()
2141 parents = ctx.parents()
2142 if keepparent and len(parents) == 2 and base in parents:
2142 if keepparent and len(parents) == 2 and base in parents:
2143 parents.remove(base)
2143 parents.remove(base)
2144 pother = parents[0].node()
2144 pother = parents[0].node()
2145 # Never set both parents equal to each other
2145 # Never set both parents equal to each other
2146 if pother == pctx.node():
2146 if pother == pctx.node():
2147 pother = nullid
2147 pother = nullid
2148
2148
2149 if wctx.isinmemory():
2149 if wctx.isinmemory():
2150 wctx.setparents(pctx.node(), pother)
2150 wctx.setparents(pctx.node(), pother)
2151 # fix up dirstate for copies and renames
2151 # fix up dirstate for copies and renames
2152 copies.graftcopies(wctx, ctx, base)
2152 copies.graftcopies(wctx, ctx, base)
2153 else:
2153 else:
2154 with repo.dirstate.parentchange():
2154 with repo.dirstate.parentchange():
2155 repo.setparents(pctx.node(), pother)
2155 repo.setparents(pctx.node(), pother)
2156 repo.dirstate.write(repo.currenttransaction())
2156 repo.dirstate.write(repo.currenttransaction())
2157 # fix up dirstate for copies and renames
2157 # fix up dirstate for copies and renames
2158 copies.graftcopies(wctx, ctx, base)
2158 copies.graftcopies(wctx, ctx, base)
2159 return stats
2159 return stats
2160
2160
2161
2161
2162 def back_out(ctx, parent=None, wc=None):
2163 if parent is None:
2164 if ctx.p2() is not None:
2165 raise error.ProgrammingError(
2166 b"must specify parent of merge commit to back out"
2167 )
2168 parent = ctx.p1()
2169 return update(
2170 ctx.repo(),
2171 parent,
2172 branchmerge=True,
2173 force=True,
2174 ancestor=ctx.node(),
2175 mergeancestor=False,
2176 )
2177
2178
2162 def purge(
2179 def purge(
2163 repo,
2180 repo,
2164 matcher,
2181 matcher,
2165 unknown=True,
2182 unknown=True,
2166 ignored=False,
2183 ignored=False,
2167 removeemptydirs=True,
2184 removeemptydirs=True,
2168 removefiles=True,
2185 removefiles=True,
2169 abortonerror=False,
2186 abortonerror=False,
2170 noop=False,
2187 noop=False,
2171 ):
2188 ):
2172 """Purge the working directory of untracked files.
2189 """Purge the working directory of untracked files.
2173
2190
2174 ``matcher`` is a matcher configured to scan the working directory -
2191 ``matcher`` is a matcher configured to scan the working directory -
2175 potentially a subset.
2192 potentially a subset.
2176
2193
2177 ``unknown`` controls whether unknown files should be purged.
2194 ``unknown`` controls whether unknown files should be purged.
2178
2195
2179 ``ignored`` controls whether ignored files should be purged.
2196 ``ignored`` controls whether ignored files should be purged.
2180
2197
2181 ``removeemptydirs`` controls whether empty directories should be removed.
2198 ``removeemptydirs`` controls whether empty directories should be removed.
2182
2199
2183 ``removefiles`` controls whether files are removed.
2200 ``removefiles`` controls whether files are removed.
2184
2201
2185 ``abortonerror`` causes an exception to be raised if an error occurs
2202 ``abortonerror`` causes an exception to be raised if an error occurs
2186 deleting a file or directory.
2203 deleting a file or directory.
2187
2204
2188 ``noop`` controls whether to actually remove files. If not defined, actions
2205 ``noop`` controls whether to actually remove files. If not defined, actions
2189 will be taken.
2206 will be taken.
2190
2207
2191 Returns an iterable of relative paths in the working directory that were
2208 Returns an iterable of relative paths in the working directory that were
2192 or would be removed.
2209 or would be removed.
2193 """
2210 """
2194
2211
2195 def remove(removefn, path):
2212 def remove(removefn, path):
2196 try:
2213 try:
2197 removefn(path)
2214 removefn(path)
2198 except OSError:
2215 except OSError:
2199 m = _(b'%s cannot be removed') % path
2216 m = _(b'%s cannot be removed') % path
2200 if abortonerror:
2217 if abortonerror:
2201 raise error.Abort(m)
2218 raise error.Abort(m)
2202 else:
2219 else:
2203 repo.ui.warn(_(b'warning: %s\n') % m)
2220 repo.ui.warn(_(b'warning: %s\n') % m)
2204
2221
2205 # There's no API to copy a matcher. So mutate the passed matcher and
2222 # There's no API to copy a matcher. So mutate the passed matcher and
2206 # restore it when we're done.
2223 # restore it when we're done.
2207 oldtraversedir = matcher.traversedir
2224 oldtraversedir = matcher.traversedir
2208
2225
2209 res = []
2226 res = []
2210
2227
2211 try:
2228 try:
2212 if removeemptydirs:
2229 if removeemptydirs:
2213 directories = []
2230 directories = []
2214 matcher.traversedir = directories.append
2231 matcher.traversedir = directories.append
2215
2232
2216 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2233 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2217
2234
2218 if removefiles:
2235 if removefiles:
2219 for f in sorted(status.unknown + status.ignored):
2236 for f in sorted(status.unknown + status.ignored):
2220 if not noop:
2237 if not noop:
2221 repo.ui.note(_(b'removing file %s\n') % f)
2238 repo.ui.note(_(b'removing file %s\n') % f)
2222 remove(repo.wvfs.unlink, f)
2239 remove(repo.wvfs.unlink, f)
2223 res.append(f)
2240 res.append(f)
2224
2241
2225 if removeemptydirs:
2242 if removeemptydirs:
2226 for f in sorted(directories, reverse=True):
2243 for f in sorted(directories, reverse=True):
2227 if matcher(f) and not repo.wvfs.listdir(f):
2244 if matcher(f) and not repo.wvfs.listdir(f):
2228 if not noop:
2245 if not noop:
2229 repo.ui.note(_(b'removing directory %s\n') % f)
2246 repo.ui.note(_(b'removing directory %s\n') % f)
2230 remove(repo.wvfs.rmdir, f)
2247 remove(repo.wvfs.rmdir, f)
2231 res.append(f)
2248 res.append(f)
2232
2249
2233 return res
2250 return res
2234
2251
2235 finally:
2252 finally:
2236 matcher.traversedir = oldtraversedir
2253 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now