##// END OF EJS Templates
mergestate: merge `preresolve()` into `resolve()`...
Martin von Zweigbergk -
r49256:38941a28 default
parent child Browse files
Show More

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

@@ -1,7974 +1,7950 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 wdirrev,
20 wdirrev,
21 )
21 )
22 from .pycompat import open
22 from .pycompat import open
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 bundlecaches,
27 bundlecaches,
28 changegroup,
28 changegroup,
29 cmdutil,
29 cmdutil,
30 copies,
30 copies,
31 debugcommands as debugcommandsmod,
31 debugcommands as debugcommandsmod,
32 destutil,
32 destutil,
33 dirstateguard,
33 dirstateguard,
34 discovery,
34 discovery,
35 encoding,
35 encoding,
36 error,
36 error,
37 exchange,
37 exchange,
38 extensions,
38 extensions,
39 filemerge,
39 filemerge,
40 formatter,
40 formatter,
41 graphmod,
41 graphmod,
42 grep as grepmod,
42 grep as grepmod,
43 hbisect,
43 hbisect,
44 help,
44 help,
45 hg,
45 hg,
46 logcmdutil,
46 logcmdutil,
47 merge as mergemod,
47 merge as mergemod,
48 mergestate as mergestatemod,
48 mergestate as mergestatemod,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 obsutil,
51 obsutil,
52 patch,
52 patch,
53 phases,
53 phases,
54 pycompat,
54 pycompat,
55 rcutil,
55 rcutil,
56 registrar,
56 registrar,
57 requirements,
57 requirements,
58 revsetlang,
58 revsetlang,
59 rewriteutil,
59 rewriteutil,
60 scmutil,
60 scmutil,
61 server,
61 server,
62 shelve as shelvemod,
62 shelve as shelvemod,
63 state as statemod,
63 state as statemod,
64 streamclone,
64 streamclone,
65 tags as tagsmod,
65 tags as tagsmod,
66 ui as uimod,
66 ui as uimod,
67 util,
67 util,
68 verify as verifymod,
68 verify as verifymod,
69 vfs as vfsmod,
69 vfs as vfsmod,
70 wireprotoserver,
70 wireprotoserver,
71 )
71 )
72 from .utils import (
72 from .utils import (
73 dateutil,
73 dateutil,
74 stringutil,
74 stringutil,
75 urlutil,
75 urlutil,
76 )
76 )
77
77
78 table = {}
78 table = {}
79 table.update(debugcommandsmod.command._table)
79 table.update(debugcommandsmod.command._table)
80
80
81 command = registrar.command(table)
81 command = registrar.command(table)
82 INTENT_READONLY = registrar.INTENT_READONLY
82 INTENT_READONLY = registrar.INTENT_READONLY
83
83
84 # common command options
84 # common command options
85
85
86 globalopts = [
86 globalopts = [
87 (
87 (
88 b'R',
88 b'R',
89 b'repository',
89 b'repository',
90 b'',
90 b'',
91 _(b'repository root directory or name of overlay bundle file'),
91 _(b'repository root directory or name of overlay bundle file'),
92 _(b'REPO'),
92 _(b'REPO'),
93 ),
93 ),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
95 (
95 (
96 b'y',
96 b'y',
97 b'noninteractive',
97 b'noninteractive',
98 None,
98 None,
99 _(
99 _(
100 b'do not prompt, automatically pick the first choice for all prompts'
100 b'do not prompt, automatically pick the first choice for all prompts'
101 ),
101 ),
102 ),
102 ),
103 (b'q', b'quiet', None, _(b'suppress output')),
103 (b'q', b'quiet', None, _(b'suppress output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
105 (
105 (
106 b'',
106 b'',
107 b'color',
107 b'color',
108 b'',
108 b'',
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
110 # and should not be translated
110 # and should not be translated
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
112 _(b'TYPE'),
112 _(b'TYPE'),
113 ),
113 ),
114 (
114 (
115 b'',
115 b'',
116 b'config',
116 b'config',
117 [],
117 [],
118 _(b'set/override config option (use \'section.name=value\')'),
118 _(b'set/override config option (use \'section.name=value\')'),
119 _(b'CONFIG'),
119 _(b'CONFIG'),
120 ),
120 ),
121 (b'', b'debug', None, _(b'enable debugging output')),
121 (b'', b'debug', None, _(b'enable debugging output')),
122 (b'', b'debugger', None, _(b'start debugger')),
122 (b'', b'debugger', None, _(b'start debugger')),
123 (
123 (
124 b'',
124 b'',
125 b'encoding',
125 b'encoding',
126 encoding.encoding,
126 encoding.encoding,
127 _(b'set the charset encoding'),
127 _(b'set the charset encoding'),
128 _(b'ENCODE'),
128 _(b'ENCODE'),
129 ),
129 ),
130 (
130 (
131 b'',
131 b'',
132 b'encodingmode',
132 b'encodingmode',
133 encoding.encodingmode,
133 encoding.encodingmode,
134 _(b'set the charset encoding mode'),
134 _(b'set the charset encoding mode'),
135 _(b'MODE'),
135 _(b'MODE'),
136 ),
136 ),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
138 (b'', b'time', None, _(b'time how long the command takes')),
138 (b'', b'time', None, _(b'time how long the command takes')),
139 (b'', b'profile', None, _(b'print command execution profile')),
139 (b'', b'profile', None, _(b'print command execution profile')),
140 (b'', b'version', None, _(b'output version information and exit')),
140 (b'', b'version', None, _(b'output version information and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
143 (
143 (
144 b'',
144 b'',
145 b'pager',
145 b'pager',
146 b'auto',
146 b'auto',
147 _(b"when to paginate (boolean, always, auto, or never)"),
147 _(b"when to paginate (boolean, always, auto, or never)"),
148 _(b'TYPE'),
148 _(b'TYPE'),
149 ),
149 ),
150 ]
150 ]
151
151
152 dryrunopts = cmdutil.dryrunopts
152 dryrunopts = cmdutil.dryrunopts
153 remoteopts = cmdutil.remoteopts
153 remoteopts = cmdutil.remoteopts
154 walkopts = cmdutil.walkopts
154 walkopts = cmdutil.walkopts
155 commitopts = cmdutil.commitopts
155 commitopts = cmdutil.commitopts
156 commitopts2 = cmdutil.commitopts2
156 commitopts2 = cmdutil.commitopts2
157 commitopts3 = cmdutil.commitopts3
157 commitopts3 = cmdutil.commitopts3
158 formatteropts = cmdutil.formatteropts
158 formatteropts = cmdutil.formatteropts
159 templateopts = cmdutil.templateopts
159 templateopts = cmdutil.templateopts
160 logopts = cmdutil.logopts
160 logopts = cmdutil.logopts
161 diffopts = cmdutil.diffopts
161 diffopts = cmdutil.diffopts
162 diffwsopts = cmdutil.diffwsopts
162 diffwsopts = cmdutil.diffwsopts
163 diffopts2 = cmdutil.diffopts2
163 diffopts2 = cmdutil.diffopts2
164 mergetoolopts = cmdutil.mergetoolopts
164 mergetoolopts = cmdutil.mergetoolopts
165 similarityopts = cmdutil.similarityopts
165 similarityopts = cmdutil.similarityopts
166 subrepoopts = cmdutil.subrepoopts
166 subrepoopts = cmdutil.subrepoopts
167 debugrevlogopts = cmdutil.debugrevlogopts
167 debugrevlogopts = cmdutil.debugrevlogopts
168
168
169 # Commands start here, listed alphabetically
169 # Commands start here, listed alphabetically
170
170
171
171
172 @command(
172 @command(
173 b'abort',
173 b'abort',
174 dryrunopts,
174 dryrunopts,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
176 helpbasic=True,
176 helpbasic=True,
177 )
177 )
178 def abort(ui, repo, **opts):
178 def abort(ui, repo, **opts):
179 """abort an unfinished operation (EXPERIMENTAL)
179 """abort an unfinished operation (EXPERIMENTAL)
180
180
181 Aborts a multistep operation like graft, histedit, rebase, merge,
181 Aborts a multistep operation like graft, histedit, rebase, merge,
182 and unshelve if they are in an unfinished state.
182 and unshelve if they are in an unfinished state.
183
183
184 use --dry-run/-n to dry run the command.
184 use --dry-run/-n to dry run the command.
185 """
185 """
186 dryrun = opts.get('dry_run')
186 dryrun = opts.get('dry_run')
187 abortstate = cmdutil.getunfinishedstate(repo)
187 abortstate = cmdutil.getunfinishedstate(repo)
188 if not abortstate:
188 if not abortstate:
189 raise error.StateError(_(b'no operation in progress'))
189 raise error.StateError(_(b'no operation in progress'))
190 if not abortstate.abortfunc:
190 if not abortstate.abortfunc:
191 raise error.InputError(
191 raise error.InputError(
192 (
192 (
193 _(b"%s in progress but does not support 'hg abort'")
193 _(b"%s in progress but does not support 'hg abort'")
194 % (abortstate._opname)
194 % (abortstate._opname)
195 ),
195 ),
196 hint=abortstate.hint(),
196 hint=abortstate.hint(),
197 )
197 )
198 if dryrun:
198 if dryrun:
199 ui.status(
199 ui.status(
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
201 )
201 )
202 return
202 return
203 return abortstate.abortfunc(ui, repo)
203 return abortstate.abortfunc(ui, repo)
204
204
205
205
206 @command(
206 @command(
207 b'add',
207 b'add',
208 walkopts + subrepoopts + dryrunopts,
208 walkopts + subrepoopts + dryrunopts,
209 _(b'[OPTION]... [FILE]...'),
209 _(b'[OPTION]... [FILE]...'),
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
211 helpbasic=True,
211 helpbasic=True,
212 inferrepo=True,
212 inferrepo=True,
213 )
213 )
214 def add(ui, repo, *pats, **opts):
214 def add(ui, repo, *pats, **opts):
215 """add the specified files on the next commit
215 """add the specified files on the next commit
216
216
217 Schedule files to be version controlled and added to the
217 Schedule files to be version controlled and added to the
218 repository.
218 repository.
219
219
220 The files will be added to the repository at the next commit. To
220 The files will be added to the repository at the next commit. To
221 undo an add before that, see :hg:`forget`.
221 undo an add before that, see :hg:`forget`.
222
222
223 If no names are given, add all files to the repository (except
223 If no names are given, add all files to the repository (except
224 files matching ``.hgignore``).
224 files matching ``.hgignore``).
225
225
226 .. container:: verbose
226 .. container:: verbose
227
227
228 Examples:
228 Examples:
229
229
230 - New (unknown) files are added
230 - New (unknown) files are added
231 automatically by :hg:`add`::
231 automatically by :hg:`add`::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ? foo.c
236 ? foo.c
237 $ hg add
237 $ hg add
238 adding foo.c
238 adding foo.c
239 $ hg status
239 $ hg status
240 A foo.c
240 A foo.c
241
241
242 - Specific files to be added can be specified::
242 - Specific files to be added can be specified::
243
243
244 $ ls
244 $ ls
245 bar.c foo.c
245 bar.c foo.c
246 $ hg status
246 $ hg status
247 ? bar.c
247 ? bar.c
248 ? foo.c
248 ? foo.c
249 $ hg add bar.c
249 $ hg add bar.c
250 $ hg status
250 $ hg status
251 A bar.c
251 A bar.c
252 ? foo.c
252 ? foo.c
253
253
254 Returns 0 if all files are successfully added.
254 Returns 0 if all files are successfully added.
255 """
255 """
256
256
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 return rejected and 1 or 0
260 return rejected and 1 or 0
261
261
262
262
263 @command(
263 @command(
264 b'addremove',
264 b'addremove',
265 similarityopts + subrepoopts + walkopts + dryrunopts,
265 similarityopts + subrepoopts + walkopts + dryrunopts,
266 _(b'[OPTION]... [FILE]...'),
266 _(b'[OPTION]... [FILE]...'),
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 inferrepo=True,
268 inferrepo=True,
269 )
269 )
270 def addremove(ui, repo, *pats, **opts):
270 def addremove(ui, repo, *pats, **opts):
271 """add all new files, delete all missing files
271 """add all new files, delete all missing files
272
272
273 Add all new files and remove all missing files from the
273 Add all new files and remove all missing files from the
274 repository.
274 repository.
275
275
276 Unless names are given, new files are ignored if they match any of
276 Unless names are given, new files are ignored if they match any of
277 the patterns in ``.hgignore``. As with add, these changes take
277 the patterns in ``.hgignore``. As with add, these changes take
278 effect at the next commit.
278 effect at the next commit.
279
279
280 Use the -s/--similarity option to detect renamed files. This
280 Use the -s/--similarity option to detect renamed files. This
281 option takes a percentage between 0 (disabled) and 100 (files must
281 option takes a percentage between 0 (disabled) and 100 (files must
282 be identical) as its parameter. With a parameter greater than 0,
282 be identical) as its parameter. With a parameter greater than 0,
283 this compares every removed file with every added file and records
283 this compares every removed file with every added file and records
284 those similar enough as renames. Detecting renamed files this way
284 those similar enough as renames. Detecting renamed files this way
285 can be expensive. After using this option, :hg:`status -C` can be
285 can be expensive. After using this option, :hg:`status -C` can be
286 used to check which files were identified as moved or renamed. If
286 used to check which files were identified as moved or renamed. If
287 not specified, -s/--similarity defaults to 100 and only renames of
287 not specified, -s/--similarity defaults to 100 and only renames of
288 identical files are detected.
288 identical files are detected.
289
289
290 .. container:: verbose
290 .. container:: verbose
291
291
292 Examples:
292 Examples:
293
293
294 - A number of files (bar.c and foo.c) are new,
294 - A number of files (bar.c and foo.c) are new,
295 while foobar.c has been removed (without using :hg:`remove`)
295 while foobar.c has been removed (without using :hg:`remove`)
296 from the repository::
296 from the repository::
297
297
298 $ ls
298 $ ls
299 bar.c foo.c
299 bar.c foo.c
300 $ hg status
300 $ hg status
301 ! foobar.c
301 ! foobar.c
302 ? bar.c
302 ? bar.c
303 ? foo.c
303 ? foo.c
304 $ hg addremove
304 $ hg addremove
305 adding bar.c
305 adding bar.c
306 adding foo.c
306 adding foo.c
307 removing foobar.c
307 removing foobar.c
308 $ hg status
308 $ hg status
309 A bar.c
309 A bar.c
310 A foo.c
310 A foo.c
311 R foobar.c
311 R foobar.c
312
312
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
314 Afterwards, it was edited slightly::
314 Afterwards, it was edited slightly::
315
315
316 $ ls
316 $ ls
317 foo.c
317 foo.c
318 $ hg status
318 $ hg status
319 ! foobar.c
319 ! foobar.c
320 ? foo.c
320 ? foo.c
321 $ hg addremove --similarity 90
321 $ hg addremove --similarity 90
322 removing foobar.c
322 removing foobar.c
323 adding foo.c
323 adding foo.c
324 recording removal of foobar.c as rename to foo.c (94% similar)
324 recording removal of foobar.c as rename to foo.c (94% similar)
325 $ hg status -C
325 $ hg status -C
326 A foo.c
326 A foo.c
327 foobar.c
327 foobar.c
328 R foobar.c
328 R foobar.c
329
329
330 Returns 0 if all files are successfully added.
330 Returns 0 if all files are successfully added.
331 """
331 """
332 opts = pycompat.byteskwargs(opts)
332 opts = pycompat.byteskwargs(opts)
333 if not opts.get(b'similarity'):
333 if not opts.get(b'similarity'):
334 opts[b'similarity'] = b'100'
334 opts[b'similarity'] = b'100'
335 matcher = scmutil.match(repo[None], pats, opts)
335 matcher = scmutil.match(repo[None], pats, opts)
336 relative = scmutil.anypats(pats, opts)
336 relative = scmutil.anypats(pats, opts)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339
339
340
340
341 @command(
341 @command(
342 b'annotate|blame',
342 b'annotate|blame',
343 [
343 [
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (
345 (
346 b'',
346 b'',
347 b'follow',
347 b'follow',
348 None,
348 None,
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 ),
350 ),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'a', b'text', None, _(b'treat all files as text')),
352 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'f', b'file', None, _(b'list the filename')),
354 (b'f', b'file', None, _(b'list the filename')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
358 (
358 (
359 b'l',
359 b'l',
360 b'line-number',
360 b'line-number',
361 None,
361 None,
362 _(b'show line number at the first appearance'),
362 _(b'show line number at the first appearance'),
363 ),
363 ),
364 (
364 (
365 b'',
365 b'',
366 b'skip',
366 b'skip',
367 [],
367 [],
368 _(b'revset to not display (EXPERIMENTAL)'),
368 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'REV'),
369 _(b'REV'),
370 ),
370 ),
371 ]
371 ]
372 + diffwsopts
372 + diffwsopts
373 + walkopts
373 + walkopts
374 + formatteropts,
374 + formatteropts,
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpbasic=True,
377 helpbasic=True,
378 inferrepo=True,
378 inferrepo=True,
379 )
379 )
380 def annotate(ui, repo, *pats, **opts):
380 def annotate(ui, repo, *pats, **opts):
381 """show changeset information by line for each file
381 """show changeset information by line for each file
382
382
383 List changes in files, showing the revision id responsible for
383 List changes in files, showing the revision id responsible for
384 each line.
384 each line.
385
385
386 This command is useful for discovering when a change was made and
386 This command is useful for discovering when a change was made and
387 by whom.
387 by whom.
388
388
389 If you include --file, --user, or --date, the revision number is
389 If you include --file, --user, or --date, the revision number is
390 suppressed unless you also include --number.
390 suppressed unless you also include --number.
391
391
392 Without the -a/--text option, annotate will avoid processing files
392 Without the -a/--text option, annotate will avoid processing files
393 it detects as binary. With -a, annotate will annotate the file
393 it detects as binary. With -a, annotate will annotate the file
394 anyway, although the results will probably be neither useful
394 anyway, although the results will probably be neither useful
395 nor desirable.
395 nor desirable.
396
396
397 .. container:: verbose
397 .. container:: verbose
398
398
399 Template:
399 Template:
400
400
401 The following keywords are supported in addition to the common template
401 The following keywords are supported in addition to the common template
402 keywords and functions. See also :hg:`help templates`.
402 keywords and functions. See also :hg:`help templates`.
403
403
404 :lines: List of lines with annotation data.
404 :lines: List of lines with annotation data.
405 :path: String. Repository-absolute path of the specified file.
405 :path: String. Repository-absolute path of the specified file.
406
406
407 And each entry of ``{lines}`` provides the following sub-keywords in
407 And each entry of ``{lines}`` provides the following sub-keywords in
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409
409
410 :line: String. Line content.
410 :line: String. Line content.
411 :lineno: Integer. Line number at that revision.
411 :lineno: Integer. Line number at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
413
413
414 See :hg:`help templates.operators` for the list expansion syntax.
414 See :hg:`help templates.operators` for the list expansion syntax.
415
415
416 Returns 0 on success.
416 Returns 0 on success.
417 """
417 """
418 opts = pycompat.byteskwargs(opts)
418 opts = pycompat.byteskwargs(opts)
419 if not pats:
419 if not pats:
420 raise error.InputError(
420 raise error.InputError(
421 _(b'at least one filename or pattern is required')
421 _(b'at least one filename or pattern is required')
422 )
422 )
423
423
424 if opts.get(b'follow'):
424 if opts.get(b'follow'):
425 # --follow is deprecated and now just an alias for -f/--file
425 # --follow is deprecated and now just an alias for -f/--file
426 # to mimic the behavior of Mercurial before version 1.5
426 # to mimic the behavior of Mercurial before version 1.5
427 opts[b'file'] = True
427 opts[b'file'] = True
428
428
429 if (
429 if (
430 not opts.get(b'user')
430 not opts.get(b'user')
431 and not opts.get(b'changeset')
431 and not opts.get(b'changeset')
432 and not opts.get(b'date')
432 and not opts.get(b'date')
433 and not opts.get(b'file')
433 and not opts.get(b'file')
434 ):
434 ):
435 opts[b'number'] = True
435 opts[b'number'] = True
436
436
437 linenumber = opts.get(b'line_number') is not None
437 linenumber = opts.get(b'line_number') is not None
438 if (
438 if (
439 linenumber
439 linenumber
440 and (not opts.get(b'changeset'))
440 and (not opts.get(b'changeset'))
441 and (not opts.get(b'number'))
441 and (not opts.get(b'number'))
442 ):
442 ):
443 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
443 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
444
444
445 rev = opts.get(b'rev')
445 rev = opts.get(b'rev')
446 if rev:
446 if rev:
447 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
447 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
448 ctx = logcmdutil.revsingle(repo, rev)
448 ctx = logcmdutil.revsingle(repo, rev)
449
449
450 ui.pager(b'annotate')
450 ui.pager(b'annotate')
451 rootfm = ui.formatter(b'annotate', opts)
451 rootfm = ui.formatter(b'annotate', opts)
452 if ui.debugflag:
452 if ui.debugflag:
453 shorthex = pycompat.identity
453 shorthex = pycompat.identity
454 else:
454 else:
455
455
456 def shorthex(h):
456 def shorthex(h):
457 return h[:12]
457 return h[:12]
458
458
459 if ui.quiet:
459 if ui.quiet:
460 datefunc = dateutil.shortdate
460 datefunc = dateutil.shortdate
461 else:
461 else:
462 datefunc = dateutil.datestr
462 datefunc = dateutil.datestr
463 if ctx.rev() is None:
463 if ctx.rev() is None:
464 if opts.get(b'changeset'):
464 if opts.get(b'changeset'):
465 # omit "+" suffix which is appended to node hex
465 # omit "+" suffix which is appended to node hex
466 def formatrev(rev):
466 def formatrev(rev):
467 if rev == wdirrev:
467 if rev == wdirrev:
468 return b'%d' % ctx.p1().rev()
468 return b'%d' % ctx.p1().rev()
469 else:
469 else:
470 return b'%d' % rev
470 return b'%d' % rev
471
471
472 else:
472 else:
473
473
474 def formatrev(rev):
474 def formatrev(rev):
475 if rev == wdirrev:
475 if rev == wdirrev:
476 return b'%d+' % ctx.p1().rev()
476 return b'%d+' % ctx.p1().rev()
477 else:
477 else:
478 return b'%d ' % rev
478 return b'%d ' % rev
479
479
480 def formathex(h):
480 def formathex(h):
481 if h == repo.nodeconstants.wdirhex:
481 if h == repo.nodeconstants.wdirhex:
482 return b'%s+' % shorthex(hex(ctx.p1().node()))
482 return b'%s+' % shorthex(hex(ctx.p1().node()))
483 else:
483 else:
484 return b'%s ' % shorthex(h)
484 return b'%s ' % shorthex(h)
485
485
486 else:
486 else:
487 formatrev = b'%d'.__mod__
487 formatrev = b'%d'.__mod__
488 formathex = shorthex
488 formathex = shorthex
489
489
490 opmap = [
490 opmap = [
491 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
491 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
492 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
492 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
493 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
493 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
494 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
494 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
495 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
495 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
496 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
496 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
497 ]
497 ]
498 opnamemap = {
498 opnamemap = {
499 b'rev': b'number',
499 b'rev': b'number',
500 b'node': b'changeset',
500 b'node': b'changeset',
501 b'path': b'file',
501 b'path': b'file',
502 b'lineno': b'line_number',
502 b'lineno': b'line_number',
503 }
503 }
504
504
505 if rootfm.isplain():
505 if rootfm.isplain():
506
506
507 def makefunc(get, fmt):
507 def makefunc(get, fmt):
508 return lambda x: fmt(get(x))
508 return lambda x: fmt(get(x))
509
509
510 else:
510 else:
511
511
512 def makefunc(get, fmt):
512 def makefunc(get, fmt):
513 return get
513 return get
514
514
515 datahint = rootfm.datahint()
515 datahint = rootfm.datahint()
516 funcmap = [
516 funcmap = [
517 (makefunc(get, fmt), sep)
517 (makefunc(get, fmt), sep)
518 for fn, sep, get, fmt in opmap
518 for fn, sep, get, fmt in opmap
519 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
519 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 ]
520 ]
521 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
521 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
522 fields = b' '.join(
522 fields = b' '.join(
523 fn
523 fn
524 for fn, sep, get, fmt in opmap
524 for fn, sep, get, fmt in opmap
525 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
525 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
526 )
526 )
527
527
528 def bad(x, y):
528 def bad(x, y):
529 raise error.InputError(b"%s: %s" % (x, y))
529 raise error.InputError(b"%s: %s" % (x, y))
530
530
531 m = scmutil.match(ctx, pats, opts, badfn=bad)
531 m = scmutil.match(ctx, pats, opts, badfn=bad)
532
532
533 follow = not opts.get(b'no_follow')
533 follow = not opts.get(b'no_follow')
534 diffopts = patch.difffeatureopts(
534 diffopts = patch.difffeatureopts(
535 ui, opts, section=b'annotate', whitespace=True
535 ui, opts, section=b'annotate', whitespace=True
536 )
536 )
537 skiprevs = opts.get(b'skip')
537 skiprevs = opts.get(b'skip')
538 if skiprevs:
538 if skiprevs:
539 skiprevs = logcmdutil.revrange(repo, skiprevs)
539 skiprevs = logcmdutil.revrange(repo, skiprevs)
540
540
541 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
541 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
542 for abs in ctx.walk(m):
542 for abs in ctx.walk(m):
543 fctx = ctx[abs]
543 fctx = ctx[abs]
544 rootfm.startitem()
544 rootfm.startitem()
545 rootfm.data(path=abs)
545 rootfm.data(path=abs)
546 if not opts.get(b'text') and fctx.isbinary():
546 if not opts.get(b'text') and fctx.isbinary():
547 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
547 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
548 continue
548 continue
549
549
550 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
550 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
551 lines = fctx.annotate(
551 lines = fctx.annotate(
552 follow=follow, skiprevs=skiprevs, diffopts=diffopts
552 follow=follow, skiprevs=skiprevs, diffopts=diffopts
553 )
553 )
554 if not lines:
554 if not lines:
555 fm.end()
555 fm.end()
556 continue
556 continue
557 formats = []
557 formats = []
558 pieces = []
558 pieces = []
559
559
560 for f, sep in funcmap:
560 for f, sep in funcmap:
561 l = [f(n) for n in lines]
561 l = [f(n) for n in lines]
562 if fm.isplain():
562 if fm.isplain():
563 sizes = [encoding.colwidth(x) for x in l]
563 sizes = [encoding.colwidth(x) for x in l]
564 ml = max(sizes)
564 ml = max(sizes)
565 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
565 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
566 else:
566 else:
567 formats.append([b'%s'] * len(l))
567 formats.append([b'%s'] * len(l))
568 pieces.append(l)
568 pieces.append(l)
569
569
570 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
570 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
571 fm.startitem()
571 fm.startitem()
572 fm.context(fctx=n.fctx)
572 fm.context(fctx=n.fctx)
573 fm.write(fields, b"".join(f), *p)
573 fm.write(fields, b"".join(f), *p)
574 if n.skip:
574 if n.skip:
575 fmt = b"* %s"
575 fmt = b"* %s"
576 else:
576 else:
577 fmt = b": %s"
577 fmt = b": %s"
578 fm.write(b'line', fmt, n.text)
578 fm.write(b'line', fmt, n.text)
579
579
580 if not lines[-1].text.endswith(b'\n'):
580 if not lines[-1].text.endswith(b'\n'):
581 fm.plain(b'\n')
581 fm.plain(b'\n')
582 fm.end()
582 fm.end()
583
583
584 rootfm.end()
584 rootfm.end()
585
585
586
586
587 @command(
587 @command(
588 b'archive',
588 b'archive',
589 [
589 [
590 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
590 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
591 (
591 (
592 b'p',
592 b'p',
593 b'prefix',
593 b'prefix',
594 b'',
594 b'',
595 _(b'directory prefix for files in archive'),
595 _(b'directory prefix for files in archive'),
596 _(b'PREFIX'),
596 _(b'PREFIX'),
597 ),
597 ),
598 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
598 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
599 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
599 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
600 ]
600 ]
601 + subrepoopts
601 + subrepoopts
602 + walkopts,
602 + walkopts,
603 _(b'[OPTION]... DEST'),
603 _(b'[OPTION]... DEST'),
604 helpcategory=command.CATEGORY_IMPORT_EXPORT,
604 helpcategory=command.CATEGORY_IMPORT_EXPORT,
605 )
605 )
606 def archive(ui, repo, dest, **opts):
606 def archive(ui, repo, dest, **opts):
607 """create an unversioned archive of a repository revision
607 """create an unversioned archive of a repository revision
608
608
609 By default, the revision used is the parent of the working
609 By default, the revision used is the parent of the working
610 directory; use -r/--rev to specify a different revision.
610 directory; use -r/--rev to specify a different revision.
611
611
612 The archive type is automatically detected based on file
612 The archive type is automatically detected based on file
613 extension (to override, use -t/--type).
613 extension (to override, use -t/--type).
614
614
615 .. container:: verbose
615 .. container:: verbose
616
616
617 Examples:
617 Examples:
618
618
619 - create a zip file containing the 1.0 release::
619 - create a zip file containing the 1.0 release::
620
620
621 hg archive -r 1.0 project-1.0.zip
621 hg archive -r 1.0 project-1.0.zip
622
622
623 - create a tarball excluding .hg files::
623 - create a tarball excluding .hg files::
624
624
625 hg archive project.tar.gz -X ".hg*"
625 hg archive project.tar.gz -X ".hg*"
626
626
627 Valid types are:
627 Valid types are:
628
628
629 :``files``: a directory full of files (default)
629 :``files``: a directory full of files (default)
630 :``tar``: tar archive, uncompressed
630 :``tar``: tar archive, uncompressed
631 :``tbz2``: tar archive, compressed using bzip2
631 :``tbz2``: tar archive, compressed using bzip2
632 :``tgz``: tar archive, compressed using gzip
632 :``tgz``: tar archive, compressed using gzip
633 :``txz``: tar archive, compressed using lzma (only in Python 3)
633 :``txz``: tar archive, compressed using lzma (only in Python 3)
634 :``uzip``: zip archive, uncompressed
634 :``uzip``: zip archive, uncompressed
635 :``zip``: zip archive, compressed using deflate
635 :``zip``: zip archive, compressed using deflate
636
636
637 The exact name of the destination archive or directory is given
637 The exact name of the destination archive or directory is given
638 using a format string; see :hg:`help export` for details.
638 using a format string; see :hg:`help export` for details.
639
639
640 Each member added to an archive file has a directory prefix
640 Each member added to an archive file has a directory prefix
641 prepended. Use -p/--prefix to specify a format string for the
641 prepended. Use -p/--prefix to specify a format string for the
642 prefix. The default is the basename of the archive, with suffixes
642 prefix. The default is the basename of the archive, with suffixes
643 removed.
643 removed.
644
644
645 Returns 0 on success.
645 Returns 0 on success.
646 """
646 """
647
647
648 opts = pycompat.byteskwargs(opts)
648 opts = pycompat.byteskwargs(opts)
649 rev = opts.get(b'rev')
649 rev = opts.get(b'rev')
650 if rev:
650 if rev:
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 ctx = logcmdutil.revsingle(repo, rev)
652 ctx = logcmdutil.revsingle(repo, rev)
653 if not ctx:
653 if not ctx:
654 raise error.InputError(
654 raise error.InputError(
655 _(b'no working directory: please specify a revision')
655 _(b'no working directory: please specify a revision')
656 )
656 )
657 node = ctx.node()
657 node = ctx.node()
658 dest = cmdutil.makefilename(ctx, dest)
658 dest = cmdutil.makefilename(ctx, dest)
659 if os.path.realpath(dest) == repo.root:
659 if os.path.realpath(dest) == repo.root:
660 raise error.InputError(_(b'repository root cannot be destination'))
660 raise error.InputError(_(b'repository root cannot be destination'))
661
661
662 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
662 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
663 prefix = opts.get(b'prefix')
663 prefix = opts.get(b'prefix')
664
664
665 if dest == b'-':
665 if dest == b'-':
666 if kind == b'files':
666 if kind == b'files':
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 dest = cmdutil.makefileobj(ctx, dest)
668 dest = cmdutil.makefileobj(ctx, dest)
669 if not prefix:
669 if not prefix:
670 prefix = os.path.basename(repo.root) + b'-%h'
670 prefix = os.path.basename(repo.root) + b'-%h'
671
671
672 prefix = cmdutil.makefilename(ctx, prefix)
672 prefix = cmdutil.makefilename(ctx, prefix)
673 match = scmutil.match(ctx, [], opts)
673 match = scmutil.match(ctx, [], opts)
674 archival.archive(
674 archival.archive(
675 repo,
675 repo,
676 dest,
676 dest,
677 node,
677 node,
678 kind,
678 kind,
679 not opts.get(b'no_decode'),
679 not opts.get(b'no_decode'),
680 match,
680 match,
681 prefix,
681 prefix,
682 subrepos=opts.get(b'subrepos'),
682 subrepos=opts.get(b'subrepos'),
683 )
683 )
684
684
685
685
686 @command(
686 @command(
687 b'backout',
687 b'backout',
688 [
688 [
689 (
689 (
690 b'',
690 b'',
691 b'merge',
691 b'merge',
692 None,
692 None,
693 _(b'merge with old dirstate parent after backout'),
693 _(b'merge with old dirstate parent after backout'),
694 ),
694 ),
695 (
695 (
696 b'',
696 b'',
697 b'commit',
697 b'commit',
698 None,
698 None,
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 ),
700 ),
701 (b'', b'no-commit', None, _(b'do not commit')),
701 (b'', b'no-commit', None, _(b'do not commit')),
702 (
702 (
703 b'',
703 b'',
704 b'parent',
704 b'parent',
705 b'',
705 b'',
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 _(b'REV'),
707 _(b'REV'),
708 ),
708 ),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 ]
711 ]
712 + mergetoolopts
712 + mergetoolopts
713 + walkopts
713 + walkopts
714 + commitopts
714 + commitopts
715 + commitopts2,
715 + commitopts2,
716 _(b'[OPTION]... [-r] REV'),
716 _(b'[OPTION]... [-r] REV'),
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 )
718 )
719 def backout(ui, repo, node=None, rev=None, **opts):
719 def backout(ui, repo, node=None, rev=None, **opts):
720 """reverse effect of earlier changeset
720 """reverse effect of earlier changeset
721
721
722 Prepare a new changeset with the effect of REV undone in the
722 Prepare a new changeset with the effect of REV undone in the
723 current working directory. If no conflicts were encountered,
723 current working directory. If no conflicts were encountered,
724 it will be committed immediately.
724 it will be committed immediately.
725
725
726 If REV is the parent of the working directory, then this new changeset
726 If REV is the parent of the working directory, then this new changeset
727 is committed automatically (unless --no-commit is specified).
727 is committed automatically (unless --no-commit is specified).
728
728
729 .. note::
729 .. note::
730
730
731 :hg:`backout` cannot be used to fix either an unwanted or
731 :hg:`backout` cannot be used to fix either an unwanted or
732 incorrect merge.
732 incorrect merge.
733
733
734 .. container:: verbose
734 .. container:: verbose
735
735
736 Examples:
736 Examples:
737
737
738 - Reverse the effect of the parent of the working directory.
738 - Reverse the effect of the parent of the working directory.
739 This backout will be committed immediately::
739 This backout will be committed immediately::
740
740
741 hg backout -r .
741 hg backout -r .
742
742
743 - Reverse the effect of previous bad revision 23::
743 - Reverse the effect of previous bad revision 23::
744
744
745 hg backout -r 23
745 hg backout -r 23
746
746
747 - Reverse the effect of previous bad revision 23 and
747 - Reverse the effect of previous bad revision 23 and
748 leave changes uncommitted::
748 leave changes uncommitted::
749
749
750 hg backout -r 23 --no-commit
750 hg backout -r 23 --no-commit
751 hg commit -m "Backout revision 23"
751 hg commit -m "Backout revision 23"
752
752
753 By default, the pending changeset will have one parent,
753 By default, the pending changeset will have one parent,
754 maintaining a linear history. With --merge, the pending
754 maintaining a linear history. With --merge, the pending
755 changeset will instead have two parents: the old parent of the
755 changeset will instead have two parents: the old parent of the
756 working directory and a new child of REV that simply undoes REV.
756 working directory and a new child of REV that simply undoes REV.
757
757
758 Before version 1.7, the behavior without --merge was equivalent
758 Before version 1.7, the behavior without --merge was equivalent
759 to specifying --merge followed by :hg:`update --clean .` to
759 to specifying --merge followed by :hg:`update --clean .` to
760 cancel the merge and leave the child of REV as a head to be
760 cancel the merge and leave the child of REV as a head to be
761 merged separately.
761 merged separately.
762
762
763 See :hg:`help dates` for a list of formats valid for -d/--date.
763 See :hg:`help dates` for a list of formats valid for -d/--date.
764
764
765 See :hg:`help revert` for a way to restore files to the state
765 See :hg:`help revert` for a way to restore files to the state
766 of another revision.
766 of another revision.
767
767
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 files.
769 files.
770 """
770 """
771 with repo.wlock(), repo.lock():
771 with repo.wlock(), repo.lock():
772 return _dobackout(ui, repo, node, rev, **opts)
772 return _dobackout(ui, repo, node, rev, **opts)
773
773
774
774
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777 opts = pycompat.byteskwargs(opts)
777 opts = pycompat.byteskwargs(opts)
778
778
779 if rev and node:
779 if rev and node:
780 raise error.InputError(_(b"please specify just one revision"))
780 raise error.InputError(_(b"please specify just one revision"))
781
781
782 if not rev:
782 if not rev:
783 rev = node
783 rev = node
784
784
785 if not rev:
785 if not rev:
786 raise error.InputError(_(b"please specify a revision to backout"))
786 raise error.InputError(_(b"please specify a revision to backout"))
787
787
788 date = opts.get(b'date')
788 date = opts.get(b'date')
789 if date:
789 if date:
790 opts[b'date'] = dateutil.parsedate(date)
790 opts[b'date'] = dateutil.parsedate(date)
791
791
792 cmdutil.checkunfinished(repo)
792 cmdutil.checkunfinished(repo)
793 cmdutil.bailifchanged(repo)
793 cmdutil.bailifchanged(repo)
794 ctx = logcmdutil.revsingle(repo, rev)
794 ctx = logcmdutil.revsingle(repo, rev)
795 node = ctx.node()
795 node = ctx.node()
796
796
797 op1, op2 = repo.dirstate.parents()
797 op1, op2 = repo.dirstate.parents()
798 if not repo.changelog.isancestor(node, op1):
798 if not repo.changelog.isancestor(node, op1):
799 raise error.InputError(
799 raise error.InputError(
800 _(b'cannot backout change that is not an ancestor')
800 _(b'cannot backout change that is not an ancestor')
801 )
801 )
802
802
803 p1, p2 = repo.changelog.parents(node)
803 p1, p2 = repo.changelog.parents(node)
804 if p1 == repo.nullid:
804 if p1 == repo.nullid:
805 raise error.InputError(_(b'cannot backout a change with no parents'))
805 raise error.InputError(_(b'cannot backout a change with no parents'))
806 if p2 != repo.nullid:
806 if p2 != repo.nullid:
807 if not opts.get(b'parent'):
807 if not opts.get(b'parent'):
808 raise error.InputError(_(b'cannot backout a merge changeset'))
808 raise error.InputError(_(b'cannot backout a merge changeset'))
809 p = repo.lookup(opts[b'parent'])
809 p = repo.lookup(opts[b'parent'])
810 if p not in (p1, p2):
810 if p not in (p1, p2):
811 raise error.InputError(
811 raise error.InputError(
812 _(b'%s is not a parent of %s') % (short(p), short(node))
812 _(b'%s is not a parent of %s') % (short(p), short(node))
813 )
813 )
814 parent = p
814 parent = p
815 else:
815 else:
816 if opts.get(b'parent'):
816 if opts.get(b'parent'):
817 raise error.InputError(
817 raise error.InputError(
818 _(b'cannot use --parent on non-merge changeset')
818 _(b'cannot use --parent on non-merge changeset')
819 )
819 )
820 parent = p1
820 parent = p1
821
821
822 # the backout should appear on the same branch
822 # the backout should appear on the same branch
823 branch = repo.dirstate.branch()
823 branch = repo.dirstate.branch()
824 bheads = repo.branchheads(branch)
824 bheads = repo.branchheads(branch)
825 rctx = scmutil.revsingle(repo, hex(parent))
825 rctx = scmutil.revsingle(repo, hex(parent))
826 if not opts.get(b'merge') and op1 != node:
826 if not opts.get(b'merge') and op1 != node:
827 with dirstateguard.dirstateguard(repo, b'backout'):
827 with dirstateguard.dirstateguard(repo, b'backout'):
828 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
828 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
829 with ui.configoverride(overrides, b'backout'):
829 with ui.configoverride(overrides, b'backout'):
830 stats = mergemod.back_out(ctx, parent=repo[parent])
830 stats = mergemod.back_out(ctx, parent=repo[parent])
831 repo.setparents(op1, op2)
831 repo.setparents(op1, op2)
832 hg._showstats(repo, stats)
832 hg._showstats(repo, stats)
833 if stats.unresolvedcount:
833 if stats.unresolvedcount:
834 repo.ui.status(
834 repo.ui.status(
835 _(b"use 'hg resolve' to retry unresolved file merges\n")
835 _(b"use 'hg resolve' to retry unresolved file merges\n")
836 )
836 )
837 return 1
837 return 1
838 else:
838 else:
839 hg.clean(repo, node, show_stats=False)
839 hg.clean(repo, node, show_stats=False)
840 repo.dirstate.setbranch(branch)
840 repo.dirstate.setbranch(branch)
841 cmdutil.revert(ui, repo, rctx)
841 cmdutil.revert(ui, repo, rctx)
842
842
843 if opts.get(b'no_commit'):
843 if opts.get(b'no_commit'):
844 msg = _(b"changeset %s backed out, don't forget to commit.\n")
844 msg = _(b"changeset %s backed out, don't forget to commit.\n")
845 ui.status(msg % short(node))
845 ui.status(msg % short(node))
846 return 0
846 return 0
847
847
848 def commitfunc(ui, repo, message, match, opts):
848 def commitfunc(ui, repo, message, match, opts):
849 editform = b'backout'
849 editform = b'backout'
850 e = cmdutil.getcommiteditor(
850 e = cmdutil.getcommiteditor(
851 editform=editform, **pycompat.strkwargs(opts)
851 editform=editform, **pycompat.strkwargs(opts)
852 )
852 )
853 if not message:
853 if not message:
854 # we don't translate commit messages
854 # we don't translate commit messages
855 message = b"Backed out changeset %s" % short(node)
855 message = b"Backed out changeset %s" % short(node)
856 e = cmdutil.getcommiteditor(edit=True, editform=editform)
856 e = cmdutil.getcommiteditor(edit=True, editform=editform)
857 return repo.commit(
857 return repo.commit(
858 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
858 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
859 )
859 )
860
860
861 # save to detect changes
861 # save to detect changes
862 tip = repo.changelog.tip()
862 tip = repo.changelog.tip()
863
863
864 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
864 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
865 if not newnode:
865 if not newnode:
866 ui.status(_(b"nothing changed\n"))
866 ui.status(_(b"nothing changed\n"))
867 return 1
867 return 1
868 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
868 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
869
869
870 def nice(node):
870 def nice(node):
871 return b'%d:%s' % (repo.changelog.rev(node), short(node))
871 return b'%d:%s' % (repo.changelog.rev(node), short(node))
872
872
873 ui.status(
873 ui.status(
874 _(b'changeset %s backs out changeset %s\n')
874 _(b'changeset %s backs out changeset %s\n')
875 % (nice(newnode), nice(node))
875 % (nice(newnode), nice(node))
876 )
876 )
877 if opts.get(b'merge') and op1 != node:
877 if opts.get(b'merge') and op1 != node:
878 hg.clean(repo, op1, show_stats=False)
878 hg.clean(repo, op1, show_stats=False)
879 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
879 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 with ui.configoverride(overrides, b'backout'):
881 with ui.configoverride(overrides, b'backout'):
882 return hg.merge(repo[b'tip'])
882 return hg.merge(repo[b'tip'])
883 return 0
883 return 0
884
884
885
885
886 @command(
886 @command(
887 b'bisect',
887 b'bisect',
888 [
888 [
889 (b'r', b'reset', False, _(b'reset bisect state')),
889 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'g', b'good', False, _(b'mark changeset good')),
890 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (
894 (
895 b'c',
895 b'c',
896 b'command',
896 b'command',
897 b'',
897 b'',
898 _(b'use command to check changeset state'),
898 _(b'use command to check changeset state'),
899 _(b'CMD'),
899 _(b'CMD'),
900 ),
900 ),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
902 ],
902 ],
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 )
905 )
906 def bisect(
906 def bisect(
907 ui,
907 ui,
908 repo,
908 repo,
909 positional_1=None,
909 positional_1=None,
910 positional_2=None,
910 positional_2=None,
911 command=None,
911 command=None,
912 reset=None,
912 reset=None,
913 good=None,
913 good=None,
914 bad=None,
914 bad=None,
915 skip=None,
915 skip=None,
916 extend=None,
916 extend=None,
917 noupdate=None,
917 noupdate=None,
918 ):
918 ):
919 """subdivision search of changesets
919 """subdivision search of changesets
920
920
921 This command helps to find changesets which introduce problems. To
921 This command helps to find changesets which introduce problems. To
922 use, mark the earliest changeset you know exhibits the problem as
922 use, mark the earliest changeset you know exhibits the problem as
923 bad, then mark the latest changeset which is free from the problem
923 bad, then mark the latest changeset which is free from the problem
924 as good. Bisect will update your working directory to a revision
924 as good. Bisect will update your working directory to a revision
925 for testing (unless the -U/--noupdate option is specified). Once
925 for testing (unless the -U/--noupdate option is specified). Once
926 you have performed tests, mark the working directory as good or
926 you have performed tests, mark the working directory as good or
927 bad, and bisect will either update to another candidate changeset
927 bad, and bisect will either update to another candidate changeset
928 or announce that it has found the bad revision.
928 or announce that it has found the bad revision.
929
929
930 As a shortcut, you can also use the revision argument to mark a
930 As a shortcut, you can also use the revision argument to mark a
931 revision as good or bad without checking it out first.
931 revision as good or bad without checking it out first.
932
932
933 If you supply a command, it will be used for automatic bisection.
933 If you supply a command, it will be used for automatic bisection.
934 The environment variable HG_NODE will contain the ID of the
934 The environment variable HG_NODE will contain the ID of the
935 changeset being tested. The exit status of the command will be
935 changeset being tested. The exit status of the command will be
936 used to mark revisions as good or bad: status 0 means good, 125
936 used to mark revisions as good or bad: status 0 means good, 125
937 means to skip the revision, 127 (command not found) will abort the
937 means to skip the revision, 127 (command not found) will abort the
938 bisection, and any other non-zero exit status means the revision
938 bisection, and any other non-zero exit status means the revision
939 is bad.
939 is bad.
940
940
941 .. container:: verbose
941 .. container:: verbose
942
942
943 Some examples:
943 Some examples:
944
944
945 - start a bisection with known bad revision 34, and good revision 12::
945 - start a bisection with known bad revision 34, and good revision 12::
946
946
947 hg bisect --bad 34
947 hg bisect --bad 34
948 hg bisect --good 12
948 hg bisect --good 12
949
949
950 - advance the current bisection by marking current revision as good or
950 - advance the current bisection by marking current revision as good or
951 bad::
951 bad::
952
952
953 hg bisect --good
953 hg bisect --good
954 hg bisect --bad
954 hg bisect --bad
955
955
956 - mark the current revision, or a known revision, to be skipped (e.g. if
956 - mark the current revision, or a known revision, to be skipped (e.g. if
957 that revision is not usable because of another issue)::
957 that revision is not usable because of another issue)::
958
958
959 hg bisect --skip
959 hg bisect --skip
960 hg bisect --skip 23
960 hg bisect --skip 23
961
961
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963
963
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965
965
966 - forget the current bisection::
966 - forget the current bisection::
967
967
968 hg bisect --reset
968 hg bisect --reset
969
969
970 - use 'make && make tests' to automatically find the first broken
970 - use 'make && make tests' to automatically find the first broken
971 revision::
971 revision::
972
972
973 hg bisect --reset
973 hg bisect --reset
974 hg bisect --bad 34
974 hg bisect --bad 34
975 hg bisect --good 12
975 hg bisect --good 12
976 hg bisect --command "make && make tests"
976 hg bisect --command "make && make tests"
977
977
978 - see all changesets whose states are already known in the current
978 - see all changesets whose states are already known in the current
979 bisection::
979 bisection::
980
980
981 hg log -r "bisect(pruned)"
981 hg log -r "bisect(pruned)"
982
982
983 - see the changeset currently being bisected (especially useful
983 - see the changeset currently being bisected (especially useful
984 if running with -U/--noupdate)::
984 if running with -U/--noupdate)::
985
985
986 hg log -r "bisect(current)"
986 hg log -r "bisect(current)"
987
987
988 - see all changesets that took part in the current bisection::
988 - see all changesets that took part in the current bisection::
989
989
990 hg log -r "bisect(range)"
990 hg log -r "bisect(range)"
991
991
992 - you can even get a nice graph::
992 - you can even get a nice graph::
993
993
994 hg log --graph -r "bisect(range)"
994 hg log --graph -r "bisect(range)"
995
995
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997
997
998 Returns 0 on success.
998 Returns 0 on success.
999 """
999 """
1000 rev = []
1000 rev = []
1001 # backward compatibility
1001 # backward compatibility
1002 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1002 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1003 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1003 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1004 cmd = positional_1
1004 cmd = positional_1
1005 rev.append(positional_2)
1005 rev.append(positional_2)
1006 if cmd == b"good":
1006 if cmd == b"good":
1007 good = True
1007 good = True
1008 elif cmd == b"bad":
1008 elif cmd == b"bad":
1009 bad = True
1009 bad = True
1010 else:
1010 else:
1011 reset = True
1011 reset = True
1012 elif positional_2:
1012 elif positional_2:
1013 raise error.InputError(_(b'incompatible arguments'))
1013 raise error.InputError(_(b'incompatible arguments'))
1014 elif positional_1 is not None:
1014 elif positional_1 is not None:
1015 rev.append(positional_1)
1015 rev.append(positional_1)
1016
1016
1017 incompatibles = {
1017 incompatibles = {
1018 b'--bad': bad,
1018 b'--bad': bad,
1019 b'--command': bool(command),
1019 b'--command': bool(command),
1020 b'--extend': extend,
1020 b'--extend': extend,
1021 b'--good': good,
1021 b'--good': good,
1022 b'--reset': reset,
1022 b'--reset': reset,
1023 b'--skip': skip,
1023 b'--skip': skip,
1024 }
1024 }
1025
1025
1026 enabled = [x for x in incompatibles if incompatibles[x]]
1026 enabled = [x for x in incompatibles if incompatibles[x]]
1027
1027
1028 if len(enabled) > 1:
1028 if len(enabled) > 1:
1029 raise error.InputError(
1029 raise error.InputError(
1030 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1030 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1031 )
1031 )
1032
1032
1033 if reset:
1033 if reset:
1034 hbisect.resetstate(repo)
1034 hbisect.resetstate(repo)
1035 return
1035 return
1036
1036
1037 state = hbisect.load_state(repo)
1037 state = hbisect.load_state(repo)
1038
1038
1039 if rev:
1039 if rev:
1040 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1040 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1041 else:
1041 else:
1042 nodes = [repo.lookup(b'.')]
1042 nodes = [repo.lookup(b'.')]
1043
1043
1044 # update state
1044 # update state
1045 if good or bad or skip:
1045 if good or bad or skip:
1046 if good:
1046 if good:
1047 state[b'good'] += nodes
1047 state[b'good'] += nodes
1048 elif bad:
1048 elif bad:
1049 state[b'bad'] += nodes
1049 state[b'bad'] += nodes
1050 elif skip:
1050 elif skip:
1051 state[b'skip'] += nodes
1051 state[b'skip'] += nodes
1052 hbisect.save_state(repo, state)
1052 hbisect.save_state(repo, state)
1053 if not (state[b'good'] and state[b'bad']):
1053 if not (state[b'good'] and state[b'bad']):
1054 return
1054 return
1055
1055
1056 def mayupdate(repo, node, show_stats=True):
1056 def mayupdate(repo, node, show_stats=True):
1057 """common used update sequence"""
1057 """common used update sequence"""
1058 if noupdate:
1058 if noupdate:
1059 return
1059 return
1060 cmdutil.checkunfinished(repo)
1060 cmdutil.checkunfinished(repo)
1061 cmdutil.bailifchanged(repo)
1061 cmdutil.bailifchanged(repo)
1062 return hg.clean(repo, node, show_stats=show_stats)
1062 return hg.clean(repo, node, show_stats=show_stats)
1063
1063
1064 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1064 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1065
1065
1066 if command:
1066 if command:
1067 changesets = 1
1067 changesets = 1
1068 if noupdate:
1068 if noupdate:
1069 try:
1069 try:
1070 node = state[b'current'][0]
1070 node = state[b'current'][0]
1071 except LookupError:
1071 except LookupError:
1072 raise error.StateError(
1072 raise error.StateError(
1073 _(
1073 _(
1074 b'current bisect revision is unknown - '
1074 b'current bisect revision is unknown - '
1075 b'start a new bisect to fix'
1075 b'start a new bisect to fix'
1076 )
1076 )
1077 )
1077 )
1078 else:
1078 else:
1079 node, p2 = repo.dirstate.parents()
1079 node, p2 = repo.dirstate.parents()
1080 if p2 != repo.nullid:
1080 if p2 != repo.nullid:
1081 raise error.StateError(_(b'current bisect revision is a merge'))
1081 raise error.StateError(_(b'current bisect revision is a merge'))
1082 if rev:
1082 if rev:
1083 if not nodes:
1083 if not nodes:
1084 raise error.InputError(_(b'empty revision set'))
1084 raise error.InputError(_(b'empty revision set'))
1085 node = repo[nodes[-1]].node()
1085 node = repo[nodes[-1]].node()
1086 with hbisect.restore_state(repo, state, node):
1086 with hbisect.restore_state(repo, state, node):
1087 while changesets:
1087 while changesets:
1088 # update state
1088 # update state
1089 state[b'current'] = [node]
1089 state[b'current'] = [node]
1090 hbisect.save_state(repo, state)
1090 hbisect.save_state(repo, state)
1091 status = ui.system(
1091 status = ui.system(
1092 command,
1092 command,
1093 environ={b'HG_NODE': hex(node)},
1093 environ={b'HG_NODE': hex(node)},
1094 blockedtag=b'bisect_check',
1094 blockedtag=b'bisect_check',
1095 )
1095 )
1096 if status == 125:
1096 if status == 125:
1097 transition = b"skip"
1097 transition = b"skip"
1098 elif status == 0:
1098 elif status == 0:
1099 transition = b"good"
1099 transition = b"good"
1100 # status < 0 means process was killed
1100 # status < 0 means process was killed
1101 elif status == 127:
1101 elif status == 127:
1102 raise error.Abort(_(b"failed to execute %s") % command)
1102 raise error.Abort(_(b"failed to execute %s") % command)
1103 elif status < 0:
1103 elif status < 0:
1104 raise error.Abort(_(b"%s killed") % command)
1104 raise error.Abort(_(b"%s killed") % command)
1105 else:
1105 else:
1106 transition = b"bad"
1106 transition = b"bad"
1107 state[transition].append(node)
1107 state[transition].append(node)
1108 ctx = repo[node]
1108 ctx = repo[node]
1109 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1109 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1110 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1110 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1111 hbisect.checkstate(state)
1111 hbisect.checkstate(state)
1112 # bisect
1112 # bisect
1113 nodes, changesets, bgood = hbisect.bisect(repo, state)
1113 nodes, changesets, bgood = hbisect.bisect(repo, state)
1114 # update to next check
1114 # update to next check
1115 node = nodes[0]
1115 node = nodes[0]
1116 mayupdate(repo, node, show_stats=False)
1116 mayupdate(repo, node, show_stats=False)
1117 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1117 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1118 return
1118 return
1119
1119
1120 hbisect.checkstate(state)
1120 hbisect.checkstate(state)
1121
1121
1122 # actually bisect
1122 # actually bisect
1123 nodes, changesets, good = hbisect.bisect(repo, state)
1123 nodes, changesets, good = hbisect.bisect(repo, state)
1124 if extend:
1124 if extend:
1125 if not changesets:
1125 if not changesets:
1126 extendctx = hbisect.extendrange(repo, state, nodes, good)
1126 extendctx = hbisect.extendrange(repo, state, nodes, good)
1127 if extendctx is not None:
1127 if extendctx is not None:
1128 ui.write(
1128 ui.write(
1129 _(b"Extending search to changeset %s\n")
1129 _(b"Extending search to changeset %s\n")
1130 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1130 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1131 )
1131 )
1132 state[b'current'] = [extendctx.node()]
1132 state[b'current'] = [extendctx.node()]
1133 hbisect.save_state(repo, state)
1133 hbisect.save_state(repo, state)
1134 return mayupdate(repo, extendctx.node())
1134 return mayupdate(repo, extendctx.node())
1135 raise error.StateError(_(b"nothing to extend"))
1135 raise error.StateError(_(b"nothing to extend"))
1136
1136
1137 if changesets == 0:
1137 if changesets == 0:
1138 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1138 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1139 else:
1139 else:
1140 assert len(nodes) == 1 # only a single node can be tested next
1140 assert len(nodes) == 1 # only a single node can be tested next
1141 node = nodes[0]
1141 node = nodes[0]
1142 # compute the approximate number of remaining tests
1142 # compute the approximate number of remaining tests
1143 tests, size = 0, 2
1143 tests, size = 0, 2
1144 while size <= changesets:
1144 while size <= changesets:
1145 tests, size = tests + 1, size * 2
1145 tests, size = tests + 1, size * 2
1146 rev = repo.changelog.rev(node)
1146 rev = repo.changelog.rev(node)
1147 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1147 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1148 ui.write(
1148 ui.write(
1149 _(
1149 _(
1150 b"Testing changeset %s "
1150 b"Testing changeset %s "
1151 b"(%d changesets remaining, ~%d tests)\n"
1151 b"(%d changesets remaining, ~%d tests)\n"
1152 )
1152 )
1153 % (summary, changesets, tests)
1153 % (summary, changesets, tests)
1154 )
1154 )
1155 state[b'current'] = [node]
1155 state[b'current'] = [node]
1156 hbisect.save_state(repo, state)
1156 hbisect.save_state(repo, state)
1157 return mayupdate(repo, node)
1157 return mayupdate(repo, node)
1158
1158
1159
1159
1160 @command(
1160 @command(
1161 b'bookmarks|bookmark',
1161 b'bookmarks|bookmark',
1162 [
1162 [
1163 (b'f', b'force', False, _(b'force')),
1163 (b'f', b'force', False, _(b'force')),
1164 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1164 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1165 (b'd', b'delete', False, _(b'delete a given bookmark')),
1165 (b'd', b'delete', False, _(b'delete a given bookmark')),
1166 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1166 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1167 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1167 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1168 (b'l', b'list', False, _(b'list existing bookmarks')),
1168 (b'l', b'list', False, _(b'list existing bookmarks')),
1169 ]
1169 ]
1170 + formatteropts,
1170 + formatteropts,
1171 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1171 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1172 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1172 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1173 )
1173 )
1174 def bookmark(ui, repo, *names, **opts):
1174 def bookmark(ui, repo, *names, **opts):
1175 """create a new bookmark or list existing bookmarks
1175 """create a new bookmark or list existing bookmarks
1176
1176
1177 Bookmarks are labels on changesets to help track lines of development.
1177 Bookmarks are labels on changesets to help track lines of development.
1178 Bookmarks are unversioned and can be moved, renamed and deleted.
1178 Bookmarks are unversioned and can be moved, renamed and deleted.
1179 Deleting or moving a bookmark has no effect on the associated changesets.
1179 Deleting or moving a bookmark has no effect on the associated changesets.
1180
1180
1181 Creating or updating to a bookmark causes it to be marked as 'active'.
1181 Creating or updating to a bookmark causes it to be marked as 'active'.
1182 The active bookmark is indicated with a '*'.
1182 The active bookmark is indicated with a '*'.
1183 When a commit is made, the active bookmark will advance to the new commit.
1183 When a commit is made, the active bookmark will advance to the new commit.
1184 A plain :hg:`update` will also advance an active bookmark, if possible.
1184 A plain :hg:`update` will also advance an active bookmark, if possible.
1185 Updating away from a bookmark will cause it to be deactivated.
1185 Updating away from a bookmark will cause it to be deactivated.
1186
1186
1187 Bookmarks can be pushed and pulled between repositories (see
1187 Bookmarks can be pushed and pulled between repositories (see
1188 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1188 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1189 diverged, a new 'divergent bookmark' of the form 'name@path' will
1189 diverged, a new 'divergent bookmark' of the form 'name@path' will
1190 be created. Using :hg:`merge` will resolve the divergence.
1190 be created. Using :hg:`merge` will resolve the divergence.
1191
1191
1192 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1192 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1193 the active bookmark's name.
1193 the active bookmark's name.
1194
1194
1195 A bookmark named '@' has the special property that :hg:`clone` will
1195 A bookmark named '@' has the special property that :hg:`clone` will
1196 check it out by default if it exists.
1196 check it out by default if it exists.
1197
1197
1198 .. container:: verbose
1198 .. container:: verbose
1199
1199
1200 Template:
1200 Template:
1201
1201
1202 The following keywords are supported in addition to the common template
1202 The following keywords are supported in addition to the common template
1203 keywords and functions such as ``{bookmark}``. See also
1203 keywords and functions such as ``{bookmark}``. See also
1204 :hg:`help templates`.
1204 :hg:`help templates`.
1205
1205
1206 :active: Boolean. True if the bookmark is active.
1206 :active: Boolean. True if the bookmark is active.
1207
1207
1208 Examples:
1208 Examples:
1209
1209
1210 - create an active bookmark for a new line of development::
1210 - create an active bookmark for a new line of development::
1211
1211
1212 hg book new-feature
1212 hg book new-feature
1213
1213
1214 - create an inactive bookmark as a place marker::
1214 - create an inactive bookmark as a place marker::
1215
1215
1216 hg book -i reviewed
1216 hg book -i reviewed
1217
1217
1218 - create an inactive bookmark on another changeset::
1218 - create an inactive bookmark on another changeset::
1219
1219
1220 hg book -r .^ tested
1220 hg book -r .^ tested
1221
1221
1222 - rename bookmark turkey to dinner::
1222 - rename bookmark turkey to dinner::
1223
1223
1224 hg book -m turkey dinner
1224 hg book -m turkey dinner
1225
1225
1226 - move the '@' bookmark from another branch::
1226 - move the '@' bookmark from another branch::
1227
1227
1228 hg book -f @
1228 hg book -f @
1229
1229
1230 - print only the active bookmark name::
1230 - print only the active bookmark name::
1231
1231
1232 hg book -ql .
1232 hg book -ql .
1233 """
1233 """
1234 opts = pycompat.byteskwargs(opts)
1234 opts = pycompat.byteskwargs(opts)
1235 force = opts.get(b'force')
1235 force = opts.get(b'force')
1236 rev = opts.get(b'rev')
1236 rev = opts.get(b'rev')
1237 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1237 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1238
1238
1239 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1239 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1240 if action:
1240 if action:
1241 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1241 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1242 elif names or rev:
1242 elif names or rev:
1243 action = b'add'
1243 action = b'add'
1244 elif inactive:
1244 elif inactive:
1245 action = b'inactive' # meaning deactivate
1245 action = b'inactive' # meaning deactivate
1246 else:
1246 else:
1247 action = b'list'
1247 action = b'list'
1248
1248
1249 cmdutil.check_incompatible_arguments(
1249 cmdutil.check_incompatible_arguments(
1250 opts, b'inactive', [b'delete', b'list']
1250 opts, b'inactive', [b'delete', b'list']
1251 )
1251 )
1252 if not names and action in {b'add', b'delete'}:
1252 if not names and action in {b'add', b'delete'}:
1253 raise error.InputError(_(b"bookmark name required"))
1253 raise error.InputError(_(b"bookmark name required"))
1254
1254
1255 if action in {b'add', b'delete', b'rename', b'inactive'}:
1255 if action in {b'add', b'delete', b'rename', b'inactive'}:
1256 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1256 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1257 if action == b'delete':
1257 if action == b'delete':
1258 names = pycompat.maplist(repo._bookmarks.expandname, names)
1258 names = pycompat.maplist(repo._bookmarks.expandname, names)
1259 bookmarks.delete(repo, tr, names)
1259 bookmarks.delete(repo, tr, names)
1260 elif action == b'rename':
1260 elif action == b'rename':
1261 if not names:
1261 if not names:
1262 raise error.InputError(_(b"new bookmark name required"))
1262 raise error.InputError(_(b"new bookmark name required"))
1263 elif len(names) > 1:
1263 elif len(names) > 1:
1264 raise error.InputError(
1264 raise error.InputError(
1265 _(b"only one new bookmark name allowed")
1265 _(b"only one new bookmark name allowed")
1266 )
1266 )
1267 oldname = repo._bookmarks.expandname(opts[b'rename'])
1267 oldname = repo._bookmarks.expandname(opts[b'rename'])
1268 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1268 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1269 elif action == b'add':
1269 elif action == b'add':
1270 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1270 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1271 elif action == b'inactive':
1271 elif action == b'inactive':
1272 if len(repo._bookmarks) == 0:
1272 if len(repo._bookmarks) == 0:
1273 ui.status(_(b"no bookmarks set\n"))
1273 ui.status(_(b"no bookmarks set\n"))
1274 elif not repo._activebookmark:
1274 elif not repo._activebookmark:
1275 ui.status(_(b"no active bookmark\n"))
1275 ui.status(_(b"no active bookmark\n"))
1276 else:
1276 else:
1277 bookmarks.deactivate(repo)
1277 bookmarks.deactivate(repo)
1278 elif action == b'list':
1278 elif action == b'list':
1279 names = pycompat.maplist(repo._bookmarks.expandname, names)
1279 names = pycompat.maplist(repo._bookmarks.expandname, names)
1280 with ui.formatter(b'bookmarks', opts) as fm:
1280 with ui.formatter(b'bookmarks', opts) as fm:
1281 bookmarks.printbookmarks(ui, repo, fm, names)
1281 bookmarks.printbookmarks(ui, repo, fm, names)
1282 else:
1282 else:
1283 raise error.ProgrammingError(b'invalid action: %s' % action)
1283 raise error.ProgrammingError(b'invalid action: %s' % action)
1284
1284
1285
1285
1286 @command(
1286 @command(
1287 b'branch',
1287 b'branch',
1288 [
1288 [
1289 (
1289 (
1290 b'f',
1290 b'f',
1291 b'force',
1291 b'force',
1292 None,
1292 None,
1293 _(b'set branch name even if it shadows an existing branch'),
1293 _(b'set branch name even if it shadows an existing branch'),
1294 ),
1294 ),
1295 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1295 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1296 (
1296 (
1297 b'r',
1297 b'r',
1298 b'rev',
1298 b'rev',
1299 [],
1299 [],
1300 _(b'change branches of the given revs (EXPERIMENTAL)'),
1300 _(b'change branches of the given revs (EXPERIMENTAL)'),
1301 ),
1301 ),
1302 ],
1302 ],
1303 _(b'[-fC] [NAME]'),
1303 _(b'[-fC] [NAME]'),
1304 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1304 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1305 )
1305 )
1306 def branch(ui, repo, label=None, **opts):
1306 def branch(ui, repo, label=None, **opts):
1307 """set or show the current branch name
1307 """set or show the current branch name
1308
1308
1309 .. note::
1309 .. note::
1310
1310
1311 Branch names are permanent and global. Use :hg:`bookmark` to create a
1311 Branch names are permanent and global. Use :hg:`bookmark` to create a
1312 light-weight bookmark instead. See :hg:`help glossary` for more
1312 light-weight bookmark instead. See :hg:`help glossary` for more
1313 information about named branches and bookmarks.
1313 information about named branches and bookmarks.
1314
1314
1315 With no argument, show the current branch name. With one argument,
1315 With no argument, show the current branch name. With one argument,
1316 set the working directory branch name (the branch will not exist
1316 set the working directory branch name (the branch will not exist
1317 in the repository until the next commit). Standard practice
1317 in the repository until the next commit). Standard practice
1318 recommends that primary development take place on the 'default'
1318 recommends that primary development take place on the 'default'
1319 branch.
1319 branch.
1320
1320
1321 Unless -f/--force is specified, branch will not let you set a
1321 Unless -f/--force is specified, branch will not let you set a
1322 branch name that already exists.
1322 branch name that already exists.
1323
1323
1324 Use -C/--clean to reset the working directory branch to that of
1324 Use -C/--clean to reset the working directory branch to that of
1325 the parent of the working directory, negating a previous branch
1325 the parent of the working directory, negating a previous branch
1326 change.
1326 change.
1327
1327
1328 Use the command :hg:`update` to switch to an existing branch. Use
1328 Use the command :hg:`update` to switch to an existing branch. Use
1329 :hg:`commit --close-branch` to mark this branch head as closed.
1329 :hg:`commit --close-branch` to mark this branch head as closed.
1330 When all heads of a branch are closed, the branch will be
1330 When all heads of a branch are closed, the branch will be
1331 considered closed.
1331 considered closed.
1332
1332
1333 Returns 0 on success.
1333 Returns 0 on success.
1334 """
1334 """
1335 opts = pycompat.byteskwargs(opts)
1335 opts = pycompat.byteskwargs(opts)
1336 revs = opts.get(b'rev')
1336 revs = opts.get(b'rev')
1337 if label:
1337 if label:
1338 label = label.strip()
1338 label = label.strip()
1339
1339
1340 if not opts.get(b'clean') and not label:
1340 if not opts.get(b'clean') and not label:
1341 if revs:
1341 if revs:
1342 raise error.InputError(
1342 raise error.InputError(
1343 _(b"no branch name specified for the revisions")
1343 _(b"no branch name specified for the revisions")
1344 )
1344 )
1345 ui.write(b"%s\n" % repo.dirstate.branch())
1345 ui.write(b"%s\n" % repo.dirstate.branch())
1346 return
1346 return
1347
1347
1348 with repo.wlock():
1348 with repo.wlock():
1349 if opts.get(b'clean'):
1349 if opts.get(b'clean'):
1350 label = repo[b'.'].branch()
1350 label = repo[b'.'].branch()
1351 repo.dirstate.setbranch(label)
1351 repo.dirstate.setbranch(label)
1352 ui.status(_(b'reset working directory to branch %s\n') % label)
1352 ui.status(_(b'reset working directory to branch %s\n') % label)
1353 elif label:
1353 elif label:
1354
1354
1355 scmutil.checknewlabel(repo, label, b'branch')
1355 scmutil.checknewlabel(repo, label, b'branch')
1356 if revs:
1356 if revs:
1357 return cmdutil.changebranch(ui, repo, revs, label, opts)
1357 return cmdutil.changebranch(ui, repo, revs, label, opts)
1358
1358
1359 if not opts.get(b'force') and label in repo.branchmap():
1359 if not opts.get(b'force') and label in repo.branchmap():
1360 if label not in [p.branch() for p in repo[None].parents()]:
1360 if label not in [p.branch() for p in repo[None].parents()]:
1361 raise error.InputError(
1361 raise error.InputError(
1362 _(b'a branch of the same name already exists'),
1362 _(b'a branch of the same name already exists'),
1363 # i18n: "it" refers to an existing branch
1363 # i18n: "it" refers to an existing branch
1364 hint=_(b"use 'hg update' to switch to it"),
1364 hint=_(b"use 'hg update' to switch to it"),
1365 )
1365 )
1366
1366
1367 repo.dirstate.setbranch(label)
1367 repo.dirstate.setbranch(label)
1368 ui.status(_(b'marked working directory as branch %s\n') % label)
1368 ui.status(_(b'marked working directory as branch %s\n') % label)
1369
1369
1370 # find any open named branches aside from default
1370 # find any open named branches aside from default
1371 for n, h, t, c in repo.branchmap().iterbranches():
1371 for n, h, t, c in repo.branchmap().iterbranches():
1372 if n != b"default" and not c:
1372 if n != b"default" and not c:
1373 return 0
1373 return 0
1374 ui.status(
1374 ui.status(
1375 _(
1375 _(
1376 b'(branches are permanent and global, '
1376 b'(branches are permanent and global, '
1377 b'did you want a bookmark?)\n'
1377 b'did you want a bookmark?)\n'
1378 )
1378 )
1379 )
1379 )
1380
1380
1381
1381
1382 @command(
1382 @command(
1383 b'branches',
1383 b'branches',
1384 [
1384 [
1385 (
1385 (
1386 b'a',
1386 b'a',
1387 b'active',
1387 b'active',
1388 False,
1388 False,
1389 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1389 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1390 ),
1390 ),
1391 (b'c', b'closed', False, _(b'show normal and closed branches')),
1391 (b'c', b'closed', False, _(b'show normal and closed branches')),
1392 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1392 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1393 ]
1393 ]
1394 + formatteropts,
1394 + formatteropts,
1395 _(b'[-c]'),
1395 _(b'[-c]'),
1396 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1396 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1397 intents={INTENT_READONLY},
1397 intents={INTENT_READONLY},
1398 )
1398 )
1399 def branches(ui, repo, active=False, closed=False, **opts):
1399 def branches(ui, repo, active=False, closed=False, **opts):
1400 """list repository named branches
1400 """list repository named branches
1401
1401
1402 List the repository's named branches, indicating which ones are
1402 List the repository's named branches, indicating which ones are
1403 inactive. If -c/--closed is specified, also list branches which have
1403 inactive. If -c/--closed is specified, also list branches which have
1404 been marked closed (see :hg:`commit --close-branch`).
1404 been marked closed (see :hg:`commit --close-branch`).
1405
1405
1406 Use the command :hg:`update` to switch to an existing branch.
1406 Use the command :hg:`update` to switch to an existing branch.
1407
1407
1408 .. container:: verbose
1408 .. container:: verbose
1409
1409
1410 Template:
1410 Template:
1411
1411
1412 The following keywords are supported in addition to the common template
1412 The following keywords are supported in addition to the common template
1413 keywords and functions such as ``{branch}``. See also
1413 keywords and functions such as ``{branch}``. See also
1414 :hg:`help templates`.
1414 :hg:`help templates`.
1415
1415
1416 :active: Boolean. True if the branch is active.
1416 :active: Boolean. True if the branch is active.
1417 :closed: Boolean. True if the branch is closed.
1417 :closed: Boolean. True if the branch is closed.
1418 :current: Boolean. True if it is the current branch.
1418 :current: Boolean. True if it is the current branch.
1419
1419
1420 Returns 0.
1420 Returns 0.
1421 """
1421 """
1422
1422
1423 opts = pycompat.byteskwargs(opts)
1423 opts = pycompat.byteskwargs(opts)
1424 revs = opts.get(b'rev')
1424 revs = opts.get(b'rev')
1425 selectedbranches = None
1425 selectedbranches = None
1426 if revs:
1426 if revs:
1427 revs = logcmdutil.revrange(repo, revs)
1427 revs = logcmdutil.revrange(repo, revs)
1428 getbi = repo.revbranchcache().branchinfo
1428 getbi = repo.revbranchcache().branchinfo
1429 selectedbranches = {getbi(r)[0] for r in revs}
1429 selectedbranches = {getbi(r)[0] for r in revs}
1430
1430
1431 ui.pager(b'branches')
1431 ui.pager(b'branches')
1432 fm = ui.formatter(b'branches', opts)
1432 fm = ui.formatter(b'branches', opts)
1433 hexfunc = fm.hexfunc
1433 hexfunc = fm.hexfunc
1434
1434
1435 allheads = set(repo.heads())
1435 allheads = set(repo.heads())
1436 branches = []
1436 branches = []
1437 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1437 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1438 if selectedbranches is not None and tag not in selectedbranches:
1438 if selectedbranches is not None and tag not in selectedbranches:
1439 continue
1439 continue
1440 isactive = False
1440 isactive = False
1441 if not isclosed:
1441 if not isclosed:
1442 openheads = set(repo.branchmap().iteropen(heads))
1442 openheads = set(repo.branchmap().iteropen(heads))
1443 isactive = bool(openheads & allheads)
1443 isactive = bool(openheads & allheads)
1444 branches.append((tag, repo[tip], isactive, not isclosed))
1444 branches.append((tag, repo[tip], isactive, not isclosed))
1445 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1445 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1446
1446
1447 for tag, ctx, isactive, isopen in branches:
1447 for tag, ctx, isactive, isopen in branches:
1448 if active and not isactive:
1448 if active and not isactive:
1449 continue
1449 continue
1450 if isactive:
1450 if isactive:
1451 label = b'branches.active'
1451 label = b'branches.active'
1452 notice = b''
1452 notice = b''
1453 elif not isopen:
1453 elif not isopen:
1454 if not closed:
1454 if not closed:
1455 continue
1455 continue
1456 label = b'branches.closed'
1456 label = b'branches.closed'
1457 notice = _(b' (closed)')
1457 notice = _(b' (closed)')
1458 else:
1458 else:
1459 label = b'branches.inactive'
1459 label = b'branches.inactive'
1460 notice = _(b' (inactive)')
1460 notice = _(b' (inactive)')
1461 current = tag == repo.dirstate.branch()
1461 current = tag == repo.dirstate.branch()
1462 if current:
1462 if current:
1463 label = b'branches.current'
1463 label = b'branches.current'
1464
1464
1465 fm.startitem()
1465 fm.startitem()
1466 fm.write(b'branch', b'%s', tag, label=label)
1466 fm.write(b'branch', b'%s', tag, label=label)
1467 rev = ctx.rev()
1467 rev = ctx.rev()
1468 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1468 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1469 fmt = b' ' * padsize + b' %d:%s'
1469 fmt = b' ' * padsize + b' %d:%s'
1470 fm.condwrite(
1470 fm.condwrite(
1471 not ui.quiet,
1471 not ui.quiet,
1472 b'rev node',
1472 b'rev node',
1473 fmt,
1473 fmt,
1474 rev,
1474 rev,
1475 hexfunc(ctx.node()),
1475 hexfunc(ctx.node()),
1476 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1476 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1477 )
1477 )
1478 fm.context(ctx=ctx)
1478 fm.context(ctx=ctx)
1479 fm.data(active=isactive, closed=not isopen, current=current)
1479 fm.data(active=isactive, closed=not isopen, current=current)
1480 if not ui.quiet:
1480 if not ui.quiet:
1481 fm.plain(notice)
1481 fm.plain(notice)
1482 fm.plain(b'\n')
1482 fm.plain(b'\n')
1483 fm.end()
1483 fm.end()
1484
1484
1485
1485
1486 @command(
1486 @command(
1487 b'bundle',
1487 b'bundle',
1488 [
1488 [
1489 (
1489 (
1490 b'f',
1490 b'f',
1491 b'force',
1491 b'force',
1492 None,
1492 None,
1493 _(b'run even when the destination is unrelated'),
1493 _(b'run even when the destination is unrelated'),
1494 ),
1494 ),
1495 (
1495 (
1496 b'r',
1496 b'r',
1497 b'rev',
1497 b'rev',
1498 [],
1498 [],
1499 _(b'a changeset intended to be added to the destination'),
1499 _(b'a changeset intended to be added to the destination'),
1500 _(b'REV'),
1500 _(b'REV'),
1501 ),
1501 ),
1502 (
1502 (
1503 b'b',
1503 b'b',
1504 b'branch',
1504 b'branch',
1505 [],
1505 [],
1506 _(b'a specific branch you would like to bundle'),
1506 _(b'a specific branch you would like to bundle'),
1507 _(b'BRANCH'),
1507 _(b'BRANCH'),
1508 ),
1508 ),
1509 (
1509 (
1510 b'',
1510 b'',
1511 b'base',
1511 b'base',
1512 [],
1512 [],
1513 _(b'a base changeset assumed to be available at the destination'),
1513 _(b'a base changeset assumed to be available at the destination'),
1514 _(b'REV'),
1514 _(b'REV'),
1515 ),
1515 ),
1516 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1516 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1517 (
1517 (
1518 b't',
1518 b't',
1519 b'type',
1519 b'type',
1520 b'bzip2',
1520 b'bzip2',
1521 _(b'bundle compression type to use'),
1521 _(b'bundle compression type to use'),
1522 _(b'TYPE'),
1522 _(b'TYPE'),
1523 ),
1523 ),
1524 ]
1524 ]
1525 + remoteopts,
1525 + remoteopts,
1526 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1526 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1527 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1527 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1528 )
1528 )
1529 def bundle(ui, repo, fname, *dests, **opts):
1529 def bundle(ui, repo, fname, *dests, **opts):
1530 """create a bundle file
1530 """create a bundle file
1531
1531
1532 Generate a bundle file containing data to be transferred to another
1532 Generate a bundle file containing data to be transferred to another
1533 repository.
1533 repository.
1534
1534
1535 To create a bundle containing all changesets, use -a/--all
1535 To create a bundle containing all changesets, use -a/--all
1536 (or --base null). Otherwise, hg assumes the destination will have
1536 (or --base null). Otherwise, hg assumes the destination will have
1537 all the nodes you specify with --base parameters. Otherwise, hg
1537 all the nodes you specify with --base parameters. Otherwise, hg
1538 will assume the repository has all the nodes in destination, or
1538 will assume the repository has all the nodes in destination, or
1539 default-push/default if no destination is specified, where destination
1539 default-push/default if no destination is specified, where destination
1540 is the repositories you provide through DEST option.
1540 is the repositories you provide through DEST option.
1541
1541
1542 You can change bundle format with the -t/--type option. See
1542 You can change bundle format with the -t/--type option. See
1543 :hg:`help bundlespec` for documentation on this format. By default,
1543 :hg:`help bundlespec` for documentation on this format. By default,
1544 the most appropriate format is used and compression defaults to
1544 the most appropriate format is used and compression defaults to
1545 bzip2.
1545 bzip2.
1546
1546
1547 The bundle file can then be transferred using conventional means
1547 The bundle file can then be transferred using conventional means
1548 and applied to another repository with the unbundle or pull
1548 and applied to another repository with the unbundle or pull
1549 command. This is useful when direct push and pull are not
1549 command. This is useful when direct push and pull are not
1550 available or when exporting an entire repository is undesirable.
1550 available or when exporting an entire repository is undesirable.
1551
1551
1552 Applying bundles preserves all changeset contents including
1552 Applying bundles preserves all changeset contents including
1553 permissions, copy/rename information, and revision history.
1553 permissions, copy/rename information, and revision history.
1554
1554
1555 Returns 0 on success, 1 if no changes found.
1555 Returns 0 on success, 1 if no changes found.
1556 """
1556 """
1557 opts = pycompat.byteskwargs(opts)
1557 opts = pycompat.byteskwargs(opts)
1558 revs = None
1558 revs = None
1559 if b'rev' in opts:
1559 if b'rev' in opts:
1560 revstrings = opts[b'rev']
1560 revstrings = opts[b'rev']
1561 revs = logcmdutil.revrange(repo, revstrings)
1561 revs = logcmdutil.revrange(repo, revstrings)
1562 if revstrings and not revs:
1562 if revstrings and not revs:
1563 raise error.InputError(_(b'no commits to bundle'))
1563 raise error.InputError(_(b'no commits to bundle'))
1564
1564
1565 bundletype = opts.get(b'type', b'bzip2').lower()
1565 bundletype = opts.get(b'type', b'bzip2').lower()
1566 try:
1566 try:
1567 bundlespec = bundlecaches.parsebundlespec(
1567 bundlespec = bundlecaches.parsebundlespec(
1568 repo, bundletype, strict=False
1568 repo, bundletype, strict=False
1569 )
1569 )
1570 except error.UnsupportedBundleSpecification as e:
1570 except error.UnsupportedBundleSpecification as e:
1571 raise error.InputError(
1571 raise error.InputError(
1572 pycompat.bytestr(e),
1572 pycompat.bytestr(e),
1573 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1573 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1574 )
1574 )
1575 cgversion = bundlespec.contentopts[b"cg.version"]
1575 cgversion = bundlespec.contentopts[b"cg.version"]
1576
1576
1577 # Packed bundles are a pseudo bundle format for now.
1577 # Packed bundles are a pseudo bundle format for now.
1578 if cgversion == b's1':
1578 if cgversion == b's1':
1579 raise error.InputError(
1579 raise error.InputError(
1580 _(b'packed bundles cannot be produced by "hg bundle"'),
1580 _(b'packed bundles cannot be produced by "hg bundle"'),
1581 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1581 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1582 )
1582 )
1583
1583
1584 if opts.get(b'all'):
1584 if opts.get(b'all'):
1585 if dests:
1585 if dests:
1586 raise error.InputError(
1586 raise error.InputError(
1587 _(b"--all is incompatible with specifying destinations")
1587 _(b"--all is incompatible with specifying destinations")
1588 )
1588 )
1589 if opts.get(b'base'):
1589 if opts.get(b'base'):
1590 ui.warn(_(b"ignoring --base because --all was specified\n"))
1590 ui.warn(_(b"ignoring --base because --all was specified\n"))
1591 base = [nullrev]
1591 base = [nullrev]
1592 else:
1592 else:
1593 base = logcmdutil.revrange(repo, opts.get(b'base'))
1593 base = logcmdutil.revrange(repo, opts.get(b'base'))
1594 if cgversion not in changegroup.supportedoutgoingversions(repo):
1594 if cgversion not in changegroup.supportedoutgoingversions(repo):
1595 raise error.Abort(
1595 raise error.Abort(
1596 _(b"repository does not support bundle version %s") % cgversion
1596 _(b"repository does not support bundle version %s") % cgversion
1597 )
1597 )
1598
1598
1599 if base:
1599 if base:
1600 if dests:
1600 if dests:
1601 raise error.InputError(
1601 raise error.InputError(
1602 _(b"--base is incompatible with specifying destinations")
1602 _(b"--base is incompatible with specifying destinations")
1603 )
1603 )
1604 common = [repo[rev].node() for rev in base]
1604 common = [repo[rev].node() for rev in base]
1605 heads = [repo[r].node() for r in revs] if revs else None
1605 heads = [repo[r].node() for r in revs] if revs else None
1606 outgoing = discovery.outgoing(repo, common, heads)
1606 outgoing = discovery.outgoing(repo, common, heads)
1607 missing = outgoing.missing
1607 missing = outgoing.missing
1608 excluded = outgoing.excluded
1608 excluded = outgoing.excluded
1609 else:
1609 else:
1610 missing = set()
1610 missing = set()
1611 excluded = set()
1611 excluded = set()
1612 for path in urlutil.get_push_paths(repo, ui, dests):
1612 for path in urlutil.get_push_paths(repo, ui, dests):
1613 other = hg.peer(repo, opts, path.rawloc)
1613 other = hg.peer(repo, opts, path.rawloc)
1614 if revs is not None:
1614 if revs is not None:
1615 hex_revs = [repo[r].hex() for r in revs]
1615 hex_revs = [repo[r].hex() for r in revs]
1616 else:
1616 else:
1617 hex_revs = None
1617 hex_revs = None
1618 branches = (path.branch, [])
1618 branches = (path.branch, [])
1619 head_revs, checkout = hg.addbranchrevs(
1619 head_revs, checkout = hg.addbranchrevs(
1620 repo, repo, branches, hex_revs
1620 repo, repo, branches, hex_revs
1621 )
1621 )
1622 heads = (
1622 heads = (
1623 head_revs
1623 head_revs
1624 and pycompat.maplist(repo.lookup, head_revs)
1624 and pycompat.maplist(repo.lookup, head_revs)
1625 or head_revs
1625 or head_revs
1626 )
1626 )
1627 outgoing = discovery.findcommonoutgoing(
1627 outgoing = discovery.findcommonoutgoing(
1628 repo,
1628 repo,
1629 other,
1629 other,
1630 onlyheads=heads,
1630 onlyheads=heads,
1631 force=opts.get(b'force'),
1631 force=opts.get(b'force'),
1632 portable=True,
1632 portable=True,
1633 )
1633 )
1634 missing.update(outgoing.missing)
1634 missing.update(outgoing.missing)
1635 excluded.update(outgoing.excluded)
1635 excluded.update(outgoing.excluded)
1636
1636
1637 if not missing:
1637 if not missing:
1638 scmutil.nochangesfound(ui, repo, not base and excluded)
1638 scmutil.nochangesfound(ui, repo, not base and excluded)
1639 return 1
1639 return 1
1640
1640
1641 if heads:
1641 if heads:
1642 outgoing = discovery.outgoing(
1642 outgoing = discovery.outgoing(
1643 repo, missingroots=missing, ancestorsof=heads
1643 repo, missingroots=missing, ancestorsof=heads
1644 )
1644 )
1645 else:
1645 else:
1646 outgoing = discovery.outgoing(repo, missingroots=missing)
1646 outgoing = discovery.outgoing(repo, missingroots=missing)
1647 outgoing.excluded = sorted(excluded)
1647 outgoing.excluded = sorted(excluded)
1648
1648
1649 if cgversion == b'01': # bundle1
1649 if cgversion == b'01': # bundle1
1650 bversion = b'HG10' + bundlespec.wirecompression
1650 bversion = b'HG10' + bundlespec.wirecompression
1651 bcompression = None
1651 bcompression = None
1652 elif cgversion in (b'02', b'03'):
1652 elif cgversion in (b'02', b'03'):
1653 bversion = b'HG20'
1653 bversion = b'HG20'
1654 bcompression = bundlespec.wirecompression
1654 bcompression = bundlespec.wirecompression
1655 else:
1655 else:
1656 raise error.ProgrammingError(
1656 raise error.ProgrammingError(
1657 b'bundle: unexpected changegroup version %s' % cgversion
1657 b'bundle: unexpected changegroup version %s' % cgversion
1658 )
1658 )
1659
1659
1660 # TODO compression options should be derived from bundlespec parsing.
1660 # TODO compression options should be derived from bundlespec parsing.
1661 # This is a temporary hack to allow adjusting bundle compression
1661 # This is a temporary hack to allow adjusting bundle compression
1662 # level without a) formalizing the bundlespec changes to declare it
1662 # level without a) formalizing the bundlespec changes to declare it
1663 # b) introducing a command flag.
1663 # b) introducing a command flag.
1664 compopts = {}
1664 compopts = {}
1665 complevel = ui.configint(
1665 complevel = ui.configint(
1666 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1666 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1667 )
1667 )
1668 if complevel is None:
1668 if complevel is None:
1669 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1669 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1670 if complevel is not None:
1670 if complevel is not None:
1671 compopts[b'level'] = complevel
1671 compopts[b'level'] = complevel
1672
1672
1673 compthreads = ui.configint(
1673 compthreads = ui.configint(
1674 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1674 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1675 )
1675 )
1676 if compthreads is None:
1676 if compthreads is None:
1677 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1677 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1678 if compthreads is not None:
1678 if compthreads is not None:
1679 compopts[b'threads'] = compthreads
1679 compopts[b'threads'] = compthreads
1680
1680
1681 # Bundling of obsmarker and phases is optional as not all clients
1681 # Bundling of obsmarker and phases is optional as not all clients
1682 # support the necessary features.
1682 # support the necessary features.
1683 cfg = ui.configbool
1683 cfg = ui.configbool
1684 contentopts = {
1684 contentopts = {
1685 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1685 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1686 b'obsolescence-mandatory': cfg(
1686 b'obsolescence-mandatory': cfg(
1687 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1687 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1688 ),
1688 ),
1689 b'phases': cfg(b'experimental', b'bundle-phases'),
1689 b'phases': cfg(b'experimental', b'bundle-phases'),
1690 }
1690 }
1691 bundlespec.contentopts.update(contentopts)
1691 bundlespec.contentopts.update(contentopts)
1692
1692
1693 bundle2.writenewbundle(
1693 bundle2.writenewbundle(
1694 ui,
1694 ui,
1695 repo,
1695 repo,
1696 b'bundle',
1696 b'bundle',
1697 fname,
1697 fname,
1698 bversion,
1698 bversion,
1699 outgoing,
1699 outgoing,
1700 bundlespec.contentopts,
1700 bundlespec.contentopts,
1701 compression=bcompression,
1701 compression=bcompression,
1702 compopts=compopts,
1702 compopts=compopts,
1703 )
1703 )
1704
1704
1705
1705
1706 @command(
1706 @command(
1707 b'cat',
1707 b'cat',
1708 [
1708 [
1709 (
1709 (
1710 b'o',
1710 b'o',
1711 b'output',
1711 b'output',
1712 b'',
1712 b'',
1713 _(b'print output to file with formatted name'),
1713 _(b'print output to file with formatted name'),
1714 _(b'FORMAT'),
1714 _(b'FORMAT'),
1715 ),
1715 ),
1716 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1716 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1717 (b'', b'decode', None, _(b'apply any matching decode filter')),
1717 (b'', b'decode', None, _(b'apply any matching decode filter')),
1718 ]
1718 ]
1719 + walkopts
1719 + walkopts
1720 + formatteropts,
1720 + formatteropts,
1721 _(b'[OPTION]... FILE...'),
1721 _(b'[OPTION]... FILE...'),
1722 helpcategory=command.CATEGORY_FILE_CONTENTS,
1722 helpcategory=command.CATEGORY_FILE_CONTENTS,
1723 inferrepo=True,
1723 inferrepo=True,
1724 intents={INTENT_READONLY},
1724 intents={INTENT_READONLY},
1725 )
1725 )
1726 def cat(ui, repo, file1, *pats, **opts):
1726 def cat(ui, repo, file1, *pats, **opts):
1727 """output the current or given revision of files
1727 """output the current or given revision of files
1728
1728
1729 Print the specified files as they were at the given revision. If
1729 Print the specified files as they were at the given revision. If
1730 no revision is given, the parent of the working directory is used.
1730 no revision is given, the parent of the working directory is used.
1731
1731
1732 Output may be to a file, in which case the name of the file is
1732 Output may be to a file, in which case the name of the file is
1733 given using a template string. See :hg:`help templates`. In addition
1733 given using a template string. See :hg:`help templates`. In addition
1734 to the common template keywords, the following formatting rules are
1734 to the common template keywords, the following formatting rules are
1735 supported:
1735 supported:
1736
1736
1737 :``%%``: literal "%" character
1737 :``%%``: literal "%" character
1738 :``%s``: basename of file being printed
1738 :``%s``: basename of file being printed
1739 :``%d``: dirname of file being printed, or '.' if in repository root
1739 :``%d``: dirname of file being printed, or '.' if in repository root
1740 :``%p``: root-relative path name of file being printed
1740 :``%p``: root-relative path name of file being printed
1741 :``%H``: changeset hash (40 hexadecimal digits)
1741 :``%H``: changeset hash (40 hexadecimal digits)
1742 :``%R``: changeset revision number
1742 :``%R``: changeset revision number
1743 :``%h``: short-form changeset hash (12 hexadecimal digits)
1743 :``%h``: short-form changeset hash (12 hexadecimal digits)
1744 :``%r``: zero-padded changeset revision number
1744 :``%r``: zero-padded changeset revision number
1745 :``%b``: basename of the exporting repository
1745 :``%b``: basename of the exporting repository
1746 :``\\``: literal "\\" character
1746 :``\\``: literal "\\" character
1747
1747
1748 .. container:: verbose
1748 .. container:: verbose
1749
1749
1750 Template:
1750 Template:
1751
1751
1752 The following keywords are supported in addition to the common template
1752 The following keywords are supported in addition to the common template
1753 keywords and functions. See also :hg:`help templates`.
1753 keywords and functions. See also :hg:`help templates`.
1754
1754
1755 :data: String. File content.
1755 :data: String. File content.
1756 :path: String. Repository-absolute path of the file.
1756 :path: String. Repository-absolute path of the file.
1757
1757
1758 Returns 0 on success.
1758 Returns 0 on success.
1759 """
1759 """
1760 opts = pycompat.byteskwargs(opts)
1760 opts = pycompat.byteskwargs(opts)
1761 rev = opts.get(b'rev')
1761 rev = opts.get(b'rev')
1762 if rev:
1762 if rev:
1763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1764 ctx = logcmdutil.revsingle(repo, rev)
1764 ctx = logcmdutil.revsingle(repo, rev)
1765 m = scmutil.match(ctx, (file1,) + pats, opts)
1765 m = scmutil.match(ctx, (file1,) + pats, opts)
1766 fntemplate = opts.pop(b'output', b'')
1766 fntemplate = opts.pop(b'output', b'')
1767 if cmdutil.isstdiofilename(fntemplate):
1767 if cmdutil.isstdiofilename(fntemplate):
1768 fntemplate = b''
1768 fntemplate = b''
1769
1769
1770 if fntemplate:
1770 if fntemplate:
1771 fm = formatter.nullformatter(ui, b'cat', opts)
1771 fm = formatter.nullformatter(ui, b'cat', opts)
1772 else:
1772 else:
1773 ui.pager(b'cat')
1773 ui.pager(b'cat')
1774 fm = ui.formatter(b'cat', opts)
1774 fm = ui.formatter(b'cat', opts)
1775 with fm:
1775 with fm:
1776 return cmdutil.cat(
1776 return cmdutil.cat(
1777 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1777 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1778 )
1778 )
1779
1779
1780
1780
1781 @command(
1781 @command(
1782 b'clone',
1782 b'clone',
1783 [
1783 [
1784 (
1784 (
1785 b'U',
1785 b'U',
1786 b'noupdate',
1786 b'noupdate',
1787 None,
1787 None,
1788 _(
1788 _(
1789 b'the clone will include an empty working '
1789 b'the clone will include an empty working '
1790 b'directory (only a repository)'
1790 b'directory (only a repository)'
1791 ),
1791 ),
1792 ),
1792 ),
1793 (
1793 (
1794 b'u',
1794 b'u',
1795 b'updaterev',
1795 b'updaterev',
1796 b'',
1796 b'',
1797 _(b'revision, tag, or branch to check out'),
1797 _(b'revision, tag, or branch to check out'),
1798 _(b'REV'),
1798 _(b'REV'),
1799 ),
1799 ),
1800 (
1800 (
1801 b'r',
1801 b'r',
1802 b'rev',
1802 b'rev',
1803 [],
1803 [],
1804 _(
1804 _(
1805 b'do not clone everything, but include this changeset'
1805 b'do not clone everything, but include this changeset'
1806 b' and its ancestors'
1806 b' and its ancestors'
1807 ),
1807 ),
1808 _(b'REV'),
1808 _(b'REV'),
1809 ),
1809 ),
1810 (
1810 (
1811 b'b',
1811 b'b',
1812 b'branch',
1812 b'branch',
1813 [],
1813 [],
1814 _(
1814 _(
1815 b'do not clone everything, but include this branch\'s'
1815 b'do not clone everything, but include this branch\'s'
1816 b' changesets and their ancestors'
1816 b' changesets and their ancestors'
1817 ),
1817 ),
1818 _(b'BRANCH'),
1818 _(b'BRANCH'),
1819 ),
1819 ),
1820 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1820 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1821 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1821 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1822 (b'', b'stream', None, _(b'clone with minimal data processing')),
1822 (b'', b'stream', None, _(b'clone with minimal data processing')),
1823 ]
1823 ]
1824 + remoteopts,
1824 + remoteopts,
1825 _(b'[OPTION]... SOURCE [DEST]'),
1825 _(b'[OPTION]... SOURCE [DEST]'),
1826 helpcategory=command.CATEGORY_REPO_CREATION,
1826 helpcategory=command.CATEGORY_REPO_CREATION,
1827 helpbasic=True,
1827 helpbasic=True,
1828 norepo=True,
1828 norepo=True,
1829 )
1829 )
1830 def clone(ui, source, dest=None, **opts):
1830 def clone(ui, source, dest=None, **opts):
1831 """make a copy of an existing repository
1831 """make a copy of an existing repository
1832
1832
1833 Create a copy of an existing repository in a new directory.
1833 Create a copy of an existing repository in a new directory.
1834
1834
1835 If no destination directory name is specified, it defaults to the
1835 If no destination directory name is specified, it defaults to the
1836 basename of the source.
1836 basename of the source.
1837
1837
1838 The location of the source is added to the new repository's
1838 The location of the source is added to the new repository's
1839 ``.hg/hgrc`` file, as the default to be used for future pulls.
1839 ``.hg/hgrc`` file, as the default to be used for future pulls.
1840
1840
1841 Only local paths and ``ssh://`` URLs are supported as
1841 Only local paths and ``ssh://`` URLs are supported as
1842 destinations. For ``ssh://`` destinations, no working directory or
1842 destinations. For ``ssh://`` destinations, no working directory or
1843 ``.hg/hgrc`` will be created on the remote side.
1843 ``.hg/hgrc`` will be created on the remote side.
1844
1844
1845 If the source repository has a bookmark called '@' set, that
1845 If the source repository has a bookmark called '@' set, that
1846 revision will be checked out in the new repository by default.
1846 revision will be checked out in the new repository by default.
1847
1847
1848 To check out a particular version, use -u/--update, or
1848 To check out a particular version, use -u/--update, or
1849 -U/--noupdate to create a clone with no working directory.
1849 -U/--noupdate to create a clone with no working directory.
1850
1850
1851 To pull only a subset of changesets, specify one or more revisions
1851 To pull only a subset of changesets, specify one or more revisions
1852 identifiers with -r/--rev or branches with -b/--branch. The
1852 identifiers with -r/--rev or branches with -b/--branch. The
1853 resulting clone will contain only the specified changesets and
1853 resulting clone will contain only the specified changesets and
1854 their ancestors. These options (or 'clone src#rev dest') imply
1854 their ancestors. These options (or 'clone src#rev dest') imply
1855 --pull, even for local source repositories.
1855 --pull, even for local source repositories.
1856
1856
1857 In normal clone mode, the remote normalizes repository data into a common
1857 In normal clone mode, the remote normalizes repository data into a common
1858 exchange format and the receiving end translates this data into its local
1858 exchange format and the receiving end translates this data into its local
1859 storage format. --stream activates a different clone mode that essentially
1859 storage format. --stream activates a different clone mode that essentially
1860 copies repository files from the remote with minimal data processing. This
1860 copies repository files from the remote with minimal data processing. This
1861 significantly reduces the CPU cost of a clone both remotely and locally.
1861 significantly reduces the CPU cost of a clone both remotely and locally.
1862 However, it often increases the transferred data size by 30-40%. This can
1862 However, it often increases the transferred data size by 30-40%. This can
1863 result in substantially faster clones where I/O throughput is plentiful,
1863 result in substantially faster clones where I/O throughput is plentiful,
1864 especially for larger repositories. A side-effect of --stream clones is
1864 especially for larger repositories. A side-effect of --stream clones is
1865 that storage settings and requirements on the remote are applied locally:
1865 that storage settings and requirements on the remote are applied locally:
1866 a modern client may inherit legacy or inefficient storage used by the
1866 a modern client may inherit legacy or inefficient storage used by the
1867 remote or a legacy Mercurial client may not be able to clone from a
1867 remote or a legacy Mercurial client may not be able to clone from a
1868 modern Mercurial remote.
1868 modern Mercurial remote.
1869
1869
1870 .. note::
1870 .. note::
1871
1871
1872 Specifying a tag will include the tagged changeset but not the
1872 Specifying a tag will include the tagged changeset but not the
1873 changeset containing the tag.
1873 changeset containing the tag.
1874
1874
1875 .. container:: verbose
1875 .. container:: verbose
1876
1876
1877 For efficiency, hardlinks are used for cloning whenever the
1877 For efficiency, hardlinks are used for cloning whenever the
1878 source and destination are on the same filesystem (note this
1878 source and destination are on the same filesystem (note this
1879 applies only to the repository data, not to the working
1879 applies only to the repository data, not to the working
1880 directory). Some filesystems, such as AFS, implement hardlinking
1880 directory). Some filesystems, such as AFS, implement hardlinking
1881 incorrectly, but do not report errors. In these cases, use the
1881 incorrectly, but do not report errors. In these cases, use the
1882 --pull option to avoid hardlinking.
1882 --pull option to avoid hardlinking.
1883
1883
1884 Mercurial will update the working directory to the first applicable
1884 Mercurial will update the working directory to the first applicable
1885 revision from this list:
1885 revision from this list:
1886
1886
1887 a) null if -U or the source repository has no changesets
1887 a) null if -U or the source repository has no changesets
1888 b) if -u . and the source repository is local, the first parent of
1888 b) if -u . and the source repository is local, the first parent of
1889 the source repository's working directory
1889 the source repository's working directory
1890 c) the changeset specified with -u (if a branch name, this means the
1890 c) the changeset specified with -u (if a branch name, this means the
1891 latest head of that branch)
1891 latest head of that branch)
1892 d) the changeset specified with -r
1892 d) the changeset specified with -r
1893 e) the tipmost head specified with -b
1893 e) the tipmost head specified with -b
1894 f) the tipmost head specified with the url#branch source syntax
1894 f) the tipmost head specified with the url#branch source syntax
1895 g) the revision marked with the '@' bookmark, if present
1895 g) the revision marked with the '@' bookmark, if present
1896 h) the tipmost head of the default branch
1896 h) the tipmost head of the default branch
1897 i) tip
1897 i) tip
1898
1898
1899 When cloning from servers that support it, Mercurial may fetch
1899 When cloning from servers that support it, Mercurial may fetch
1900 pre-generated data from a server-advertised URL or inline from the
1900 pre-generated data from a server-advertised URL or inline from the
1901 same stream. When this is done, hooks operating on incoming changesets
1901 same stream. When this is done, hooks operating on incoming changesets
1902 and changegroups may fire more than once, once for each pre-generated
1902 and changegroups may fire more than once, once for each pre-generated
1903 bundle and as well as for any additional remaining data. In addition,
1903 bundle and as well as for any additional remaining data. In addition,
1904 if an error occurs, the repository may be rolled back to a partial
1904 if an error occurs, the repository may be rolled back to a partial
1905 clone. This behavior may change in future releases.
1905 clone. This behavior may change in future releases.
1906 See :hg:`help -e clonebundles` for more.
1906 See :hg:`help -e clonebundles` for more.
1907
1907
1908 Examples:
1908 Examples:
1909
1909
1910 - clone a remote repository to a new directory named hg/::
1910 - clone a remote repository to a new directory named hg/::
1911
1911
1912 hg clone https://www.mercurial-scm.org/repo/hg/
1912 hg clone https://www.mercurial-scm.org/repo/hg/
1913
1913
1914 - create a lightweight local clone::
1914 - create a lightweight local clone::
1915
1915
1916 hg clone project/ project-feature/
1916 hg clone project/ project-feature/
1917
1917
1918 - clone from an absolute path on an ssh server (note double-slash)::
1918 - clone from an absolute path on an ssh server (note double-slash)::
1919
1919
1920 hg clone ssh://user@server//home/projects/alpha/
1920 hg clone ssh://user@server//home/projects/alpha/
1921
1921
1922 - do a streaming clone while checking out a specified version::
1922 - do a streaming clone while checking out a specified version::
1923
1923
1924 hg clone --stream http://server/repo -u 1.5
1924 hg clone --stream http://server/repo -u 1.5
1925
1925
1926 - create a repository without changesets after a particular revision::
1926 - create a repository without changesets after a particular revision::
1927
1927
1928 hg clone -r 04e544 experimental/ good/
1928 hg clone -r 04e544 experimental/ good/
1929
1929
1930 - clone (and track) a particular named branch::
1930 - clone (and track) a particular named branch::
1931
1931
1932 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1932 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1933
1933
1934 See :hg:`help urls` for details on specifying URLs.
1934 See :hg:`help urls` for details on specifying URLs.
1935
1935
1936 Returns 0 on success.
1936 Returns 0 on success.
1937 """
1937 """
1938 opts = pycompat.byteskwargs(opts)
1938 opts = pycompat.byteskwargs(opts)
1939 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1939 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1940
1940
1941 # --include/--exclude can come from narrow or sparse.
1941 # --include/--exclude can come from narrow or sparse.
1942 includepats, excludepats = None, None
1942 includepats, excludepats = None, None
1943
1943
1944 # hg.clone() differentiates between None and an empty set. So make sure
1944 # hg.clone() differentiates between None and an empty set. So make sure
1945 # patterns are sets if narrow is requested without patterns.
1945 # patterns are sets if narrow is requested without patterns.
1946 if opts.get(b'narrow'):
1946 if opts.get(b'narrow'):
1947 includepats = set()
1947 includepats = set()
1948 excludepats = set()
1948 excludepats = set()
1949
1949
1950 if opts.get(b'include'):
1950 if opts.get(b'include'):
1951 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1951 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1952 if opts.get(b'exclude'):
1952 if opts.get(b'exclude'):
1953 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1953 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1954
1954
1955 r = hg.clone(
1955 r = hg.clone(
1956 ui,
1956 ui,
1957 opts,
1957 opts,
1958 source,
1958 source,
1959 dest,
1959 dest,
1960 pull=opts.get(b'pull'),
1960 pull=opts.get(b'pull'),
1961 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1961 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1962 revs=opts.get(b'rev'),
1962 revs=opts.get(b'rev'),
1963 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1963 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1964 branch=opts.get(b'branch'),
1964 branch=opts.get(b'branch'),
1965 shareopts=opts.get(b'shareopts'),
1965 shareopts=opts.get(b'shareopts'),
1966 storeincludepats=includepats,
1966 storeincludepats=includepats,
1967 storeexcludepats=excludepats,
1967 storeexcludepats=excludepats,
1968 depth=opts.get(b'depth') or None,
1968 depth=opts.get(b'depth') or None,
1969 )
1969 )
1970
1970
1971 return r is None
1971 return r is None
1972
1972
1973
1973
1974 @command(
1974 @command(
1975 b'commit|ci',
1975 b'commit|ci',
1976 [
1976 [
1977 (
1977 (
1978 b'A',
1978 b'A',
1979 b'addremove',
1979 b'addremove',
1980 None,
1980 None,
1981 _(b'mark new/missing files as added/removed before committing'),
1981 _(b'mark new/missing files as added/removed before committing'),
1982 ),
1982 ),
1983 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1983 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1984 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1984 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1985 (b's', b'secret', None, _(b'use the secret phase for committing')),
1985 (b's', b'secret', None, _(b'use the secret phase for committing')),
1986 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1986 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1987 (
1987 (
1988 b'',
1988 b'',
1989 b'force-close-branch',
1989 b'force-close-branch',
1990 None,
1990 None,
1991 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1991 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1992 ),
1992 ),
1993 (b'i', b'interactive', None, _(b'use interactive mode')),
1993 (b'i', b'interactive', None, _(b'use interactive mode')),
1994 ]
1994 ]
1995 + walkopts
1995 + walkopts
1996 + commitopts
1996 + commitopts
1997 + commitopts2
1997 + commitopts2
1998 + subrepoopts,
1998 + subrepoopts,
1999 _(b'[OPTION]... [FILE]...'),
1999 _(b'[OPTION]... [FILE]...'),
2000 helpcategory=command.CATEGORY_COMMITTING,
2000 helpcategory=command.CATEGORY_COMMITTING,
2001 helpbasic=True,
2001 helpbasic=True,
2002 inferrepo=True,
2002 inferrepo=True,
2003 )
2003 )
2004 def commit(ui, repo, *pats, **opts):
2004 def commit(ui, repo, *pats, **opts):
2005 """commit the specified files or all outstanding changes
2005 """commit the specified files or all outstanding changes
2006
2006
2007 Commit changes to the given files into the repository. Unlike a
2007 Commit changes to the given files into the repository. Unlike a
2008 centralized SCM, this operation is a local operation. See
2008 centralized SCM, this operation is a local operation. See
2009 :hg:`push` for a way to actively distribute your changes.
2009 :hg:`push` for a way to actively distribute your changes.
2010
2010
2011 If a list of files is omitted, all changes reported by :hg:`status`
2011 If a list of files is omitted, all changes reported by :hg:`status`
2012 will be committed.
2012 will be committed.
2013
2013
2014 If you are committing the result of a merge, do not provide any
2014 If you are committing the result of a merge, do not provide any
2015 filenames or -I/-X filters.
2015 filenames or -I/-X filters.
2016
2016
2017 If no commit message is specified, Mercurial starts your
2017 If no commit message is specified, Mercurial starts your
2018 configured editor where you can enter a message. In case your
2018 configured editor where you can enter a message. In case your
2019 commit fails, you will find a backup of your message in
2019 commit fails, you will find a backup of your message in
2020 ``.hg/last-message.txt``.
2020 ``.hg/last-message.txt``.
2021
2021
2022 The --close-branch flag can be used to mark the current branch
2022 The --close-branch flag can be used to mark the current branch
2023 head closed. When all heads of a branch are closed, the branch
2023 head closed. When all heads of a branch are closed, the branch
2024 will be considered closed and no longer listed.
2024 will be considered closed and no longer listed.
2025
2025
2026 The --amend flag can be used to amend the parent of the
2026 The --amend flag can be used to amend the parent of the
2027 working directory with a new commit that contains the changes
2027 working directory with a new commit that contains the changes
2028 in the parent in addition to those currently reported by :hg:`status`,
2028 in the parent in addition to those currently reported by :hg:`status`,
2029 if there are any. The old commit is stored in a backup bundle in
2029 if there are any. The old commit is stored in a backup bundle in
2030 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2030 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2031 on how to restore it).
2031 on how to restore it).
2032
2032
2033 Message, user and date are taken from the amended commit unless
2033 Message, user and date are taken from the amended commit unless
2034 specified. When a message isn't specified on the command line,
2034 specified. When a message isn't specified on the command line,
2035 the editor will open with the message of the amended commit.
2035 the editor will open with the message of the amended commit.
2036
2036
2037 It is not possible to amend public changesets (see :hg:`help phases`)
2037 It is not possible to amend public changesets (see :hg:`help phases`)
2038 or changesets that have children.
2038 or changesets that have children.
2039
2039
2040 See :hg:`help dates` for a list of formats valid for -d/--date.
2040 See :hg:`help dates` for a list of formats valid for -d/--date.
2041
2041
2042 Returns 0 on success, 1 if nothing changed.
2042 Returns 0 on success, 1 if nothing changed.
2043
2043
2044 .. container:: verbose
2044 .. container:: verbose
2045
2045
2046 Examples:
2046 Examples:
2047
2047
2048 - commit all files ending in .py::
2048 - commit all files ending in .py::
2049
2049
2050 hg commit --include "set:**.py"
2050 hg commit --include "set:**.py"
2051
2051
2052 - commit all non-binary files::
2052 - commit all non-binary files::
2053
2053
2054 hg commit --exclude "set:binary()"
2054 hg commit --exclude "set:binary()"
2055
2055
2056 - amend the current commit and set the date to now::
2056 - amend the current commit and set the date to now::
2057
2057
2058 hg commit --amend --date now
2058 hg commit --amend --date now
2059 """
2059 """
2060 with repo.wlock(), repo.lock():
2060 with repo.wlock(), repo.lock():
2061 return _docommit(ui, repo, *pats, **opts)
2061 return _docommit(ui, repo, *pats, **opts)
2062
2062
2063
2063
2064 def _docommit(ui, repo, *pats, **opts):
2064 def _docommit(ui, repo, *pats, **opts):
2065 if opts.get('interactive'):
2065 if opts.get('interactive'):
2066 opts.pop('interactive')
2066 opts.pop('interactive')
2067 ret = cmdutil.dorecord(
2067 ret = cmdutil.dorecord(
2068 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2068 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2069 )
2069 )
2070 # ret can be 0 (no changes to record) or the value returned by
2070 # ret can be 0 (no changes to record) or the value returned by
2071 # commit(), 1 if nothing changed or None on success.
2071 # commit(), 1 if nothing changed or None on success.
2072 return 1 if ret == 0 else ret
2072 return 1 if ret == 0 else ret
2073
2073
2074 if opts.get('subrepos'):
2074 if opts.get('subrepos'):
2075 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2075 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2076 # Let --subrepos on the command line override config setting.
2076 # Let --subrepos on the command line override config setting.
2077 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2077 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2078
2078
2079 cmdutil.checkunfinished(repo, commit=True)
2079 cmdutil.checkunfinished(repo, commit=True)
2080
2080
2081 branch = repo[None].branch()
2081 branch = repo[None].branch()
2082 bheads = repo.branchheads(branch)
2082 bheads = repo.branchheads(branch)
2083 tip = repo.changelog.tip()
2083 tip = repo.changelog.tip()
2084
2084
2085 extra = {}
2085 extra = {}
2086 if opts.get('close_branch') or opts.get('force_close_branch'):
2086 if opts.get('close_branch') or opts.get('force_close_branch'):
2087 extra[b'close'] = b'1'
2087 extra[b'close'] = b'1'
2088
2088
2089 if repo[b'.'].closesbranch():
2089 if repo[b'.'].closesbranch():
2090 raise error.InputError(
2090 raise error.InputError(
2091 _(b'current revision is already a branch closing head')
2091 _(b'current revision is already a branch closing head')
2092 )
2092 )
2093 elif not bheads:
2093 elif not bheads:
2094 raise error.InputError(
2094 raise error.InputError(
2095 _(b'branch "%s" has no heads to close') % branch
2095 _(b'branch "%s" has no heads to close') % branch
2096 )
2096 )
2097 elif (
2097 elif (
2098 branch == repo[b'.'].branch()
2098 branch == repo[b'.'].branch()
2099 and repo[b'.'].node() not in bheads
2099 and repo[b'.'].node() not in bheads
2100 and not opts.get('force_close_branch')
2100 and not opts.get('force_close_branch')
2101 ):
2101 ):
2102 hint = _(
2102 hint = _(
2103 b'use --force-close-branch to close branch from a non-head'
2103 b'use --force-close-branch to close branch from a non-head'
2104 b' changeset'
2104 b' changeset'
2105 )
2105 )
2106 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2106 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2107 elif opts.get('amend'):
2107 elif opts.get('amend'):
2108 if (
2108 if (
2109 repo[b'.'].p1().branch() != branch
2109 repo[b'.'].p1().branch() != branch
2110 and repo[b'.'].p2().branch() != branch
2110 and repo[b'.'].p2().branch() != branch
2111 ):
2111 ):
2112 raise error.InputError(_(b'can only close branch heads'))
2112 raise error.InputError(_(b'can only close branch heads'))
2113
2113
2114 if opts.get('amend'):
2114 if opts.get('amend'):
2115 if ui.configbool(b'ui', b'commitsubrepos'):
2115 if ui.configbool(b'ui', b'commitsubrepos'):
2116 raise error.InputError(
2116 raise error.InputError(
2117 _(b'cannot amend with ui.commitsubrepos enabled')
2117 _(b'cannot amend with ui.commitsubrepos enabled')
2118 )
2118 )
2119
2119
2120 old = repo[b'.']
2120 old = repo[b'.']
2121 rewriteutil.precheck(repo, [old.rev()], b'amend')
2121 rewriteutil.precheck(repo, [old.rev()], b'amend')
2122
2122
2123 # Currently histedit gets confused if an amend happens while histedit
2123 # Currently histedit gets confused if an amend happens while histedit
2124 # is in progress. Since we have a checkunfinished command, we are
2124 # is in progress. Since we have a checkunfinished command, we are
2125 # temporarily honoring it.
2125 # temporarily honoring it.
2126 #
2126 #
2127 # Note: eventually this guard will be removed. Please do not expect
2127 # Note: eventually this guard will be removed. Please do not expect
2128 # this behavior to remain.
2128 # this behavior to remain.
2129 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2129 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2130 cmdutil.checkunfinished(repo)
2130 cmdutil.checkunfinished(repo)
2131
2131
2132 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2132 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2133 opts = pycompat.byteskwargs(opts)
2133 opts = pycompat.byteskwargs(opts)
2134 if node == old.node():
2134 if node == old.node():
2135 ui.status(_(b"nothing changed\n"))
2135 ui.status(_(b"nothing changed\n"))
2136 return 1
2136 return 1
2137 else:
2137 else:
2138
2138
2139 def commitfunc(ui, repo, message, match, opts):
2139 def commitfunc(ui, repo, message, match, opts):
2140 overrides = {}
2140 overrides = {}
2141 if opts.get(b'secret'):
2141 if opts.get(b'secret'):
2142 overrides[(b'phases', b'new-commit')] = b'secret'
2142 overrides[(b'phases', b'new-commit')] = b'secret'
2143
2143
2144 baseui = repo.baseui
2144 baseui = repo.baseui
2145 with baseui.configoverride(overrides, b'commit'):
2145 with baseui.configoverride(overrides, b'commit'):
2146 with ui.configoverride(overrides, b'commit'):
2146 with ui.configoverride(overrides, b'commit'):
2147 editform = cmdutil.mergeeditform(
2147 editform = cmdutil.mergeeditform(
2148 repo[None], b'commit.normal'
2148 repo[None], b'commit.normal'
2149 )
2149 )
2150 editor = cmdutil.getcommiteditor(
2150 editor = cmdutil.getcommiteditor(
2151 editform=editform, **pycompat.strkwargs(opts)
2151 editform=editform, **pycompat.strkwargs(opts)
2152 )
2152 )
2153 return repo.commit(
2153 return repo.commit(
2154 message,
2154 message,
2155 opts.get(b'user'),
2155 opts.get(b'user'),
2156 opts.get(b'date'),
2156 opts.get(b'date'),
2157 match,
2157 match,
2158 editor=editor,
2158 editor=editor,
2159 extra=extra,
2159 extra=extra,
2160 )
2160 )
2161
2161
2162 opts = pycompat.byteskwargs(opts)
2162 opts = pycompat.byteskwargs(opts)
2163 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2163 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2164
2164
2165 if not node:
2165 if not node:
2166 stat = cmdutil.postcommitstatus(repo, pats, opts)
2166 stat = cmdutil.postcommitstatus(repo, pats, opts)
2167 if stat.deleted:
2167 if stat.deleted:
2168 ui.status(
2168 ui.status(
2169 _(
2169 _(
2170 b"nothing changed (%d missing files, see "
2170 b"nothing changed (%d missing files, see "
2171 b"'hg status')\n"
2171 b"'hg status')\n"
2172 )
2172 )
2173 % len(stat.deleted)
2173 % len(stat.deleted)
2174 )
2174 )
2175 else:
2175 else:
2176 ui.status(_(b"nothing changed\n"))
2176 ui.status(_(b"nothing changed\n"))
2177 return 1
2177 return 1
2178
2178
2179 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2179 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2180
2180
2181 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2181 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2182 status(
2182 status(
2183 ui,
2183 ui,
2184 repo,
2184 repo,
2185 modified=True,
2185 modified=True,
2186 added=True,
2186 added=True,
2187 removed=True,
2187 removed=True,
2188 deleted=True,
2188 deleted=True,
2189 unknown=True,
2189 unknown=True,
2190 subrepos=opts.get(b'subrepos'),
2190 subrepos=opts.get(b'subrepos'),
2191 )
2191 )
2192
2192
2193
2193
2194 @command(
2194 @command(
2195 b'config|showconfig|debugconfig',
2195 b'config|showconfig|debugconfig',
2196 [
2196 [
2197 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2197 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2198 # This is experimental because we need
2198 # This is experimental because we need
2199 # * reasonable behavior around aliases,
2199 # * reasonable behavior around aliases,
2200 # * decide if we display [debug] [experimental] and [devel] section par
2200 # * decide if we display [debug] [experimental] and [devel] section par
2201 # default
2201 # default
2202 # * some way to display "generic" config entry (the one matching
2202 # * some way to display "generic" config entry (the one matching
2203 # regexp,
2203 # regexp,
2204 # * proper display of the different value type
2204 # * proper display of the different value type
2205 # * a better way to handle <DYNAMIC> values (and variable types),
2205 # * a better way to handle <DYNAMIC> values (and variable types),
2206 # * maybe some type information ?
2206 # * maybe some type information ?
2207 (
2207 (
2208 b'',
2208 b'',
2209 b'exp-all-known',
2209 b'exp-all-known',
2210 None,
2210 None,
2211 _(b'show all known config option (EXPERIMENTAL)'),
2211 _(b'show all known config option (EXPERIMENTAL)'),
2212 ),
2212 ),
2213 (b'e', b'edit', None, _(b'edit user config')),
2213 (b'e', b'edit', None, _(b'edit user config')),
2214 (b'l', b'local', None, _(b'edit repository config')),
2214 (b'l', b'local', None, _(b'edit repository config')),
2215 (b'', b'source', None, _(b'show source of configuration value')),
2215 (b'', b'source', None, _(b'show source of configuration value')),
2216 (
2216 (
2217 b'',
2217 b'',
2218 b'shared',
2218 b'shared',
2219 None,
2219 None,
2220 _(b'edit shared source repository config (EXPERIMENTAL)'),
2220 _(b'edit shared source repository config (EXPERIMENTAL)'),
2221 ),
2221 ),
2222 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2222 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2223 (b'g', b'global', None, _(b'edit global config')),
2223 (b'g', b'global', None, _(b'edit global config')),
2224 ]
2224 ]
2225 + formatteropts,
2225 + formatteropts,
2226 _(b'[-u] [NAME]...'),
2226 _(b'[-u] [NAME]...'),
2227 helpcategory=command.CATEGORY_HELP,
2227 helpcategory=command.CATEGORY_HELP,
2228 optionalrepo=True,
2228 optionalrepo=True,
2229 intents={INTENT_READONLY},
2229 intents={INTENT_READONLY},
2230 )
2230 )
2231 def config(ui, repo, *values, **opts):
2231 def config(ui, repo, *values, **opts):
2232 """show combined config settings from all hgrc files
2232 """show combined config settings from all hgrc files
2233
2233
2234 With no arguments, print names and values of all config items.
2234 With no arguments, print names and values of all config items.
2235
2235
2236 With one argument of the form section.name, print just the value
2236 With one argument of the form section.name, print just the value
2237 of that config item.
2237 of that config item.
2238
2238
2239 With multiple arguments, print names and values of all config
2239 With multiple arguments, print names and values of all config
2240 items with matching section names or section.names.
2240 items with matching section names or section.names.
2241
2241
2242 With --edit, start an editor on the user-level config file. With
2242 With --edit, start an editor on the user-level config file. With
2243 --global, edit the system-wide config file. With --local, edit the
2243 --global, edit the system-wide config file. With --local, edit the
2244 repository-level config file.
2244 repository-level config file.
2245
2245
2246 With --source, the source (filename and line number) is printed
2246 With --source, the source (filename and line number) is printed
2247 for each config item.
2247 for each config item.
2248
2248
2249 See :hg:`help config` for more information about config files.
2249 See :hg:`help config` for more information about config files.
2250
2250
2251 .. container:: verbose
2251 .. container:: verbose
2252
2252
2253 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2253 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2254 This file is not shared across shares when in share-safe mode.
2254 This file is not shared across shares when in share-safe mode.
2255
2255
2256 Template:
2256 Template:
2257
2257
2258 The following keywords are supported. See also :hg:`help templates`.
2258 The following keywords are supported. See also :hg:`help templates`.
2259
2259
2260 :name: String. Config name.
2260 :name: String. Config name.
2261 :source: String. Filename and line number where the item is defined.
2261 :source: String. Filename and line number where the item is defined.
2262 :value: String. Config value.
2262 :value: String. Config value.
2263
2263
2264 The --shared flag can be used to edit the config file of shared source
2264 The --shared flag can be used to edit the config file of shared source
2265 repository. It only works when you have shared using the experimental
2265 repository. It only works when you have shared using the experimental
2266 share safe feature.
2266 share safe feature.
2267
2267
2268 Returns 0 on success, 1 if NAME does not exist.
2268 Returns 0 on success, 1 if NAME does not exist.
2269
2269
2270 """
2270 """
2271
2271
2272 opts = pycompat.byteskwargs(opts)
2272 opts = pycompat.byteskwargs(opts)
2273 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2273 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2274 if any(opts.get(o) for o in editopts):
2274 if any(opts.get(o) for o in editopts):
2275 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2275 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2276 if opts.get(b'local'):
2276 if opts.get(b'local'):
2277 if not repo:
2277 if not repo:
2278 raise error.InputError(
2278 raise error.InputError(
2279 _(b"can't use --local outside a repository")
2279 _(b"can't use --local outside a repository")
2280 )
2280 )
2281 paths = [repo.vfs.join(b'hgrc')]
2281 paths = [repo.vfs.join(b'hgrc')]
2282 elif opts.get(b'global'):
2282 elif opts.get(b'global'):
2283 paths = rcutil.systemrcpath()
2283 paths = rcutil.systemrcpath()
2284 elif opts.get(b'shared'):
2284 elif opts.get(b'shared'):
2285 if not repo.shared():
2285 if not repo.shared():
2286 raise error.InputError(
2286 raise error.InputError(
2287 _(b"repository is not shared; can't use --shared")
2287 _(b"repository is not shared; can't use --shared")
2288 )
2288 )
2289 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2289 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2290 raise error.InputError(
2290 raise error.InputError(
2291 _(
2291 _(
2292 b"share safe feature not enabled; "
2292 b"share safe feature not enabled; "
2293 b"unable to edit shared source repository config"
2293 b"unable to edit shared source repository config"
2294 )
2294 )
2295 )
2295 )
2296 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2296 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2297 elif opts.get(b'non_shared'):
2297 elif opts.get(b'non_shared'):
2298 paths = [repo.vfs.join(b'hgrc-not-shared')]
2298 paths = [repo.vfs.join(b'hgrc-not-shared')]
2299 else:
2299 else:
2300 paths = rcutil.userrcpath()
2300 paths = rcutil.userrcpath()
2301
2301
2302 for f in paths:
2302 for f in paths:
2303 if os.path.exists(f):
2303 if os.path.exists(f):
2304 break
2304 break
2305 else:
2305 else:
2306 if opts.get(b'global'):
2306 if opts.get(b'global'):
2307 samplehgrc = uimod.samplehgrcs[b'global']
2307 samplehgrc = uimod.samplehgrcs[b'global']
2308 elif opts.get(b'local'):
2308 elif opts.get(b'local'):
2309 samplehgrc = uimod.samplehgrcs[b'local']
2309 samplehgrc = uimod.samplehgrcs[b'local']
2310 else:
2310 else:
2311 samplehgrc = uimod.samplehgrcs[b'user']
2311 samplehgrc = uimod.samplehgrcs[b'user']
2312
2312
2313 f = paths[0]
2313 f = paths[0]
2314 fp = open(f, b"wb")
2314 fp = open(f, b"wb")
2315 fp.write(util.tonativeeol(samplehgrc))
2315 fp.write(util.tonativeeol(samplehgrc))
2316 fp.close()
2316 fp.close()
2317
2317
2318 editor = ui.geteditor()
2318 editor = ui.geteditor()
2319 ui.system(
2319 ui.system(
2320 b"%s \"%s\"" % (editor, f),
2320 b"%s \"%s\"" % (editor, f),
2321 onerr=error.InputError,
2321 onerr=error.InputError,
2322 errprefix=_(b"edit failed"),
2322 errprefix=_(b"edit failed"),
2323 blockedtag=b'config_edit',
2323 blockedtag=b'config_edit',
2324 )
2324 )
2325 return
2325 return
2326 ui.pager(b'config')
2326 ui.pager(b'config')
2327 fm = ui.formatter(b'config', opts)
2327 fm = ui.formatter(b'config', opts)
2328 for t, f in rcutil.rccomponents():
2328 for t, f in rcutil.rccomponents():
2329 if t == b'path':
2329 if t == b'path':
2330 ui.debug(b'read config from: %s\n' % f)
2330 ui.debug(b'read config from: %s\n' % f)
2331 elif t == b'resource':
2331 elif t == b'resource':
2332 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2332 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2333 elif t == b'items':
2333 elif t == b'items':
2334 # Don't print anything for 'items'.
2334 # Don't print anything for 'items'.
2335 pass
2335 pass
2336 else:
2336 else:
2337 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2337 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2338 untrusted = bool(opts.get(b'untrusted'))
2338 untrusted = bool(opts.get(b'untrusted'))
2339
2339
2340 selsections = selentries = []
2340 selsections = selentries = []
2341 if values:
2341 if values:
2342 selsections = [v for v in values if b'.' not in v]
2342 selsections = [v for v in values if b'.' not in v]
2343 selentries = [v for v in values if b'.' in v]
2343 selentries = [v for v in values if b'.' in v]
2344 uniquesel = len(selentries) == 1 and not selsections
2344 uniquesel = len(selentries) == 1 and not selsections
2345 selsections = set(selsections)
2345 selsections = set(selsections)
2346 selentries = set(selentries)
2346 selentries = set(selentries)
2347
2347
2348 matched = False
2348 matched = False
2349 all_known = opts[b'exp_all_known']
2349 all_known = opts[b'exp_all_known']
2350 show_source = ui.debugflag or opts.get(b'source')
2350 show_source = ui.debugflag or opts.get(b'source')
2351 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2351 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2352 for section, name, value in entries:
2352 for section, name, value in entries:
2353 source = ui.configsource(section, name, untrusted)
2353 source = ui.configsource(section, name, untrusted)
2354 value = pycompat.bytestr(value)
2354 value = pycompat.bytestr(value)
2355 defaultvalue = ui.configdefault(section, name)
2355 defaultvalue = ui.configdefault(section, name)
2356 if fm.isplain():
2356 if fm.isplain():
2357 source = source or b'none'
2357 source = source or b'none'
2358 value = value.replace(b'\n', b'\\n')
2358 value = value.replace(b'\n', b'\\n')
2359 entryname = section + b'.' + name
2359 entryname = section + b'.' + name
2360 if values and not (section in selsections or entryname in selentries):
2360 if values and not (section in selsections or entryname in selentries):
2361 continue
2361 continue
2362 fm.startitem()
2362 fm.startitem()
2363 fm.condwrite(show_source, b'source', b'%s: ', source)
2363 fm.condwrite(show_source, b'source', b'%s: ', source)
2364 if uniquesel:
2364 if uniquesel:
2365 fm.data(name=entryname)
2365 fm.data(name=entryname)
2366 fm.write(b'value', b'%s\n', value)
2366 fm.write(b'value', b'%s\n', value)
2367 else:
2367 else:
2368 fm.write(b'name value', b'%s=%s\n', entryname, value)
2368 fm.write(b'name value', b'%s=%s\n', entryname, value)
2369 if formatter.isprintable(defaultvalue):
2369 if formatter.isprintable(defaultvalue):
2370 fm.data(defaultvalue=defaultvalue)
2370 fm.data(defaultvalue=defaultvalue)
2371 elif isinstance(defaultvalue, list) and all(
2371 elif isinstance(defaultvalue, list) and all(
2372 formatter.isprintable(e) for e in defaultvalue
2372 formatter.isprintable(e) for e in defaultvalue
2373 ):
2373 ):
2374 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2374 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2375 # TODO: no idea how to process unsupported defaultvalue types
2375 # TODO: no idea how to process unsupported defaultvalue types
2376 matched = True
2376 matched = True
2377 fm.end()
2377 fm.end()
2378 if matched:
2378 if matched:
2379 return 0
2379 return 0
2380 return 1
2380 return 1
2381
2381
2382
2382
2383 @command(
2383 @command(
2384 b'continue',
2384 b'continue',
2385 dryrunopts,
2385 dryrunopts,
2386 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2386 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2387 helpbasic=True,
2387 helpbasic=True,
2388 )
2388 )
2389 def continuecmd(ui, repo, **opts):
2389 def continuecmd(ui, repo, **opts):
2390 """resumes an interrupted operation (EXPERIMENTAL)
2390 """resumes an interrupted operation (EXPERIMENTAL)
2391
2391
2392 Finishes a multistep operation like graft, histedit, rebase, merge,
2392 Finishes a multistep operation like graft, histedit, rebase, merge,
2393 and unshelve if they are in an interrupted state.
2393 and unshelve if they are in an interrupted state.
2394
2394
2395 use --dry-run/-n to dry run the command.
2395 use --dry-run/-n to dry run the command.
2396 """
2396 """
2397 dryrun = opts.get('dry_run')
2397 dryrun = opts.get('dry_run')
2398 contstate = cmdutil.getunfinishedstate(repo)
2398 contstate = cmdutil.getunfinishedstate(repo)
2399 if not contstate:
2399 if not contstate:
2400 raise error.StateError(_(b'no operation in progress'))
2400 raise error.StateError(_(b'no operation in progress'))
2401 if not contstate.continuefunc:
2401 if not contstate.continuefunc:
2402 raise error.StateError(
2402 raise error.StateError(
2403 (
2403 (
2404 _(b"%s in progress but does not support 'hg continue'")
2404 _(b"%s in progress but does not support 'hg continue'")
2405 % (contstate._opname)
2405 % (contstate._opname)
2406 ),
2406 ),
2407 hint=contstate.continuemsg(),
2407 hint=contstate.continuemsg(),
2408 )
2408 )
2409 if dryrun:
2409 if dryrun:
2410 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2410 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2411 return
2411 return
2412 return contstate.continuefunc(ui, repo)
2412 return contstate.continuefunc(ui, repo)
2413
2413
2414
2414
2415 @command(
2415 @command(
2416 b'copy|cp',
2416 b'copy|cp',
2417 [
2417 [
2418 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2418 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2419 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2419 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2420 (
2420 (
2421 b'',
2421 b'',
2422 b'at-rev',
2422 b'at-rev',
2423 b'',
2423 b'',
2424 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2424 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2425 _(b'REV'),
2425 _(b'REV'),
2426 ),
2426 ),
2427 (
2427 (
2428 b'f',
2428 b'f',
2429 b'force',
2429 b'force',
2430 None,
2430 None,
2431 _(b'forcibly copy over an existing managed file'),
2431 _(b'forcibly copy over an existing managed file'),
2432 ),
2432 ),
2433 ]
2433 ]
2434 + walkopts
2434 + walkopts
2435 + dryrunopts,
2435 + dryrunopts,
2436 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2436 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2437 helpcategory=command.CATEGORY_FILE_CONTENTS,
2437 helpcategory=command.CATEGORY_FILE_CONTENTS,
2438 )
2438 )
2439 def copy(ui, repo, *pats, **opts):
2439 def copy(ui, repo, *pats, **opts):
2440 """mark files as copied for the next commit
2440 """mark files as copied for the next commit
2441
2441
2442 Mark dest as having copies of source files. If dest is a
2442 Mark dest as having copies of source files. If dest is a
2443 directory, copies are put in that directory. If dest is a file,
2443 directory, copies are put in that directory. If dest is a file,
2444 the source must be a single file.
2444 the source must be a single file.
2445
2445
2446 By default, this command copies the contents of files as they
2446 By default, this command copies the contents of files as they
2447 exist in the working directory. If invoked with -A/--after, the
2447 exist in the working directory. If invoked with -A/--after, the
2448 operation is recorded, but no copying is performed.
2448 operation is recorded, but no copying is performed.
2449
2449
2450 To undo marking a destination file as copied, use --forget. With that
2450 To undo marking a destination file as copied, use --forget. With that
2451 option, all given (positional) arguments are unmarked as copies. The
2451 option, all given (positional) arguments are unmarked as copies. The
2452 destination file(s) will be left in place (still tracked). Note that
2452 destination file(s) will be left in place (still tracked). Note that
2453 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2453 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2454
2454
2455 This command takes effect with the next commit by default.
2455 This command takes effect with the next commit by default.
2456
2456
2457 Returns 0 on success, 1 if errors are encountered.
2457 Returns 0 on success, 1 if errors are encountered.
2458 """
2458 """
2459 opts = pycompat.byteskwargs(opts)
2459 opts = pycompat.byteskwargs(opts)
2460 with repo.wlock():
2460 with repo.wlock():
2461 return cmdutil.copy(ui, repo, pats, opts)
2461 return cmdutil.copy(ui, repo, pats, opts)
2462
2462
2463
2463
2464 @command(
2464 @command(
2465 b'debugcommands',
2465 b'debugcommands',
2466 [],
2466 [],
2467 _(b'[COMMAND]'),
2467 _(b'[COMMAND]'),
2468 helpcategory=command.CATEGORY_HELP,
2468 helpcategory=command.CATEGORY_HELP,
2469 norepo=True,
2469 norepo=True,
2470 )
2470 )
2471 def debugcommands(ui, cmd=b'', *args):
2471 def debugcommands(ui, cmd=b'', *args):
2472 """list all available commands and options"""
2472 """list all available commands and options"""
2473 for cmd, vals in sorted(pycompat.iteritems(table)):
2473 for cmd, vals in sorted(pycompat.iteritems(table)):
2474 cmd = cmd.split(b'|')[0]
2474 cmd = cmd.split(b'|')[0]
2475 opts = b', '.join([i[1] for i in vals[1]])
2475 opts = b', '.join([i[1] for i in vals[1]])
2476 ui.write(b'%s: %s\n' % (cmd, opts))
2476 ui.write(b'%s: %s\n' % (cmd, opts))
2477
2477
2478
2478
2479 @command(
2479 @command(
2480 b'debugcomplete',
2480 b'debugcomplete',
2481 [(b'o', b'options', None, _(b'show the command options'))],
2481 [(b'o', b'options', None, _(b'show the command options'))],
2482 _(b'[-o] CMD'),
2482 _(b'[-o] CMD'),
2483 helpcategory=command.CATEGORY_HELP,
2483 helpcategory=command.CATEGORY_HELP,
2484 norepo=True,
2484 norepo=True,
2485 )
2485 )
2486 def debugcomplete(ui, cmd=b'', **opts):
2486 def debugcomplete(ui, cmd=b'', **opts):
2487 """returns the completion list associated with the given command"""
2487 """returns the completion list associated with the given command"""
2488
2488
2489 if opts.get('options'):
2489 if opts.get('options'):
2490 options = []
2490 options = []
2491 otables = [globalopts]
2491 otables = [globalopts]
2492 if cmd:
2492 if cmd:
2493 aliases, entry = cmdutil.findcmd(cmd, table, False)
2493 aliases, entry = cmdutil.findcmd(cmd, table, False)
2494 otables.append(entry[1])
2494 otables.append(entry[1])
2495 for t in otables:
2495 for t in otables:
2496 for o in t:
2496 for o in t:
2497 if b"(DEPRECATED)" in o[3]:
2497 if b"(DEPRECATED)" in o[3]:
2498 continue
2498 continue
2499 if o[0]:
2499 if o[0]:
2500 options.append(b'-%s' % o[0])
2500 options.append(b'-%s' % o[0])
2501 options.append(b'--%s' % o[1])
2501 options.append(b'--%s' % o[1])
2502 ui.write(b"%s\n" % b"\n".join(options))
2502 ui.write(b"%s\n" % b"\n".join(options))
2503 return
2503 return
2504
2504
2505 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2505 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2506 if ui.verbose:
2506 if ui.verbose:
2507 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2507 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2508 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2508 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2509
2509
2510
2510
2511 @command(
2511 @command(
2512 b'diff',
2512 b'diff',
2513 [
2513 [
2514 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2514 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2515 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2515 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2516 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2516 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2517 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2517 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2518 ]
2518 ]
2519 + diffopts
2519 + diffopts
2520 + diffopts2
2520 + diffopts2
2521 + walkopts
2521 + walkopts
2522 + subrepoopts,
2522 + subrepoopts,
2523 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2523 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2524 helpcategory=command.CATEGORY_FILE_CONTENTS,
2524 helpcategory=command.CATEGORY_FILE_CONTENTS,
2525 helpbasic=True,
2525 helpbasic=True,
2526 inferrepo=True,
2526 inferrepo=True,
2527 intents={INTENT_READONLY},
2527 intents={INTENT_READONLY},
2528 )
2528 )
2529 def diff(ui, repo, *pats, **opts):
2529 def diff(ui, repo, *pats, **opts):
2530 """diff repository (or selected files)
2530 """diff repository (or selected files)
2531
2531
2532 Show differences between revisions for the specified files.
2532 Show differences between revisions for the specified files.
2533
2533
2534 Differences between files are shown using the unified diff format.
2534 Differences between files are shown using the unified diff format.
2535
2535
2536 .. note::
2536 .. note::
2537
2537
2538 :hg:`diff` may generate unexpected results for merges, as it will
2538 :hg:`diff` may generate unexpected results for merges, as it will
2539 default to comparing against the working directory's first
2539 default to comparing against the working directory's first
2540 parent changeset if no revisions are specified.
2540 parent changeset if no revisions are specified.
2541
2541
2542 By default, the working directory files are compared to its first parent. To
2542 By default, the working directory files are compared to its first parent. To
2543 see the differences from another revision, use --from. To see the difference
2543 see the differences from another revision, use --from. To see the difference
2544 to another revision, use --to. For example, :hg:`diff --from .^` will show
2544 to another revision, use --to. For example, :hg:`diff --from .^` will show
2545 the differences from the working copy's grandparent to the working copy,
2545 the differences from the working copy's grandparent to the working copy,
2546 :hg:`diff --to .` will show the diff from the working copy to its parent
2546 :hg:`diff --to .` will show the diff from the working copy to its parent
2547 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2547 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2548 show the diff between those two revisions.
2548 show the diff between those two revisions.
2549
2549
2550 Alternatively you can specify -c/--change with a revision to see the changes
2550 Alternatively you can specify -c/--change with a revision to see the changes
2551 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2551 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2552 equivalent to :hg:`diff --from 42^ --to 42`)
2552 equivalent to :hg:`diff --from 42^ --to 42`)
2553
2553
2554 Without the -a/--text option, diff will avoid generating diffs of
2554 Without the -a/--text option, diff will avoid generating diffs of
2555 files it detects as binary. With -a, diff will generate a diff
2555 files it detects as binary. With -a, diff will generate a diff
2556 anyway, probably with undesirable results.
2556 anyway, probably with undesirable results.
2557
2557
2558 Use the -g/--git option to generate diffs in the git extended diff
2558 Use the -g/--git option to generate diffs in the git extended diff
2559 format. For more information, read :hg:`help diffs`.
2559 format. For more information, read :hg:`help diffs`.
2560
2560
2561 .. container:: verbose
2561 .. container:: verbose
2562
2562
2563 Examples:
2563 Examples:
2564
2564
2565 - compare a file in the current working directory to its parent::
2565 - compare a file in the current working directory to its parent::
2566
2566
2567 hg diff foo.c
2567 hg diff foo.c
2568
2568
2569 - compare two historical versions of a directory, with rename info::
2569 - compare two historical versions of a directory, with rename info::
2570
2570
2571 hg diff --git --from 1.0 --to 1.2 lib/
2571 hg diff --git --from 1.0 --to 1.2 lib/
2572
2572
2573 - get change stats relative to the last change on some date::
2573 - get change stats relative to the last change on some date::
2574
2574
2575 hg diff --stat --from "date('may 2')"
2575 hg diff --stat --from "date('may 2')"
2576
2576
2577 - diff all newly-added files that contain a keyword::
2577 - diff all newly-added files that contain a keyword::
2578
2578
2579 hg diff "set:added() and grep(GNU)"
2579 hg diff "set:added() and grep(GNU)"
2580
2580
2581 - compare a revision and its parents::
2581 - compare a revision and its parents::
2582
2582
2583 hg diff -c 9353 # compare against first parent
2583 hg diff -c 9353 # compare against first parent
2584 hg diff --from 9353^ --to 9353 # same using revset syntax
2584 hg diff --from 9353^ --to 9353 # same using revset syntax
2585 hg diff --from 9353^2 --to 9353 # compare against the second parent
2585 hg diff --from 9353^2 --to 9353 # compare against the second parent
2586
2586
2587 Returns 0 on success.
2587 Returns 0 on success.
2588 """
2588 """
2589
2589
2590 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2590 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2591 opts = pycompat.byteskwargs(opts)
2591 opts = pycompat.byteskwargs(opts)
2592 revs = opts.get(b'rev')
2592 revs = opts.get(b'rev')
2593 change = opts.get(b'change')
2593 change = opts.get(b'change')
2594 from_rev = opts.get(b'from')
2594 from_rev = opts.get(b'from')
2595 to_rev = opts.get(b'to')
2595 to_rev = opts.get(b'to')
2596 stat = opts.get(b'stat')
2596 stat = opts.get(b'stat')
2597 reverse = opts.get(b'reverse')
2597 reverse = opts.get(b'reverse')
2598
2598
2599 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2599 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2600 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2600 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2601 if change:
2601 if change:
2602 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2602 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2603 ctx2 = logcmdutil.revsingle(repo, change, None)
2603 ctx2 = logcmdutil.revsingle(repo, change, None)
2604 ctx1 = logcmdutil.diff_parent(ctx2)
2604 ctx1 = logcmdutil.diff_parent(ctx2)
2605 elif from_rev or to_rev:
2605 elif from_rev or to_rev:
2606 repo = scmutil.unhidehashlikerevs(
2606 repo = scmutil.unhidehashlikerevs(
2607 repo, [from_rev] + [to_rev], b'nowarn'
2607 repo, [from_rev] + [to_rev], b'nowarn'
2608 )
2608 )
2609 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2609 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2610 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2610 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2611 else:
2611 else:
2612 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2612 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2613 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2613 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2614
2614
2615 if reverse:
2615 if reverse:
2616 ctxleft = ctx2
2616 ctxleft = ctx2
2617 ctxright = ctx1
2617 ctxright = ctx1
2618 else:
2618 else:
2619 ctxleft = ctx1
2619 ctxleft = ctx1
2620 ctxright = ctx2
2620 ctxright = ctx2
2621
2621
2622 diffopts = patch.diffallopts(ui, opts)
2622 diffopts = patch.diffallopts(ui, opts)
2623 m = scmutil.match(ctx2, pats, opts)
2623 m = scmutil.match(ctx2, pats, opts)
2624 m = repo.narrowmatch(m)
2624 m = repo.narrowmatch(m)
2625 ui.pager(b'diff')
2625 ui.pager(b'diff')
2626 logcmdutil.diffordiffstat(
2626 logcmdutil.diffordiffstat(
2627 ui,
2627 ui,
2628 repo,
2628 repo,
2629 diffopts,
2629 diffopts,
2630 ctxleft,
2630 ctxleft,
2631 ctxright,
2631 ctxright,
2632 m,
2632 m,
2633 stat=stat,
2633 stat=stat,
2634 listsubrepos=opts.get(b'subrepos'),
2634 listsubrepos=opts.get(b'subrepos'),
2635 root=opts.get(b'root'),
2635 root=opts.get(b'root'),
2636 )
2636 )
2637
2637
2638
2638
2639 @command(
2639 @command(
2640 b'export',
2640 b'export',
2641 [
2641 [
2642 (
2642 (
2643 b'B',
2643 b'B',
2644 b'bookmark',
2644 b'bookmark',
2645 b'',
2645 b'',
2646 _(b'export changes only reachable by given bookmark'),
2646 _(b'export changes only reachable by given bookmark'),
2647 _(b'BOOKMARK'),
2647 _(b'BOOKMARK'),
2648 ),
2648 ),
2649 (
2649 (
2650 b'o',
2650 b'o',
2651 b'output',
2651 b'output',
2652 b'',
2652 b'',
2653 _(b'print output to file with formatted name'),
2653 _(b'print output to file with formatted name'),
2654 _(b'FORMAT'),
2654 _(b'FORMAT'),
2655 ),
2655 ),
2656 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2656 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2657 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2657 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2658 ]
2658 ]
2659 + diffopts
2659 + diffopts
2660 + formatteropts,
2660 + formatteropts,
2661 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2661 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2662 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2662 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2663 helpbasic=True,
2663 helpbasic=True,
2664 intents={INTENT_READONLY},
2664 intents={INTENT_READONLY},
2665 )
2665 )
2666 def export(ui, repo, *changesets, **opts):
2666 def export(ui, repo, *changesets, **opts):
2667 """dump the header and diffs for one or more changesets
2667 """dump the header and diffs for one or more changesets
2668
2668
2669 Print the changeset header and diffs for one or more revisions.
2669 Print the changeset header and diffs for one or more revisions.
2670 If no revision is given, the parent of the working directory is used.
2670 If no revision is given, the parent of the working directory is used.
2671
2671
2672 The information shown in the changeset header is: author, date,
2672 The information shown in the changeset header is: author, date,
2673 branch name (if non-default), changeset hash, parent(s) and commit
2673 branch name (if non-default), changeset hash, parent(s) and commit
2674 comment.
2674 comment.
2675
2675
2676 .. note::
2676 .. note::
2677
2677
2678 :hg:`export` may generate unexpected diff output for merge
2678 :hg:`export` may generate unexpected diff output for merge
2679 changesets, as it will compare the merge changeset against its
2679 changesets, as it will compare the merge changeset against its
2680 first parent only.
2680 first parent only.
2681
2681
2682 Output may be to a file, in which case the name of the file is
2682 Output may be to a file, in which case the name of the file is
2683 given using a template string. See :hg:`help templates`. In addition
2683 given using a template string. See :hg:`help templates`. In addition
2684 to the common template keywords, the following formatting rules are
2684 to the common template keywords, the following formatting rules are
2685 supported:
2685 supported:
2686
2686
2687 :``%%``: literal "%" character
2687 :``%%``: literal "%" character
2688 :``%H``: changeset hash (40 hexadecimal digits)
2688 :``%H``: changeset hash (40 hexadecimal digits)
2689 :``%N``: number of patches being generated
2689 :``%N``: number of patches being generated
2690 :``%R``: changeset revision number
2690 :``%R``: changeset revision number
2691 :``%b``: basename of the exporting repository
2691 :``%b``: basename of the exporting repository
2692 :``%h``: short-form changeset hash (12 hexadecimal digits)
2692 :``%h``: short-form changeset hash (12 hexadecimal digits)
2693 :``%m``: first line of the commit message (only alphanumeric characters)
2693 :``%m``: first line of the commit message (only alphanumeric characters)
2694 :``%n``: zero-padded sequence number, starting at 1
2694 :``%n``: zero-padded sequence number, starting at 1
2695 :``%r``: zero-padded changeset revision number
2695 :``%r``: zero-padded changeset revision number
2696 :``\\``: literal "\\" character
2696 :``\\``: literal "\\" character
2697
2697
2698 Without the -a/--text option, export will avoid generating diffs
2698 Without the -a/--text option, export will avoid generating diffs
2699 of files it detects as binary. With -a, export will generate a
2699 of files it detects as binary. With -a, export will generate a
2700 diff anyway, probably with undesirable results.
2700 diff anyway, probably with undesirable results.
2701
2701
2702 With -B/--bookmark changesets reachable by the given bookmark are
2702 With -B/--bookmark changesets reachable by the given bookmark are
2703 selected.
2703 selected.
2704
2704
2705 Use the -g/--git option to generate diffs in the git extended diff
2705 Use the -g/--git option to generate diffs in the git extended diff
2706 format. See :hg:`help diffs` for more information.
2706 format. See :hg:`help diffs` for more information.
2707
2707
2708 With the --switch-parent option, the diff will be against the
2708 With the --switch-parent option, the diff will be against the
2709 second parent. It can be useful to review a merge.
2709 second parent. It can be useful to review a merge.
2710
2710
2711 .. container:: verbose
2711 .. container:: verbose
2712
2712
2713 Template:
2713 Template:
2714
2714
2715 The following keywords are supported in addition to the common template
2715 The following keywords are supported in addition to the common template
2716 keywords and functions. See also :hg:`help templates`.
2716 keywords and functions. See also :hg:`help templates`.
2717
2717
2718 :diff: String. Diff content.
2718 :diff: String. Diff content.
2719 :parents: List of strings. Parent nodes of the changeset.
2719 :parents: List of strings. Parent nodes of the changeset.
2720
2720
2721 Examples:
2721 Examples:
2722
2722
2723 - use export and import to transplant a bugfix to the current
2723 - use export and import to transplant a bugfix to the current
2724 branch::
2724 branch::
2725
2725
2726 hg export -r 9353 | hg import -
2726 hg export -r 9353 | hg import -
2727
2727
2728 - export all the changesets between two revisions to a file with
2728 - export all the changesets between two revisions to a file with
2729 rename information::
2729 rename information::
2730
2730
2731 hg export --git -r 123:150 > changes.txt
2731 hg export --git -r 123:150 > changes.txt
2732
2732
2733 - split outgoing changes into a series of patches with
2733 - split outgoing changes into a series of patches with
2734 descriptive names::
2734 descriptive names::
2735
2735
2736 hg export -r "outgoing()" -o "%n-%m.patch"
2736 hg export -r "outgoing()" -o "%n-%m.patch"
2737
2737
2738 Returns 0 on success.
2738 Returns 0 on success.
2739 """
2739 """
2740 opts = pycompat.byteskwargs(opts)
2740 opts = pycompat.byteskwargs(opts)
2741 bookmark = opts.get(b'bookmark')
2741 bookmark = opts.get(b'bookmark')
2742 changesets += tuple(opts.get(b'rev', []))
2742 changesets += tuple(opts.get(b'rev', []))
2743
2743
2744 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2744 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2745
2745
2746 if bookmark:
2746 if bookmark:
2747 if bookmark not in repo._bookmarks:
2747 if bookmark not in repo._bookmarks:
2748 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2748 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2749
2749
2750 revs = scmutil.bookmarkrevs(repo, bookmark)
2750 revs = scmutil.bookmarkrevs(repo, bookmark)
2751 else:
2751 else:
2752 if not changesets:
2752 if not changesets:
2753 changesets = [b'.']
2753 changesets = [b'.']
2754
2754
2755 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2755 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2756 revs = logcmdutil.revrange(repo, changesets)
2756 revs = logcmdutil.revrange(repo, changesets)
2757
2757
2758 if not revs:
2758 if not revs:
2759 raise error.InputError(_(b"export requires at least one changeset"))
2759 raise error.InputError(_(b"export requires at least one changeset"))
2760 if len(revs) > 1:
2760 if len(revs) > 1:
2761 ui.note(_(b'exporting patches:\n'))
2761 ui.note(_(b'exporting patches:\n'))
2762 else:
2762 else:
2763 ui.note(_(b'exporting patch:\n'))
2763 ui.note(_(b'exporting patch:\n'))
2764
2764
2765 fntemplate = opts.get(b'output')
2765 fntemplate = opts.get(b'output')
2766 if cmdutil.isstdiofilename(fntemplate):
2766 if cmdutil.isstdiofilename(fntemplate):
2767 fntemplate = b''
2767 fntemplate = b''
2768
2768
2769 if fntemplate:
2769 if fntemplate:
2770 fm = formatter.nullformatter(ui, b'export', opts)
2770 fm = formatter.nullformatter(ui, b'export', opts)
2771 else:
2771 else:
2772 ui.pager(b'export')
2772 ui.pager(b'export')
2773 fm = ui.formatter(b'export', opts)
2773 fm = ui.formatter(b'export', opts)
2774 with fm:
2774 with fm:
2775 cmdutil.export(
2775 cmdutil.export(
2776 repo,
2776 repo,
2777 revs,
2777 revs,
2778 fm,
2778 fm,
2779 fntemplate=fntemplate,
2779 fntemplate=fntemplate,
2780 switch_parent=opts.get(b'switch_parent'),
2780 switch_parent=opts.get(b'switch_parent'),
2781 opts=patch.diffallopts(ui, opts),
2781 opts=patch.diffallopts(ui, opts),
2782 )
2782 )
2783
2783
2784
2784
2785 @command(
2785 @command(
2786 b'files',
2786 b'files',
2787 [
2787 [
2788 (
2788 (
2789 b'r',
2789 b'r',
2790 b'rev',
2790 b'rev',
2791 b'',
2791 b'',
2792 _(b'search the repository as it is in REV'),
2792 _(b'search the repository as it is in REV'),
2793 _(b'REV'),
2793 _(b'REV'),
2794 ),
2794 ),
2795 (
2795 (
2796 b'0',
2796 b'0',
2797 b'print0',
2797 b'print0',
2798 None,
2798 None,
2799 _(b'end filenames with NUL, for use with xargs'),
2799 _(b'end filenames with NUL, for use with xargs'),
2800 ),
2800 ),
2801 ]
2801 ]
2802 + walkopts
2802 + walkopts
2803 + formatteropts
2803 + formatteropts
2804 + subrepoopts,
2804 + subrepoopts,
2805 _(b'[OPTION]... [FILE]...'),
2805 _(b'[OPTION]... [FILE]...'),
2806 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2806 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2807 intents={INTENT_READONLY},
2807 intents={INTENT_READONLY},
2808 )
2808 )
2809 def files(ui, repo, *pats, **opts):
2809 def files(ui, repo, *pats, **opts):
2810 """list tracked files
2810 """list tracked files
2811
2811
2812 Print files under Mercurial control in the working directory or
2812 Print files under Mercurial control in the working directory or
2813 specified revision for given files (excluding removed files).
2813 specified revision for given files (excluding removed files).
2814 Files can be specified as filenames or filesets.
2814 Files can be specified as filenames or filesets.
2815
2815
2816 If no files are given to match, this command prints the names
2816 If no files are given to match, this command prints the names
2817 of all files under Mercurial control.
2817 of all files under Mercurial control.
2818
2818
2819 .. container:: verbose
2819 .. container:: verbose
2820
2820
2821 Template:
2821 Template:
2822
2822
2823 The following keywords are supported in addition to the common template
2823 The following keywords are supported in addition to the common template
2824 keywords and functions. See also :hg:`help templates`.
2824 keywords and functions. See also :hg:`help templates`.
2825
2825
2826 :flags: String. Character denoting file's symlink and executable bits.
2826 :flags: String. Character denoting file's symlink and executable bits.
2827 :path: String. Repository-absolute path of the file.
2827 :path: String. Repository-absolute path of the file.
2828 :size: Integer. Size of the file in bytes.
2828 :size: Integer. Size of the file in bytes.
2829
2829
2830 Examples:
2830 Examples:
2831
2831
2832 - list all files under the current directory::
2832 - list all files under the current directory::
2833
2833
2834 hg files .
2834 hg files .
2835
2835
2836 - shows sizes and flags for current revision::
2836 - shows sizes and flags for current revision::
2837
2837
2838 hg files -vr .
2838 hg files -vr .
2839
2839
2840 - list all files named README::
2840 - list all files named README::
2841
2841
2842 hg files -I "**/README"
2842 hg files -I "**/README"
2843
2843
2844 - list all binary files::
2844 - list all binary files::
2845
2845
2846 hg files "set:binary()"
2846 hg files "set:binary()"
2847
2847
2848 - find files containing a regular expression::
2848 - find files containing a regular expression::
2849
2849
2850 hg files "set:grep('bob')"
2850 hg files "set:grep('bob')"
2851
2851
2852 - search tracked file contents with xargs and grep::
2852 - search tracked file contents with xargs and grep::
2853
2853
2854 hg files -0 | xargs -0 grep foo
2854 hg files -0 | xargs -0 grep foo
2855
2855
2856 See :hg:`help patterns` and :hg:`help filesets` for more information
2856 See :hg:`help patterns` and :hg:`help filesets` for more information
2857 on specifying file patterns.
2857 on specifying file patterns.
2858
2858
2859 Returns 0 if a match is found, 1 otherwise.
2859 Returns 0 if a match is found, 1 otherwise.
2860
2860
2861 """
2861 """
2862
2862
2863 opts = pycompat.byteskwargs(opts)
2863 opts = pycompat.byteskwargs(opts)
2864 rev = opts.get(b'rev')
2864 rev = opts.get(b'rev')
2865 if rev:
2865 if rev:
2866 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2866 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2867 ctx = logcmdutil.revsingle(repo, rev, None)
2867 ctx = logcmdutil.revsingle(repo, rev, None)
2868
2868
2869 end = b'\n'
2869 end = b'\n'
2870 if opts.get(b'print0'):
2870 if opts.get(b'print0'):
2871 end = b'\0'
2871 end = b'\0'
2872 fmt = b'%s' + end
2872 fmt = b'%s' + end
2873
2873
2874 m = scmutil.match(ctx, pats, opts)
2874 m = scmutil.match(ctx, pats, opts)
2875 ui.pager(b'files')
2875 ui.pager(b'files')
2876 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2876 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2877 with ui.formatter(b'files', opts) as fm:
2877 with ui.formatter(b'files', opts) as fm:
2878 return cmdutil.files(
2878 return cmdutil.files(
2879 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2879 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2880 )
2880 )
2881
2881
2882
2882
2883 @command(
2883 @command(
2884 b'forget',
2884 b'forget',
2885 [
2885 [
2886 (b'i', b'interactive', None, _(b'use interactive mode')),
2886 (b'i', b'interactive', None, _(b'use interactive mode')),
2887 ]
2887 ]
2888 + walkopts
2888 + walkopts
2889 + dryrunopts,
2889 + dryrunopts,
2890 _(b'[OPTION]... FILE...'),
2890 _(b'[OPTION]... FILE...'),
2891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2892 helpbasic=True,
2892 helpbasic=True,
2893 inferrepo=True,
2893 inferrepo=True,
2894 )
2894 )
2895 def forget(ui, repo, *pats, **opts):
2895 def forget(ui, repo, *pats, **opts):
2896 """forget the specified files on the next commit
2896 """forget the specified files on the next commit
2897
2897
2898 Mark the specified files so they will no longer be tracked
2898 Mark the specified files so they will no longer be tracked
2899 after the next commit.
2899 after the next commit.
2900
2900
2901 This only removes files from the current branch, not from the
2901 This only removes files from the current branch, not from the
2902 entire project history, and it does not delete them from the
2902 entire project history, and it does not delete them from the
2903 working directory.
2903 working directory.
2904
2904
2905 To delete the file from the working directory, see :hg:`remove`.
2905 To delete the file from the working directory, see :hg:`remove`.
2906
2906
2907 To undo a forget before the next commit, see :hg:`add`.
2907 To undo a forget before the next commit, see :hg:`add`.
2908
2908
2909 .. container:: verbose
2909 .. container:: verbose
2910
2910
2911 Examples:
2911 Examples:
2912
2912
2913 - forget newly-added binary files::
2913 - forget newly-added binary files::
2914
2914
2915 hg forget "set:added() and binary()"
2915 hg forget "set:added() and binary()"
2916
2916
2917 - forget files that would be excluded by .hgignore::
2917 - forget files that would be excluded by .hgignore::
2918
2918
2919 hg forget "set:hgignore()"
2919 hg forget "set:hgignore()"
2920
2920
2921 Returns 0 on success.
2921 Returns 0 on success.
2922 """
2922 """
2923
2923
2924 opts = pycompat.byteskwargs(opts)
2924 opts = pycompat.byteskwargs(opts)
2925 if not pats:
2925 if not pats:
2926 raise error.InputError(_(b'no files specified'))
2926 raise error.InputError(_(b'no files specified'))
2927
2927
2928 m = scmutil.match(repo[None], pats, opts)
2928 m = scmutil.match(repo[None], pats, opts)
2929 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2929 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2930 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2930 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2931 rejected = cmdutil.forget(
2931 rejected = cmdutil.forget(
2932 ui,
2932 ui,
2933 repo,
2933 repo,
2934 m,
2934 m,
2935 prefix=b"",
2935 prefix=b"",
2936 uipathfn=uipathfn,
2936 uipathfn=uipathfn,
2937 explicitonly=False,
2937 explicitonly=False,
2938 dryrun=dryrun,
2938 dryrun=dryrun,
2939 interactive=interactive,
2939 interactive=interactive,
2940 )[0]
2940 )[0]
2941 return rejected and 1 or 0
2941 return rejected and 1 or 0
2942
2942
2943
2943
2944 @command(
2944 @command(
2945 b'graft',
2945 b'graft',
2946 [
2946 [
2947 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2947 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2948 (
2948 (
2949 b'',
2949 b'',
2950 b'base',
2950 b'base',
2951 b'',
2951 b'',
2952 _(b'base revision when doing the graft merge (ADVANCED)'),
2952 _(b'base revision when doing the graft merge (ADVANCED)'),
2953 _(b'REV'),
2953 _(b'REV'),
2954 ),
2954 ),
2955 (b'c', b'continue', False, _(b'resume interrupted graft')),
2955 (b'c', b'continue', False, _(b'resume interrupted graft')),
2956 (b'', b'stop', False, _(b'stop interrupted graft')),
2956 (b'', b'stop', False, _(b'stop interrupted graft')),
2957 (b'', b'abort', False, _(b'abort interrupted graft')),
2957 (b'', b'abort', False, _(b'abort interrupted graft')),
2958 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2958 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2959 (b'', b'log', None, _(b'append graft info to log message')),
2959 (b'', b'log', None, _(b'append graft info to log message')),
2960 (
2960 (
2961 b'',
2961 b'',
2962 b'no-commit',
2962 b'no-commit',
2963 None,
2963 None,
2964 _(b"don't commit, just apply the changes in working directory"),
2964 _(b"don't commit, just apply the changes in working directory"),
2965 ),
2965 ),
2966 (b'f', b'force', False, _(b'force graft')),
2966 (b'f', b'force', False, _(b'force graft')),
2967 (
2967 (
2968 b'D',
2968 b'D',
2969 b'currentdate',
2969 b'currentdate',
2970 False,
2970 False,
2971 _(b'record the current date as commit date'),
2971 _(b'record the current date as commit date'),
2972 ),
2972 ),
2973 (
2973 (
2974 b'U',
2974 b'U',
2975 b'currentuser',
2975 b'currentuser',
2976 False,
2976 False,
2977 _(b'record the current user as committer'),
2977 _(b'record the current user as committer'),
2978 ),
2978 ),
2979 ]
2979 ]
2980 + commitopts2
2980 + commitopts2
2981 + mergetoolopts
2981 + mergetoolopts
2982 + dryrunopts,
2982 + dryrunopts,
2983 _(b'[OPTION]... [-r REV]... REV...'),
2983 _(b'[OPTION]... [-r REV]... REV...'),
2984 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2984 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2985 )
2985 )
2986 def graft(ui, repo, *revs, **opts):
2986 def graft(ui, repo, *revs, **opts):
2987 """copy changes from other branches onto the current branch
2987 """copy changes from other branches onto the current branch
2988
2988
2989 This command uses Mercurial's merge logic to copy individual
2989 This command uses Mercurial's merge logic to copy individual
2990 changes from other branches without merging branches in the
2990 changes from other branches without merging branches in the
2991 history graph. This is sometimes known as 'backporting' or
2991 history graph. This is sometimes known as 'backporting' or
2992 'cherry-picking'. By default, graft will copy user, date, and
2992 'cherry-picking'. By default, graft will copy user, date, and
2993 description from the source changesets.
2993 description from the source changesets.
2994
2994
2995 Changesets that are ancestors of the current revision, that have
2995 Changesets that are ancestors of the current revision, that have
2996 already been grafted, or that are merges will be skipped.
2996 already been grafted, or that are merges will be skipped.
2997
2997
2998 If --log is specified, log messages will have a comment appended
2998 If --log is specified, log messages will have a comment appended
2999 of the form::
2999 of the form::
3000
3000
3001 (grafted from CHANGESETHASH)
3001 (grafted from CHANGESETHASH)
3002
3002
3003 If --force is specified, revisions will be grafted even if they
3003 If --force is specified, revisions will be grafted even if they
3004 are already ancestors of, or have been grafted to, the destination.
3004 are already ancestors of, or have been grafted to, the destination.
3005 This is useful when the revisions have since been backed out.
3005 This is useful when the revisions have since been backed out.
3006
3006
3007 If a graft merge results in conflicts, the graft process is
3007 If a graft merge results in conflicts, the graft process is
3008 interrupted so that the current merge can be manually resolved.
3008 interrupted so that the current merge can be manually resolved.
3009 Once all conflicts are addressed, the graft process can be
3009 Once all conflicts are addressed, the graft process can be
3010 continued with the -c/--continue option.
3010 continued with the -c/--continue option.
3011
3011
3012 The -c/--continue option reapplies all the earlier options.
3012 The -c/--continue option reapplies all the earlier options.
3013
3013
3014 .. container:: verbose
3014 .. container:: verbose
3015
3015
3016 The --base option exposes more of how graft internally uses merge with a
3016 The --base option exposes more of how graft internally uses merge with a
3017 custom base revision. --base can be used to specify another ancestor than
3017 custom base revision. --base can be used to specify another ancestor than
3018 the first and only parent.
3018 the first and only parent.
3019
3019
3020 The command::
3020 The command::
3021
3021
3022 hg graft -r 345 --base 234
3022 hg graft -r 345 --base 234
3023
3023
3024 is thus pretty much the same as::
3024 is thus pretty much the same as::
3025
3025
3026 hg diff --from 234 --to 345 | hg import
3026 hg diff --from 234 --to 345 | hg import
3027
3027
3028 but using merge to resolve conflicts and track moved files.
3028 but using merge to resolve conflicts and track moved files.
3029
3029
3030 The result of a merge can thus be backported as a single commit by
3030 The result of a merge can thus be backported as a single commit by
3031 specifying one of the merge parents as base, and thus effectively
3031 specifying one of the merge parents as base, and thus effectively
3032 grafting the changes from the other side.
3032 grafting the changes from the other side.
3033
3033
3034 It is also possible to collapse multiple changesets and clean up history
3034 It is also possible to collapse multiple changesets and clean up history
3035 by specifying another ancestor as base, much like rebase --collapse
3035 by specifying another ancestor as base, much like rebase --collapse
3036 --keep.
3036 --keep.
3037
3037
3038 The commit message can be tweaked after the fact using commit --amend .
3038 The commit message can be tweaked after the fact using commit --amend .
3039
3039
3040 For using non-ancestors as the base to backout changes, see the backout
3040 For using non-ancestors as the base to backout changes, see the backout
3041 command and the hidden --parent option.
3041 command and the hidden --parent option.
3042
3042
3043 .. container:: verbose
3043 .. container:: verbose
3044
3044
3045 Examples:
3045 Examples:
3046
3046
3047 - copy a single change to the stable branch and edit its description::
3047 - copy a single change to the stable branch and edit its description::
3048
3048
3049 hg update stable
3049 hg update stable
3050 hg graft --edit 9393
3050 hg graft --edit 9393
3051
3051
3052 - graft a range of changesets with one exception, updating dates::
3052 - graft a range of changesets with one exception, updating dates::
3053
3053
3054 hg graft -D "2085::2093 and not 2091"
3054 hg graft -D "2085::2093 and not 2091"
3055
3055
3056 - continue a graft after resolving conflicts::
3056 - continue a graft after resolving conflicts::
3057
3057
3058 hg graft -c
3058 hg graft -c
3059
3059
3060 - show the source of a grafted changeset::
3060 - show the source of a grafted changeset::
3061
3061
3062 hg log --debug -r .
3062 hg log --debug -r .
3063
3063
3064 - show revisions sorted by date::
3064 - show revisions sorted by date::
3065
3065
3066 hg log -r "sort(all(), date)"
3066 hg log -r "sort(all(), date)"
3067
3067
3068 - backport the result of a merge as a single commit::
3068 - backport the result of a merge as a single commit::
3069
3069
3070 hg graft -r 123 --base 123^
3070 hg graft -r 123 --base 123^
3071
3071
3072 - land a feature branch as one changeset::
3072 - land a feature branch as one changeset::
3073
3073
3074 hg up -cr default
3074 hg up -cr default
3075 hg graft -r featureX --base "ancestor('featureX', 'default')"
3075 hg graft -r featureX --base "ancestor('featureX', 'default')"
3076
3076
3077 See :hg:`help revisions` for more about specifying revisions.
3077 See :hg:`help revisions` for more about specifying revisions.
3078
3078
3079 Returns 0 on successful completion, 1 if there are unresolved files.
3079 Returns 0 on successful completion, 1 if there are unresolved files.
3080 """
3080 """
3081 with repo.wlock():
3081 with repo.wlock():
3082 return _dograft(ui, repo, *revs, **opts)
3082 return _dograft(ui, repo, *revs, **opts)
3083
3083
3084
3084
3085 def _dograft(ui, repo, *revs, **opts):
3085 def _dograft(ui, repo, *revs, **opts):
3086 if revs and opts.get('rev'):
3086 if revs and opts.get('rev'):
3087 ui.warn(
3087 ui.warn(
3088 _(
3088 _(
3089 b'warning: inconsistent use of --rev might give unexpected '
3089 b'warning: inconsistent use of --rev might give unexpected '
3090 b'revision ordering!\n'
3090 b'revision ordering!\n'
3091 )
3091 )
3092 )
3092 )
3093
3093
3094 revs = list(revs)
3094 revs = list(revs)
3095 revs.extend(opts.get('rev'))
3095 revs.extend(opts.get('rev'))
3096 # a dict of data to be stored in state file
3096 # a dict of data to be stored in state file
3097 statedata = {}
3097 statedata = {}
3098 # list of new nodes created by ongoing graft
3098 # list of new nodes created by ongoing graft
3099 statedata[b'newnodes'] = []
3099 statedata[b'newnodes'] = []
3100
3100
3101 cmdutil.resolve_commit_options(ui, opts)
3101 cmdutil.resolve_commit_options(ui, opts)
3102
3102
3103 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3103 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3104
3104
3105 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3105 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3106
3106
3107 cont = False
3107 cont = False
3108 if opts.get('no_commit'):
3108 if opts.get('no_commit'):
3109 cmdutil.check_incompatible_arguments(
3109 cmdutil.check_incompatible_arguments(
3110 opts,
3110 opts,
3111 'no_commit',
3111 'no_commit',
3112 ['edit', 'currentuser', 'currentdate', 'log'],
3112 ['edit', 'currentuser', 'currentdate', 'log'],
3113 )
3113 )
3114
3114
3115 graftstate = statemod.cmdstate(repo, b'graftstate')
3115 graftstate = statemod.cmdstate(repo, b'graftstate')
3116
3116
3117 if opts.get('stop'):
3117 if opts.get('stop'):
3118 cmdutil.check_incompatible_arguments(
3118 cmdutil.check_incompatible_arguments(
3119 opts,
3119 opts,
3120 'stop',
3120 'stop',
3121 [
3121 [
3122 'edit',
3122 'edit',
3123 'log',
3123 'log',
3124 'user',
3124 'user',
3125 'date',
3125 'date',
3126 'currentdate',
3126 'currentdate',
3127 'currentuser',
3127 'currentuser',
3128 'rev',
3128 'rev',
3129 ],
3129 ],
3130 )
3130 )
3131 return _stopgraft(ui, repo, graftstate)
3131 return _stopgraft(ui, repo, graftstate)
3132 elif opts.get('abort'):
3132 elif opts.get('abort'):
3133 cmdutil.check_incompatible_arguments(
3133 cmdutil.check_incompatible_arguments(
3134 opts,
3134 opts,
3135 'abort',
3135 'abort',
3136 [
3136 [
3137 'edit',
3137 'edit',
3138 'log',
3138 'log',
3139 'user',
3139 'user',
3140 'date',
3140 'date',
3141 'currentdate',
3141 'currentdate',
3142 'currentuser',
3142 'currentuser',
3143 'rev',
3143 'rev',
3144 ],
3144 ],
3145 )
3145 )
3146 return cmdutil.abortgraft(ui, repo, graftstate)
3146 return cmdutil.abortgraft(ui, repo, graftstate)
3147 elif opts.get('continue'):
3147 elif opts.get('continue'):
3148 cont = True
3148 cont = True
3149 if revs:
3149 if revs:
3150 raise error.InputError(_(b"can't specify --continue and revisions"))
3150 raise error.InputError(_(b"can't specify --continue and revisions"))
3151 # read in unfinished revisions
3151 # read in unfinished revisions
3152 if graftstate.exists():
3152 if graftstate.exists():
3153 statedata = cmdutil.readgraftstate(repo, graftstate)
3153 statedata = cmdutil.readgraftstate(repo, graftstate)
3154 if statedata.get(b'date'):
3154 if statedata.get(b'date'):
3155 opts['date'] = statedata[b'date']
3155 opts['date'] = statedata[b'date']
3156 if statedata.get(b'user'):
3156 if statedata.get(b'user'):
3157 opts['user'] = statedata[b'user']
3157 opts['user'] = statedata[b'user']
3158 if statedata.get(b'log'):
3158 if statedata.get(b'log'):
3159 opts['log'] = True
3159 opts['log'] = True
3160 if statedata.get(b'no_commit'):
3160 if statedata.get(b'no_commit'):
3161 opts['no_commit'] = statedata.get(b'no_commit')
3161 opts['no_commit'] = statedata.get(b'no_commit')
3162 if statedata.get(b'base'):
3162 if statedata.get(b'base'):
3163 opts['base'] = statedata.get(b'base')
3163 opts['base'] = statedata.get(b'base')
3164 nodes = statedata[b'nodes']
3164 nodes = statedata[b'nodes']
3165 revs = [repo[node].rev() for node in nodes]
3165 revs = [repo[node].rev() for node in nodes]
3166 else:
3166 else:
3167 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3167 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3168 else:
3168 else:
3169 if not revs:
3169 if not revs:
3170 raise error.InputError(_(b'no revisions specified'))
3170 raise error.InputError(_(b'no revisions specified'))
3171 cmdutil.checkunfinished(repo)
3171 cmdutil.checkunfinished(repo)
3172 cmdutil.bailifchanged(repo)
3172 cmdutil.bailifchanged(repo)
3173 revs = logcmdutil.revrange(repo, revs)
3173 revs = logcmdutil.revrange(repo, revs)
3174
3174
3175 skipped = set()
3175 skipped = set()
3176 basectx = None
3176 basectx = None
3177 if opts.get('base'):
3177 if opts.get('base'):
3178 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3178 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3179 if basectx is None:
3179 if basectx is None:
3180 # check for merges
3180 # check for merges
3181 for rev in repo.revs(b'%ld and merge()', revs):
3181 for rev in repo.revs(b'%ld and merge()', revs):
3182 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3182 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3183 skipped.add(rev)
3183 skipped.add(rev)
3184 revs = [r for r in revs if r not in skipped]
3184 revs = [r for r in revs if r not in skipped]
3185 if not revs:
3185 if not revs:
3186 return -1
3186 return -1
3187 if basectx is not None and len(revs) != 1:
3187 if basectx is not None and len(revs) != 1:
3188 raise error.InputError(_(b'only one revision allowed with --base '))
3188 raise error.InputError(_(b'only one revision allowed with --base '))
3189
3189
3190 # Don't check in the --continue case, in effect retaining --force across
3190 # Don't check in the --continue case, in effect retaining --force across
3191 # --continues. That's because without --force, any revisions we decided to
3191 # --continues. That's because without --force, any revisions we decided to
3192 # skip would have been filtered out here, so they wouldn't have made their
3192 # skip would have been filtered out here, so they wouldn't have made their
3193 # way to the graftstate. With --force, any revisions we would have otherwise
3193 # way to the graftstate. With --force, any revisions we would have otherwise
3194 # skipped would not have been filtered out, and if they hadn't been applied
3194 # skipped would not have been filtered out, and if they hadn't been applied
3195 # already, they'd have been in the graftstate.
3195 # already, they'd have been in the graftstate.
3196 if not (cont or opts.get('force')) and basectx is None:
3196 if not (cont or opts.get('force')) and basectx is None:
3197 # check for ancestors of dest branch
3197 # check for ancestors of dest branch
3198 ancestors = repo.revs(b'%ld & (::.)', revs)
3198 ancestors = repo.revs(b'%ld & (::.)', revs)
3199 for rev in ancestors:
3199 for rev in ancestors:
3200 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3200 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3201
3201
3202 revs = [r for r in revs if r not in ancestors]
3202 revs = [r for r in revs if r not in ancestors]
3203
3203
3204 if not revs:
3204 if not revs:
3205 return -1
3205 return -1
3206
3206
3207 # analyze revs for earlier grafts
3207 # analyze revs for earlier grafts
3208 ids = {}
3208 ids = {}
3209 for ctx in repo.set(b"%ld", revs):
3209 for ctx in repo.set(b"%ld", revs):
3210 ids[ctx.hex()] = ctx.rev()
3210 ids[ctx.hex()] = ctx.rev()
3211 n = ctx.extra().get(b'source')
3211 n = ctx.extra().get(b'source')
3212 if n:
3212 if n:
3213 ids[n] = ctx.rev()
3213 ids[n] = ctx.rev()
3214
3214
3215 # check ancestors for earlier grafts
3215 # check ancestors for earlier grafts
3216 ui.debug(b'scanning for duplicate grafts\n')
3216 ui.debug(b'scanning for duplicate grafts\n')
3217
3217
3218 # The only changesets we can be sure doesn't contain grafts of any
3218 # The only changesets we can be sure doesn't contain grafts of any
3219 # revs, are the ones that are common ancestors of *all* revs:
3219 # revs, are the ones that are common ancestors of *all* revs:
3220 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3220 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3221 ctx = repo[rev]
3221 ctx = repo[rev]
3222 n = ctx.extra().get(b'source')
3222 n = ctx.extra().get(b'source')
3223 if n in ids:
3223 if n in ids:
3224 try:
3224 try:
3225 r = repo[n].rev()
3225 r = repo[n].rev()
3226 except error.RepoLookupError:
3226 except error.RepoLookupError:
3227 r = None
3227 r = None
3228 if r in revs:
3228 if r in revs:
3229 ui.warn(
3229 ui.warn(
3230 _(
3230 _(
3231 b'skipping revision %d:%s '
3231 b'skipping revision %d:%s '
3232 b'(already grafted to %d:%s)\n'
3232 b'(already grafted to %d:%s)\n'
3233 )
3233 )
3234 % (r, repo[r], rev, ctx)
3234 % (r, repo[r], rev, ctx)
3235 )
3235 )
3236 revs.remove(r)
3236 revs.remove(r)
3237 elif ids[n] in revs:
3237 elif ids[n] in revs:
3238 if r is None:
3238 if r is None:
3239 ui.warn(
3239 ui.warn(
3240 _(
3240 _(
3241 b'skipping already grafted revision %d:%s '
3241 b'skipping already grafted revision %d:%s '
3242 b'(%d:%s also has unknown origin %s)\n'
3242 b'(%d:%s also has unknown origin %s)\n'
3243 )
3243 )
3244 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3244 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3245 )
3245 )
3246 else:
3246 else:
3247 ui.warn(
3247 ui.warn(
3248 _(
3248 _(
3249 b'skipping already grafted revision %d:%s '
3249 b'skipping already grafted revision %d:%s '
3250 b'(%d:%s also has origin %d:%s)\n'
3250 b'(%d:%s also has origin %d:%s)\n'
3251 )
3251 )
3252 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3252 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3253 )
3253 )
3254 revs.remove(ids[n])
3254 revs.remove(ids[n])
3255 elif ctx.hex() in ids:
3255 elif ctx.hex() in ids:
3256 r = ids[ctx.hex()]
3256 r = ids[ctx.hex()]
3257 if r in revs:
3257 if r in revs:
3258 ui.warn(
3258 ui.warn(
3259 _(
3259 _(
3260 b'skipping already grafted revision %d:%s '
3260 b'skipping already grafted revision %d:%s '
3261 b'(was grafted from %d:%s)\n'
3261 b'(was grafted from %d:%s)\n'
3262 )
3262 )
3263 % (r, repo[r], rev, ctx)
3263 % (r, repo[r], rev, ctx)
3264 )
3264 )
3265 revs.remove(r)
3265 revs.remove(r)
3266 if not revs:
3266 if not revs:
3267 return -1
3267 return -1
3268
3268
3269 if opts.get('no_commit'):
3269 if opts.get('no_commit'):
3270 statedata[b'no_commit'] = True
3270 statedata[b'no_commit'] = True
3271 if opts.get('base'):
3271 if opts.get('base'):
3272 statedata[b'base'] = opts['base']
3272 statedata[b'base'] = opts['base']
3273 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3273 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3274 desc = b'%d:%s "%s"' % (
3274 desc = b'%d:%s "%s"' % (
3275 ctx.rev(),
3275 ctx.rev(),
3276 ctx,
3276 ctx,
3277 ctx.description().split(b'\n', 1)[0],
3277 ctx.description().split(b'\n', 1)[0],
3278 )
3278 )
3279 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3279 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3280 if names:
3280 if names:
3281 desc += b' (%s)' % b' '.join(names)
3281 desc += b' (%s)' % b' '.join(names)
3282 ui.status(_(b'grafting %s\n') % desc)
3282 ui.status(_(b'grafting %s\n') % desc)
3283 if opts.get('dry_run'):
3283 if opts.get('dry_run'):
3284 continue
3284 continue
3285
3285
3286 source = ctx.extra().get(b'source')
3286 source = ctx.extra().get(b'source')
3287 extra = {}
3287 extra = {}
3288 if source:
3288 if source:
3289 extra[b'source'] = source
3289 extra[b'source'] = source
3290 extra[b'intermediate-source'] = ctx.hex()
3290 extra[b'intermediate-source'] = ctx.hex()
3291 else:
3291 else:
3292 extra[b'source'] = ctx.hex()
3292 extra[b'source'] = ctx.hex()
3293 user = ctx.user()
3293 user = ctx.user()
3294 if opts.get('user'):
3294 if opts.get('user'):
3295 user = opts['user']
3295 user = opts['user']
3296 statedata[b'user'] = user
3296 statedata[b'user'] = user
3297 date = ctx.date()
3297 date = ctx.date()
3298 if opts.get('date'):
3298 if opts.get('date'):
3299 date = opts['date']
3299 date = opts['date']
3300 statedata[b'date'] = date
3300 statedata[b'date'] = date
3301 message = ctx.description()
3301 message = ctx.description()
3302 if opts.get('log'):
3302 if opts.get('log'):
3303 message += b'\n(grafted from %s)' % ctx.hex()
3303 message += b'\n(grafted from %s)' % ctx.hex()
3304 statedata[b'log'] = True
3304 statedata[b'log'] = True
3305
3305
3306 # we don't merge the first commit when continuing
3306 # we don't merge the first commit when continuing
3307 if not cont:
3307 if not cont:
3308 # perform the graft merge with p1(rev) as 'ancestor'
3308 # perform the graft merge with p1(rev) as 'ancestor'
3309 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3309 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3310 base = ctx.p1() if basectx is None else basectx
3310 base = ctx.p1() if basectx is None else basectx
3311 with ui.configoverride(overrides, b'graft'):
3311 with ui.configoverride(overrides, b'graft'):
3312 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3312 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3313 # report any conflicts
3313 # report any conflicts
3314 if stats.unresolvedcount > 0:
3314 if stats.unresolvedcount > 0:
3315 # write out state for --continue
3315 # write out state for --continue
3316 nodes = [repo[rev].hex() for rev in revs[pos:]]
3316 nodes = [repo[rev].hex() for rev in revs[pos:]]
3317 statedata[b'nodes'] = nodes
3317 statedata[b'nodes'] = nodes
3318 stateversion = 1
3318 stateversion = 1
3319 graftstate.save(stateversion, statedata)
3319 graftstate.save(stateversion, statedata)
3320 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3320 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3321 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3321 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3322 return 1
3322 return 1
3323 else:
3323 else:
3324 cont = False
3324 cont = False
3325
3325
3326 # commit if --no-commit is false
3326 # commit if --no-commit is false
3327 if not opts.get('no_commit'):
3327 if not opts.get('no_commit'):
3328 node = repo.commit(
3328 node = repo.commit(
3329 text=message, user=user, date=date, extra=extra, editor=editor
3329 text=message, user=user, date=date, extra=extra, editor=editor
3330 )
3330 )
3331 if node is None:
3331 if node is None:
3332 ui.warn(
3332 ui.warn(
3333 _(b'note: graft of %d:%s created no changes to commit\n')
3333 _(b'note: graft of %d:%s created no changes to commit\n')
3334 % (ctx.rev(), ctx)
3334 % (ctx.rev(), ctx)
3335 )
3335 )
3336 # checking that newnodes exist because old state files won't have it
3336 # checking that newnodes exist because old state files won't have it
3337 elif statedata.get(b'newnodes') is not None:
3337 elif statedata.get(b'newnodes') is not None:
3338 nn = statedata[b'newnodes']
3338 nn = statedata[b'newnodes']
3339 assert isinstance(nn, list) # list of bytes
3339 assert isinstance(nn, list) # list of bytes
3340 nn.append(node)
3340 nn.append(node)
3341
3341
3342 # remove state when we complete successfully
3342 # remove state when we complete successfully
3343 if not opts.get('dry_run'):
3343 if not opts.get('dry_run'):
3344 graftstate.delete()
3344 graftstate.delete()
3345
3345
3346 return 0
3346 return 0
3347
3347
3348
3348
3349 def _stopgraft(ui, repo, graftstate):
3349 def _stopgraft(ui, repo, graftstate):
3350 """stop the interrupted graft"""
3350 """stop the interrupted graft"""
3351 if not graftstate.exists():
3351 if not graftstate.exists():
3352 raise error.StateError(_(b"no interrupted graft found"))
3352 raise error.StateError(_(b"no interrupted graft found"))
3353 pctx = repo[b'.']
3353 pctx = repo[b'.']
3354 mergemod.clean_update(pctx)
3354 mergemod.clean_update(pctx)
3355 graftstate.delete()
3355 graftstate.delete()
3356 ui.status(_(b"stopped the interrupted graft\n"))
3356 ui.status(_(b"stopped the interrupted graft\n"))
3357 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3357 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3358 return 0
3358 return 0
3359
3359
3360
3360
3361 statemod.addunfinished(
3361 statemod.addunfinished(
3362 b'graft',
3362 b'graft',
3363 fname=b'graftstate',
3363 fname=b'graftstate',
3364 clearable=True,
3364 clearable=True,
3365 stopflag=True,
3365 stopflag=True,
3366 continueflag=True,
3366 continueflag=True,
3367 abortfunc=cmdutil.hgabortgraft,
3367 abortfunc=cmdutil.hgabortgraft,
3368 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3368 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3369 )
3369 )
3370
3370
3371
3371
3372 @command(
3372 @command(
3373 b'grep',
3373 b'grep',
3374 [
3374 [
3375 (b'0', b'print0', None, _(b'end fields with NUL')),
3375 (b'0', b'print0', None, _(b'end fields with NUL')),
3376 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3376 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3377 (
3377 (
3378 b'',
3378 b'',
3379 b'diff',
3379 b'diff',
3380 None,
3380 None,
3381 _(
3381 _(
3382 b'search revision differences for when the pattern was added '
3382 b'search revision differences for when the pattern was added '
3383 b'or removed'
3383 b'or removed'
3384 ),
3384 ),
3385 ),
3385 ),
3386 (b'a', b'text', None, _(b'treat all files as text')),
3386 (b'a', b'text', None, _(b'treat all files as text')),
3387 (
3387 (
3388 b'f',
3388 b'f',
3389 b'follow',
3389 b'follow',
3390 None,
3390 None,
3391 _(
3391 _(
3392 b'follow changeset history,'
3392 b'follow changeset history,'
3393 b' or file history across copies and renames'
3393 b' or file history across copies and renames'
3394 ),
3394 ),
3395 ),
3395 ),
3396 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3396 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3397 (
3397 (
3398 b'l',
3398 b'l',
3399 b'files-with-matches',
3399 b'files-with-matches',
3400 None,
3400 None,
3401 _(b'print only filenames and revisions that match'),
3401 _(b'print only filenames and revisions that match'),
3402 ),
3402 ),
3403 (b'n', b'line-number', None, _(b'print matching line numbers')),
3403 (b'n', b'line-number', None, _(b'print matching line numbers')),
3404 (
3404 (
3405 b'r',
3405 b'r',
3406 b'rev',
3406 b'rev',
3407 [],
3407 [],
3408 _(b'search files changed within revision range'),
3408 _(b'search files changed within revision range'),
3409 _(b'REV'),
3409 _(b'REV'),
3410 ),
3410 ),
3411 (
3411 (
3412 b'',
3412 b'',
3413 b'all-files',
3413 b'all-files',
3414 None,
3414 None,
3415 _(
3415 _(
3416 b'include all files in the changeset while grepping (DEPRECATED)'
3416 b'include all files in the changeset while grepping (DEPRECATED)'
3417 ),
3417 ),
3418 ),
3418 ),
3419 (b'u', b'user', None, _(b'list the author (long with -v)')),
3419 (b'u', b'user', None, _(b'list the author (long with -v)')),
3420 (b'd', b'date', None, _(b'list the date (short with -q)')),
3420 (b'd', b'date', None, _(b'list the date (short with -q)')),
3421 ]
3421 ]
3422 + formatteropts
3422 + formatteropts
3423 + walkopts,
3423 + walkopts,
3424 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3424 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3425 helpcategory=command.CATEGORY_FILE_CONTENTS,
3425 helpcategory=command.CATEGORY_FILE_CONTENTS,
3426 inferrepo=True,
3426 inferrepo=True,
3427 intents={INTENT_READONLY},
3427 intents={INTENT_READONLY},
3428 )
3428 )
3429 def grep(ui, repo, pattern, *pats, **opts):
3429 def grep(ui, repo, pattern, *pats, **opts):
3430 """search for a pattern in specified files
3430 """search for a pattern in specified files
3431
3431
3432 Search the working directory or revision history for a regular
3432 Search the working directory or revision history for a regular
3433 expression in the specified files for the entire repository.
3433 expression in the specified files for the entire repository.
3434
3434
3435 By default, grep searches the repository files in the working
3435 By default, grep searches the repository files in the working
3436 directory and prints the files where it finds a match. To specify
3436 directory and prints the files where it finds a match. To specify
3437 historical revisions instead of the working directory, use the
3437 historical revisions instead of the working directory, use the
3438 --rev flag.
3438 --rev flag.
3439
3439
3440 To search instead historical revision differences that contains a
3440 To search instead historical revision differences that contains a
3441 change in match status ("-" for a match that becomes a non-match,
3441 change in match status ("-" for a match that becomes a non-match,
3442 or "+" for a non-match that becomes a match), use the --diff flag.
3442 or "+" for a non-match that becomes a match), use the --diff flag.
3443
3443
3444 PATTERN can be any Python (roughly Perl-compatible) regular
3444 PATTERN can be any Python (roughly Perl-compatible) regular
3445 expression.
3445 expression.
3446
3446
3447 If no FILEs are specified and the --rev flag isn't supplied, all
3447 If no FILEs are specified and the --rev flag isn't supplied, all
3448 files in the working directory are searched. When using the --rev
3448 files in the working directory are searched. When using the --rev
3449 flag and specifying FILEs, use the --follow argument to also
3449 flag and specifying FILEs, use the --follow argument to also
3450 follow the specified FILEs across renames and copies.
3450 follow the specified FILEs across renames and copies.
3451
3451
3452 .. container:: verbose
3452 .. container:: verbose
3453
3453
3454 Template:
3454 Template:
3455
3455
3456 The following keywords are supported in addition to the common template
3456 The following keywords are supported in addition to the common template
3457 keywords and functions. See also :hg:`help templates`.
3457 keywords and functions. See also :hg:`help templates`.
3458
3458
3459 :change: String. Character denoting insertion ``+`` or removal ``-``.
3459 :change: String. Character denoting insertion ``+`` or removal ``-``.
3460 Available if ``--diff`` is specified.
3460 Available if ``--diff`` is specified.
3461 :lineno: Integer. Line number of the match.
3461 :lineno: Integer. Line number of the match.
3462 :path: String. Repository-absolute path of the file.
3462 :path: String. Repository-absolute path of the file.
3463 :texts: List of text chunks.
3463 :texts: List of text chunks.
3464
3464
3465 And each entry of ``{texts}`` provides the following sub-keywords.
3465 And each entry of ``{texts}`` provides the following sub-keywords.
3466
3466
3467 :matched: Boolean. True if the chunk matches the specified pattern.
3467 :matched: Boolean. True if the chunk matches the specified pattern.
3468 :text: String. Chunk content.
3468 :text: String. Chunk content.
3469
3469
3470 See :hg:`help templates.operators` for the list expansion syntax.
3470 See :hg:`help templates.operators` for the list expansion syntax.
3471
3471
3472 Returns 0 if a match is found, 1 otherwise.
3472 Returns 0 if a match is found, 1 otherwise.
3473
3473
3474 """
3474 """
3475 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3475 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3476 opts = pycompat.byteskwargs(opts)
3476 opts = pycompat.byteskwargs(opts)
3477 diff = opts.get(b'all') or opts.get(b'diff')
3477 diff = opts.get(b'all') or opts.get(b'diff')
3478 follow = opts.get(b'follow')
3478 follow = opts.get(b'follow')
3479 if opts.get(b'all_files') is None and not diff:
3479 if opts.get(b'all_files') is None and not diff:
3480 opts[b'all_files'] = True
3480 opts[b'all_files'] = True
3481 plaingrep = (
3481 plaingrep = (
3482 opts.get(b'all_files')
3482 opts.get(b'all_files')
3483 and not opts.get(b'rev')
3483 and not opts.get(b'rev')
3484 and not opts.get(b'follow')
3484 and not opts.get(b'follow')
3485 )
3485 )
3486 all_files = opts.get(b'all_files')
3486 all_files = opts.get(b'all_files')
3487 if plaingrep:
3487 if plaingrep:
3488 opts[b'rev'] = [b'wdir()']
3488 opts[b'rev'] = [b'wdir()']
3489
3489
3490 reflags = re.M
3490 reflags = re.M
3491 if opts.get(b'ignore_case'):
3491 if opts.get(b'ignore_case'):
3492 reflags |= re.I
3492 reflags |= re.I
3493 try:
3493 try:
3494 regexp = util.re.compile(pattern, reflags)
3494 regexp = util.re.compile(pattern, reflags)
3495 except re.error as inst:
3495 except re.error as inst:
3496 ui.warn(
3496 ui.warn(
3497 _(b"grep: invalid match pattern: %s\n")
3497 _(b"grep: invalid match pattern: %s\n")
3498 % stringutil.forcebytestr(inst)
3498 % stringutil.forcebytestr(inst)
3499 )
3499 )
3500 return 1
3500 return 1
3501 sep, eol = b':', b'\n'
3501 sep, eol = b':', b'\n'
3502 if opts.get(b'print0'):
3502 if opts.get(b'print0'):
3503 sep = eol = b'\0'
3503 sep = eol = b'\0'
3504
3504
3505 searcher = grepmod.grepsearcher(
3505 searcher = grepmod.grepsearcher(
3506 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3506 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3507 )
3507 )
3508
3508
3509 getfile = searcher._getfile
3509 getfile = searcher._getfile
3510
3510
3511 uipathfn = scmutil.getuipathfn(repo)
3511 uipathfn = scmutil.getuipathfn(repo)
3512
3512
3513 def display(fm, fn, ctx, pstates, states):
3513 def display(fm, fn, ctx, pstates, states):
3514 rev = scmutil.intrev(ctx)
3514 rev = scmutil.intrev(ctx)
3515 if fm.isplain():
3515 if fm.isplain():
3516 formatuser = ui.shortuser
3516 formatuser = ui.shortuser
3517 else:
3517 else:
3518 formatuser = pycompat.bytestr
3518 formatuser = pycompat.bytestr
3519 if ui.quiet:
3519 if ui.quiet:
3520 datefmt = b'%Y-%m-%d'
3520 datefmt = b'%Y-%m-%d'
3521 else:
3521 else:
3522 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3522 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3523 found = False
3523 found = False
3524
3524
3525 @util.cachefunc
3525 @util.cachefunc
3526 def binary():
3526 def binary():
3527 flog = getfile(fn)
3527 flog = getfile(fn)
3528 try:
3528 try:
3529 return stringutil.binary(flog.read(ctx.filenode(fn)))
3529 return stringutil.binary(flog.read(ctx.filenode(fn)))
3530 except error.WdirUnsupported:
3530 except error.WdirUnsupported:
3531 return ctx[fn].isbinary()
3531 return ctx[fn].isbinary()
3532
3532
3533 fieldnamemap = {b'linenumber': b'lineno'}
3533 fieldnamemap = {b'linenumber': b'lineno'}
3534 if diff:
3534 if diff:
3535 iter = grepmod.difflinestates(pstates, states)
3535 iter = grepmod.difflinestates(pstates, states)
3536 else:
3536 else:
3537 iter = [(b'', l) for l in states]
3537 iter = [(b'', l) for l in states]
3538 for change, l in iter:
3538 for change, l in iter:
3539 fm.startitem()
3539 fm.startitem()
3540 fm.context(ctx=ctx)
3540 fm.context(ctx=ctx)
3541 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3541 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3542 fm.plain(uipathfn(fn), label=b'grep.filename')
3542 fm.plain(uipathfn(fn), label=b'grep.filename')
3543
3543
3544 cols = [
3544 cols = [
3545 (b'rev', b'%d', rev, not plaingrep, b''),
3545 (b'rev', b'%d', rev, not plaingrep, b''),
3546 (
3546 (
3547 b'linenumber',
3547 b'linenumber',
3548 b'%d',
3548 b'%d',
3549 l.linenum,
3549 l.linenum,
3550 opts.get(b'line_number'),
3550 opts.get(b'line_number'),
3551 b'',
3551 b'',
3552 ),
3552 ),
3553 ]
3553 ]
3554 if diff:
3554 if diff:
3555 cols.append(
3555 cols.append(
3556 (
3556 (
3557 b'change',
3557 b'change',
3558 b'%s',
3558 b'%s',
3559 change,
3559 change,
3560 True,
3560 True,
3561 b'grep.inserted '
3561 b'grep.inserted '
3562 if change == b'+'
3562 if change == b'+'
3563 else b'grep.deleted ',
3563 else b'grep.deleted ',
3564 )
3564 )
3565 )
3565 )
3566 cols.extend(
3566 cols.extend(
3567 [
3567 [
3568 (
3568 (
3569 b'user',
3569 b'user',
3570 b'%s',
3570 b'%s',
3571 formatuser(ctx.user()),
3571 formatuser(ctx.user()),
3572 opts.get(b'user'),
3572 opts.get(b'user'),
3573 b'',
3573 b'',
3574 ),
3574 ),
3575 (
3575 (
3576 b'date',
3576 b'date',
3577 b'%s',
3577 b'%s',
3578 fm.formatdate(ctx.date(), datefmt),
3578 fm.formatdate(ctx.date(), datefmt),
3579 opts.get(b'date'),
3579 opts.get(b'date'),
3580 b'',
3580 b'',
3581 ),
3581 ),
3582 ]
3582 ]
3583 )
3583 )
3584 for name, fmt, data, cond, extra_label in cols:
3584 for name, fmt, data, cond, extra_label in cols:
3585 if cond:
3585 if cond:
3586 fm.plain(sep, label=b'grep.sep')
3586 fm.plain(sep, label=b'grep.sep')
3587 field = fieldnamemap.get(name, name)
3587 field = fieldnamemap.get(name, name)
3588 label = extra_label + (b'grep.%s' % name)
3588 label = extra_label + (b'grep.%s' % name)
3589 fm.condwrite(cond, field, fmt, data, label=label)
3589 fm.condwrite(cond, field, fmt, data, label=label)
3590 if not opts.get(b'files_with_matches'):
3590 if not opts.get(b'files_with_matches'):
3591 fm.plain(sep, label=b'grep.sep')
3591 fm.plain(sep, label=b'grep.sep')
3592 if not opts.get(b'text') and binary():
3592 if not opts.get(b'text') and binary():
3593 fm.plain(_(b" Binary file matches"))
3593 fm.plain(_(b" Binary file matches"))
3594 else:
3594 else:
3595 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3595 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3596 fm.plain(eol)
3596 fm.plain(eol)
3597 found = True
3597 found = True
3598 if opts.get(b'files_with_matches'):
3598 if opts.get(b'files_with_matches'):
3599 break
3599 break
3600 return found
3600 return found
3601
3601
3602 def displaymatches(fm, l):
3602 def displaymatches(fm, l):
3603 p = 0
3603 p = 0
3604 for s, e in l.findpos(regexp):
3604 for s, e in l.findpos(regexp):
3605 if p < s:
3605 if p < s:
3606 fm.startitem()
3606 fm.startitem()
3607 fm.write(b'text', b'%s', l.line[p:s])
3607 fm.write(b'text', b'%s', l.line[p:s])
3608 fm.data(matched=False)
3608 fm.data(matched=False)
3609 fm.startitem()
3609 fm.startitem()
3610 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3610 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3611 fm.data(matched=True)
3611 fm.data(matched=True)
3612 p = e
3612 p = e
3613 if p < len(l.line):
3613 if p < len(l.line):
3614 fm.startitem()
3614 fm.startitem()
3615 fm.write(b'text', b'%s', l.line[p:])
3615 fm.write(b'text', b'%s', l.line[p:])
3616 fm.data(matched=False)
3616 fm.data(matched=False)
3617 fm.end()
3617 fm.end()
3618
3618
3619 found = False
3619 found = False
3620
3620
3621 wopts = logcmdutil.walkopts(
3621 wopts = logcmdutil.walkopts(
3622 pats=pats,
3622 pats=pats,
3623 opts=opts,
3623 opts=opts,
3624 revspec=opts[b'rev'],
3624 revspec=opts[b'rev'],
3625 include_pats=opts[b'include'],
3625 include_pats=opts[b'include'],
3626 exclude_pats=opts[b'exclude'],
3626 exclude_pats=opts[b'exclude'],
3627 follow=follow,
3627 follow=follow,
3628 force_changelog_traversal=all_files,
3628 force_changelog_traversal=all_files,
3629 filter_revisions_by_pats=not all_files,
3629 filter_revisions_by_pats=not all_files,
3630 )
3630 )
3631 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3631 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3632
3632
3633 ui.pager(b'grep')
3633 ui.pager(b'grep')
3634 fm = ui.formatter(b'grep', opts)
3634 fm = ui.formatter(b'grep', opts)
3635 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3635 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3636 r = display(fm, fn, ctx, pstates, states)
3636 r = display(fm, fn, ctx, pstates, states)
3637 found = found or r
3637 found = found or r
3638 if r and not diff and not all_files:
3638 if r and not diff and not all_files:
3639 searcher.skipfile(fn, ctx.rev())
3639 searcher.skipfile(fn, ctx.rev())
3640 fm.end()
3640 fm.end()
3641
3641
3642 return not found
3642 return not found
3643
3643
3644
3644
3645 @command(
3645 @command(
3646 b'heads',
3646 b'heads',
3647 [
3647 [
3648 (
3648 (
3649 b'r',
3649 b'r',
3650 b'rev',
3650 b'rev',
3651 b'',
3651 b'',
3652 _(b'show only heads which are descendants of STARTREV'),
3652 _(b'show only heads which are descendants of STARTREV'),
3653 _(b'STARTREV'),
3653 _(b'STARTREV'),
3654 ),
3654 ),
3655 (b't', b'topo', False, _(b'show topological heads only')),
3655 (b't', b'topo', False, _(b'show topological heads only')),
3656 (
3656 (
3657 b'a',
3657 b'a',
3658 b'active',
3658 b'active',
3659 False,
3659 False,
3660 _(b'show active branchheads only (DEPRECATED)'),
3660 _(b'show active branchheads only (DEPRECATED)'),
3661 ),
3661 ),
3662 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3662 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3663 ]
3663 ]
3664 + templateopts,
3664 + templateopts,
3665 _(b'[-ct] [-r STARTREV] [REV]...'),
3665 _(b'[-ct] [-r STARTREV] [REV]...'),
3666 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3666 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3667 intents={INTENT_READONLY},
3667 intents={INTENT_READONLY},
3668 )
3668 )
3669 def heads(ui, repo, *branchrevs, **opts):
3669 def heads(ui, repo, *branchrevs, **opts):
3670 """show branch heads
3670 """show branch heads
3671
3671
3672 With no arguments, show all open branch heads in the repository.
3672 With no arguments, show all open branch heads in the repository.
3673 Branch heads are changesets that have no descendants on the
3673 Branch heads are changesets that have no descendants on the
3674 same branch. They are where development generally takes place and
3674 same branch. They are where development generally takes place and
3675 are the usual targets for update and merge operations.
3675 are the usual targets for update and merge operations.
3676
3676
3677 If one or more REVs are given, only open branch heads on the
3677 If one or more REVs are given, only open branch heads on the
3678 branches associated with the specified changesets are shown. This
3678 branches associated with the specified changesets are shown. This
3679 means that you can use :hg:`heads .` to see the heads on the
3679 means that you can use :hg:`heads .` to see the heads on the
3680 currently checked-out branch.
3680 currently checked-out branch.
3681
3681
3682 If -c/--closed is specified, also show branch heads marked closed
3682 If -c/--closed is specified, also show branch heads marked closed
3683 (see :hg:`commit --close-branch`).
3683 (see :hg:`commit --close-branch`).
3684
3684
3685 If STARTREV is specified, only those heads that are descendants of
3685 If STARTREV is specified, only those heads that are descendants of
3686 STARTREV will be displayed.
3686 STARTREV will be displayed.
3687
3687
3688 If -t/--topo is specified, named branch mechanics will be ignored and only
3688 If -t/--topo is specified, named branch mechanics will be ignored and only
3689 topological heads (changesets with no children) will be shown.
3689 topological heads (changesets with no children) will be shown.
3690
3690
3691 Returns 0 if matching heads are found, 1 if not.
3691 Returns 0 if matching heads are found, 1 if not.
3692 """
3692 """
3693
3693
3694 opts = pycompat.byteskwargs(opts)
3694 opts = pycompat.byteskwargs(opts)
3695 start = None
3695 start = None
3696 rev = opts.get(b'rev')
3696 rev = opts.get(b'rev')
3697 if rev:
3697 if rev:
3698 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3698 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3699 start = logcmdutil.revsingle(repo, rev, None).node()
3699 start = logcmdutil.revsingle(repo, rev, None).node()
3700
3700
3701 if opts.get(b'topo'):
3701 if opts.get(b'topo'):
3702 heads = [repo[h] for h in repo.heads(start)]
3702 heads = [repo[h] for h in repo.heads(start)]
3703 else:
3703 else:
3704 heads = []
3704 heads = []
3705 for branch in repo.branchmap():
3705 for branch in repo.branchmap():
3706 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3706 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3707 heads = [repo[h] for h in heads]
3707 heads = [repo[h] for h in heads]
3708
3708
3709 if branchrevs:
3709 if branchrevs:
3710 branches = {
3710 branches = {
3711 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3711 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3712 }
3712 }
3713 heads = [h for h in heads if h.branch() in branches]
3713 heads = [h for h in heads if h.branch() in branches]
3714
3714
3715 if opts.get(b'active') and branchrevs:
3715 if opts.get(b'active') and branchrevs:
3716 dagheads = repo.heads(start)
3716 dagheads = repo.heads(start)
3717 heads = [h for h in heads if h.node() in dagheads]
3717 heads = [h for h in heads if h.node() in dagheads]
3718
3718
3719 if branchrevs:
3719 if branchrevs:
3720 haveheads = {h.branch() for h in heads}
3720 haveheads = {h.branch() for h in heads}
3721 if branches - haveheads:
3721 if branches - haveheads:
3722 headless = b', '.join(b for b in branches - haveheads)
3722 headless = b', '.join(b for b in branches - haveheads)
3723 msg = _(b'no open branch heads found on branches %s')
3723 msg = _(b'no open branch heads found on branches %s')
3724 if opts.get(b'rev'):
3724 if opts.get(b'rev'):
3725 msg += _(b' (started at %s)') % opts[b'rev']
3725 msg += _(b' (started at %s)') % opts[b'rev']
3726 ui.warn((msg + b'\n') % headless)
3726 ui.warn((msg + b'\n') % headless)
3727
3727
3728 if not heads:
3728 if not heads:
3729 return 1
3729 return 1
3730
3730
3731 ui.pager(b'heads')
3731 ui.pager(b'heads')
3732 heads = sorted(heads, key=lambda x: -(x.rev()))
3732 heads = sorted(heads, key=lambda x: -(x.rev()))
3733 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3733 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3734 for ctx in heads:
3734 for ctx in heads:
3735 displayer.show(ctx)
3735 displayer.show(ctx)
3736 displayer.close()
3736 displayer.close()
3737
3737
3738
3738
3739 @command(
3739 @command(
3740 b'help',
3740 b'help',
3741 [
3741 [
3742 (b'e', b'extension', None, _(b'show only help for extensions')),
3742 (b'e', b'extension', None, _(b'show only help for extensions')),
3743 (b'c', b'command', None, _(b'show only help for commands')),
3743 (b'c', b'command', None, _(b'show only help for commands')),
3744 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3744 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3745 (
3745 (
3746 b's',
3746 b's',
3747 b'system',
3747 b'system',
3748 [],
3748 [],
3749 _(b'show help for specific platform(s)'),
3749 _(b'show help for specific platform(s)'),
3750 _(b'PLATFORM'),
3750 _(b'PLATFORM'),
3751 ),
3751 ),
3752 ],
3752 ],
3753 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3753 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3754 helpcategory=command.CATEGORY_HELP,
3754 helpcategory=command.CATEGORY_HELP,
3755 norepo=True,
3755 norepo=True,
3756 intents={INTENT_READONLY},
3756 intents={INTENT_READONLY},
3757 )
3757 )
3758 def help_(ui, name=None, **opts):
3758 def help_(ui, name=None, **opts):
3759 """show help for a given topic or a help overview
3759 """show help for a given topic or a help overview
3760
3760
3761 With no arguments, print a list of commands with short help messages.
3761 With no arguments, print a list of commands with short help messages.
3762
3762
3763 Given a topic, extension, or command name, print help for that
3763 Given a topic, extension, or command name, print help for that
3764 topic.
3764 topic.
3765
3765
3766 Returns 0 if successful.
3766 Returns 0 if successful.
3767 """
3767 """
3768
3768
3769 keep = opts.get('system') or []
3769 keep = opts.get('system') or []
3770 if len(keep) == 0:
3770 if len(keep) == 0:
3771 if pycompat.sysplatform.startswith(b'win'):
3771 if pycompat.sysplatform.startswith(b'win'):
3772 keep.append(b'windows')
3772 keep.append(b'windows')
3773 elif pycompat.sysplatform == b'OpenVMS':
3773 elif pycompat.sysplatform == b'OpenVMS':
3774 keep.append(b'vms')
3774 keep.append(b'vms')
3775 elif pycompat.sysplatform == b'plan9':
3775 elif pycompat.sysplatform == b'plan9':
3776 keep.append(b'plan9')
3776 keep.append(b'plan9')
3777 else:
3777 else:
3778 keep.append(b'unix')
3778 keep.append(b'unix')
3779 keep.append(pycompat.sysplatform.lower())
3779 keep.append(pycompat.sysplatform.lower())
3780 if ui.verbose:
3780 if ui.verbose:
3781 keep.append(b'verbose')
3781 keep.append(b'verbose')
3782
3782
3783 commands = sys.modules[__name__]
3783 commands = sys.modules[__name__]
3784 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3784 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3785 ui.pager(b'help')
3785 ui.pager(b'help')
3786 ui.write(formatted)
3786 ui.write(formatted)
3787
3787
3788
3788
3789 @command(
3789 @command(
3790 b'identify|id',
3790 b'identify|id',
3791 [
3791 [
3792 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3792 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3793 (b'n', b'num', None, _(b'show local revision number')),
3793 (b'n', b'num', None, _(b'show local revision number')),
3794 (b'i', b'id', None, _(b'show global revision id')),
3794 (b'i', b'id', None, _(b'show global revision id')),
3795 (b'b', b'branch', None, _(b'show branch')),
3795 (b'b', b'branch', None, _(b'show branch')),
3796 (b't', b'tags', None, _(b'show tags')),
3796 (b't', b'tags', None, _(b'show tags')),
3797 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3797 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3798 ]
3798 ]
3799 + remoteopts
3799 + remoteopts
3800 + formatteropts,
3800 + formatteropts,
3801 _(b'[-nibtB] [-r REV] [SOURCE]'),
3801 _(b'[-nibtB] [-r REV] [SOURCE]'),
3802 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3802 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3803 optionalrepo=True,
3803 optionalrepo=True,
3804 intents={INTENT_READONLY},
3804 intents={INTENT_READONLY},
3805 )
3805 )
3806 def identify(
3806 def identify(
3807 ui,
3807 ui,
3808 repo,
3808 repo,
3809 source=None,
3809 source=None,
3810 rev=None,
3810 rev=None,
3811 num=None,
3811 num=None,
3812 id=None,
3812 id=None,
3813 branch=None,
3813 branch=None,
3814 tags=None,
3814 tags=None,
3815 bookmarks=None,
3815 bookmarks=None,
3816 **opts
3816 **opts
3817 ):
3817 ):
3818 """identify the working directory or specified revision
3818 """identify the working directory or specified revision
3819
3819
3820 Print a summary identifying the repository state at REV using one or
3820 Print a summary identifying the repository state at REV using one or
3821 two parent hash identifiers, followed by a "+" if the working
3821 two parent hash identifiers, followed by a "+" if the working
3822 directory has uncommitted changes, the branch name (if not default),
3822 directory has uncommitted changes, the branch name (if not default),
3823 a list of tags, and a list of bookmarks.
3823 a list of tags, and a list of bookmarks.
3824
3824
3825 When REV is not given, print a summary of the current state of the
3825 When REV is not given, print a summary of the current state of the
3826 repository including the working directory. Specify -r. to get information
3826 repository including the working directory. Specify -r. to get information
3827 of the working directory parent without scanning uncommitted changes.
3827 of the working directory parent without scanning uncommitted changes.
3828
3828
3829 Specifying a path to a repository root or Mercurial bundle will
3829 Specifying a path to a repository root or Mercurial bundle will
3830 cause lookup to operate on that repository/bundle.
3830 cause lookup to operate on that repository/bundle.
3831
3831
3832 .. container:: verbose
3832 .. container:: verbose
3833
3833
3834 Template:
3834 Template:
3835
3835
3836 The following keywords are supported in addition to the common template
3836 The following keywords are supported in addition to the common template
3837 keywords and functions. See also :hg:`help templates`.
3837 keywords and functions. See also :hg:`help templates`.
3838
3838
3839 :dirty: String. Character ``+`` denoting if the working directory has
3839 :dirty: String. Character ``+`` denoting if the working directory has
3840 uncommitted changes.
3840 uncommitted changes.
3841 :id: String. One or two nodes, optionally followed by ``+``.
3841 :id: String. One or two nodes, optionally followed by ``+``.
3842 :parents: List of strings. Parent nodes of the changeset.
3842 :parents: List of strings. Parent nodes of the changeset.
3843
3843
3844 Examples:
3844 Examples:
3845
3845
3846 - generate a build identifier for the working directory::
3846 - generate a build identifier for the working directory::
3847
3847
3848 hg id --id > build-id.dat
3848 hg id --id > build-id.dat
3849
3849
3850 - find the revision corresponding to a tag::
3850 - find the revision corresponding to a tag::
3851
3851
3852 hg id -n -r 1.3
3852 hg id -n -r 1.3
3853
3853
3854 - check the most recent revision of a remote repository::
3854 - check the most recent revision of a remote repository::
3855
3855
3856 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3856 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3857
3857
3858 See :hg:`log` for generating more information about specific revisions,
3858 See :hg:`log` for generating more information about specific revisions,
3859 including full hash identifiers.
3859 including full hash identifiers.
3860
3860
3861 Returns 0 if successful.
3861 Returns 0 if successful.
3862 """
3862 """
3863
3863
3864 opts = pycompat.byteskwargs(opts)
3864 opts = pycompat.byteskwargs(opts)
3865 if not repo and not source:
3865 if not repo and not source:
3866 raise error.InputError(
3866 raise error.InputError(
3867 _(b"there is no Mercurial repository here (.hg not found)")
3867 _(b"there is no Mercurial repository here (.hg not found)")
3868 )
3868 )
3869
3869
3870 default = not (num or id or branch or tags or bookmarks)
3870 default = not (num or id or branch or tags or bookmarks)
3871 output = []
3871 output = []
3872 revs = []
3872 revs = []
3873
3873
3874 peer = None
3874 peer = None
3875 try:
3875 try:
3876 if source:
3876 if source:
3877 source, branches = urlutil.get_unique_pull_path(
3877 source, branches = urlutil.get_unique_pull_path(
3878 b'identify', repo, ui, source
3878 b'identify', repo, ui, source
3879 )
3879 )
3880 # only pass ui when no repo
3880 # only pass ui when no repo
3881 peer = hg.peer(repo or ui, opts, source)
3881 peer = hg.peer(repo or ui, opts, source)
3882 repo = peer.local()
3882 repo = peer.local()
3883 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3883 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3884
3884
3885 fm = ui.formatter(b'identify', opts)
3885 fm = ui.formatter(b'identify', opts)
3886 fm.startitem()
3886 fm.startitem()
3887
3887
3888 if not repo:
3888 if not repo:
3889 if num or branch or tags:
3889 if num or branch or tags:
3890 raise error.InputError(
3890 raise error.InputError(
3891 _(b"can't query remote revision number, branch, or tags")
3891 _(b"can't query remote revision number, branch, or tags")
3892 )
3892 )
3893 if not rev and revs:
3893 if not rev and revs:
3894 rev = revs[0]
3894 rev = revs[0]
3895 if not rev:
3895 if not rev:
3896 rev = b"tip"
3896 rev = b"tip"
3897
3897
3898 remoterev = peer.lookup(rev)
3898 remoterev = peer.lookup(rev)
3899 hexrev = fm.hexfunc(remoterev)
3899 hexrev = fm.hexfunc(remoterev)
3900 if default or id:
3900 if default or id:
3901 output = [hexrev]
3901 output = [hexrev]
3902 fm.data(id=hexrev)
3902 fm.data(id=hexrev)
3903
3903
3904 @util.cachefunc
3904 @util.cachefunc
3905 def getbms():
3905 def getbms():
3906 bms = []
3906 bms = []
3907
3907
3908 if b'bookmarks' in peer.listkeys(b'namespaces'):
3908 if b'bookmarks' in peer.listkeys(b'namespaces'):
3909 hexremoterev = hex(remoterev)
3909 hexremoterev = hex(remoterev)
3910 bms = [
3910 bms = [
3911 bm
3911 bm
3912 for bm, bmr in pycompat.iteritems(
3912 for bm, bmr in pycompat.iteritems(
3913 peer.listkeys(b'bookmarks')
3913 peer.listkeys(b'bookmarks')
3914 )
3914 )
3915 if bmr == hexremoterev
3915 if bmr == hexremoterev
3916 ]
3916 ]
3917
3917
3918 return sorted(bms)
3918 return sorted(bms)
3919
3919
3920 if fm.isplain():
3920 if fm.isplain():
3921 if bookmarks:
3921 if bookmarks:
3922 output.extend(getbms())
3922 output.extend(getbms())
3923 elif default and not ui.quiet:
3923 elif default and not ui.quiet:
3924 # multiple bookmarks for a single parent separated by '/'
3924 # multiple bookmarks for a single parent separated by '/'
3925 bm = b'/'.join(getbms())
3925 bm = b'/'.join(getbms())
3926 if bm:
3926 if bm:
3927 output.append(bm)
3927 output.append(bm)
3928 else:
3928 else:
3929 fm.data(node=hex(remoterev))
3929 fm.data(node=hex(remoterev))
3930 if bookmarks or b'bookmarks' in fm.datahint():
3930 if bookmarks or b'bookmarks' in fm.datahint():
3931 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3931 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3932 else:
3932 else:
3933 if rev:
3933 if rev:
3934 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3934 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3935 ctx = logcmdutil.revsingle(repo, rev, None)
3935 ctx = logcmdutil.revsingle(repo, rev, None)
3936
3936
3937 if ctx.rev() is None:
3937 if ctx.rev() is None:
3938 ctx = repo[None]
3938 ctx = repo[None]
3939 parents = ctx.parents()
3939 parents = ctx.parents()
3940 taglist = []
3940 taglist = []
3941 for p in parents:
3941 for p in parents:
3942 taglist.extend(p.tags())
3942 taglist.extend(p.tags())
3943
3943
3944 dirty = b""
3944 dirty = b""
3945 if ctx.dirty(missing=True, merge=False, branch=False):
3945 if ctx.dirty(missing=True, merge=False, branch=False):
3946 dirty = b'+'
3946 dirty = b'+'
3947 fm.data(dirty=dirty)
3947 fm.data(dirty=dirty)
3948
3948
3949 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3949 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3950 if default or id:
3950 if default or id:
3951 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3951 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3952 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3952 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3953
3953
3954 if num:
3954 if num:
3955 numoutput = [b"%d" % p.rev() for p in parents]
3955 numoutput = [b"%d" % p.rev() for p in parents]
3956 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3956 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3957
3957
3958 fm.data(
3958 fm.data(
3959 parents=fm.formatlist(
3959 parents=fm.formatlist(
3960 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3960 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3961 )
3961 )
3962 )
3962 )
3963 else:
3963 else:
3964 hexoutput = fm.hexfunc(ctx.node())
3964 hexoutput = fm.hexfunc(ctx.node())
3965 if default or id:
3965 if default or id:
3966 output = [hexoutput]
3966 output = [hexoutput]
3967 fm.data(id=hexoutput)
3967 fm.data(id=hexoutput)
3968
3968
3969 if num:
3969 if num:
3970 output.append(pycompat.bytestr(ctx.rev()))
3970 output.append(pycompat.bytestr(ctx.rev()))
3971 taglist = ctx.tags()
3971 taglist = ctx.tags()
3972
3972
3973 if default and not ui.quiet:
3973 if default and not ui.quiet:
3974 b = ctx.branch()
3974 b = ctx.branch()
3975 if b != b'default':
3975 if b != b'default':
3976 output.append(b"(%s)" % b)
3976 output.append(b"(%s)" % b)
3977
3977
3978 # multiple tags for a single parent separated by '/'
3978 # multiple tags for a single parent separated by '/'
3979 t = b'/'.join(taglist)
3979 t = b'/'.join(taglist)
3980 if t:
3980 if t:
3981 output.append(t)
3981 output.append(t)
3982
3982
3983 # multiple bookmarks for a single parent separated by '/'
3983 # multiple bookmarks for a single parent separated by '/'
3984 bm = b'/'.join(ctx.bookmarks())
3984 bm = b'/'.join(ctx.bookmarks())
3985 if bm:
3985 if bm:
3986 output.append(bm)
3986 output.append(bm)
3987 else:
3987 else:
3988 if branch:
3988 if branch:
3989 output.append(ctx.branch())
3989 output.append(ctx.branch())
3990
3990
3991 if tags:
3991 if tags:
3992 output.extend(taglist)
3992 output.extend(taglist)
3993
3993
3994 if bookmarks:
3994 if bookmarks:
3995 output.extend(ctx.bookmarks())
3995 output.extend(ctx.bookmarks())
3996
3996
3997 fm.data(node=ctx.hex())
3997 fm.data(node=ctx.hex())
3998 fm.data(branch=ctx.branch())
3998 fm.data(branch=ctx.branch())
3999 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3999 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4000 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4000 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4001 fm.context(ctx=ctx)
4001 fm.context(ctx=ctx)
4002
4002
4003 fm.plain(b"%s\n" % b' '.join(output))
4003 fm.plain(b"%s\n" % b' '.join(output))
4004 fm.end()
4004 fm.end()
4005 finally:
4005 finally:
4006 if peer:
4006 if peer:
4007 peer.close()
4007 peer.close()
4008
4008
4009
4009
4010 @command(
4010 @command(
4011 b'import|patch',
4011 b'import|patch',
4012 [
4012 [
4013 (
4013 (
4014 b'p',
4014 b'p',
4015 b'strip',
4015 b'strip',
4016 1,
4016 1,
4017 _(
4017 _(
4018 b'directory strip option for patch. This has the same '
4018 b'directory strip option for patch. This has the same '
4019 b'meaning as the corresponding patch option'
4019 b'meaning as the corresponding patch option'
4020 ),
4020 ),
4021 _(b'NUM'),
4021 _(b'NUM'),
4022 ),
4022 ),
4023 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4023 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4024 (b'', b'secret', None, _(b'use the secret phase for committing')),
4024 (b'', b'secret', None, _(b'use the secret phase for committing')),
4025 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4025 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4026 (
4026 (
4027 b'f',
4027 b'f',
4028 b'force',
4028 b'force',
4029 None,
4029 None,
4030 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4030 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4031 ),
4031 ),
4032 (
4032 (
4033 b'',
4033 b'',
4034 b'no-commit',
4034 b'no-commit',
4035 None,
4035 None,
4036 _(b"don't commit, just update the working directory"),
4036 _(b"don't commit, just update the working directory"),
4037 ),
4037 ),
4038 (
4038 (
4039 b'',
4039 b'',
4040 b'bypass',
4040 b'bypass',
4041 None,
4041 None,
4042 _(b"apply patch without touching the working directory"),
4042 _(b"apply patch without touching the working directory"),
4043 ),
4043 ),
4044 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4044 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4045 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4045 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4046 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4046 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4047 (
4047 (
4048 b'',
4048 b'',
4049 b'import-branch',
4049 b'import-branch',
4050 None,
4050 None,
4051 _(b'use any branch information in patch (implied by --exact)'),
4051 _(b'use any branch information in patch (implied by --exact)'),
4052 ),
4052 ),
4053 ]
4053 ]
4054 + commitopts
4054 + commitopts
4055 + commitopts2
4055 + commitopts2
4056 + similarityopts,
4056 + similarityopts,
4057 _(b'[OPTION]... PATCH...'),
4057 _(b'[OPTION]... PATCH...'),
4058 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4058 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4059 )
4059 )
4060 def import_(ui, repo, patch1=None, *patches, **opts):
4060 def import_(ui, repo, patch1=None, *patches, **opts):
4061 """import an ordered set of patches
4061 """import an ordered set of patches
4062
4062
4063 Import a list of patches and commit them individually (unless
4063 Import a list of patches and commit them individually (unless
4064 --no-commit is specified).
4064 --no-commit is specified).
4065
4065
4066 To read a patch from standard input (stdin), use "-" as the patch
4066 To read a patch from standard input (stdin), use "-" as the patch
4067 name. If a URL is specified, the patch will be downloaded from
4067 name. If a URL is specified, the patch will be downloaded from
4068 there.
4068 there.
4069
4069
4070 Import first applies changes to the working directory (unless
4070 Import first applies changes to the working directory (unless
4071 --bypass is specified), import will abort if there are outstanding
4071 --bypass is specified), import will abort if there are outstanding
4072 changes.
4072 changes.
4073
4073
4074 Use --bypass to apply and commit patches directly to the
4074 Use --bypass to apply and commit patches directly to the
4075 repository, without affecting the working directory. Without
4075 repository, without affecting the working directory. Without
4076 --exact, patches will be applied on top of the working directory
4076 --exact, patches will be applied on top of the working directory
4077 parent revision.
4077 parent revision.
4078
4078
4079 You can import a patch straight from a mail message. Even patches
4079 You can import a patch straight from a mail message. Even patches
4080 as attachments work (to use the body part, it must have type
4080 as attachments work (to use the body part, it must have type
4081 text/plain or text/x-patch). From and Subject headers of email
4081 text/plain or text/x-patch). From and Subject headers of email
4082 message are used as default committer and commit message. All
4082 message are used as default committer and commit message. All
4083 text/plain body parts before first diff are added to the commit
4083 text/plain body parts before first diff are added to the commit
4084 message.
4084 message.
4085
4085
4086 If the imported patch was generated by :hg:`export`, user and
4086 If the imported patch was generated by :hg:`export`, user and
4087 description from patch override values from message headers and
4087 description from patch override values from message headers and
4088 body. Values given on command line with -m/--message and -u/--user
4088 body. Values given on command line with -m/--message and -u/--user
4089 override these.
4089 override these.
4090
4090
4091 If --exact is specified, import will set the working directory to
4091 If --exact is specified, import will set the working directory to
4092 the parent of each patch before applying it, and will abort if the
4092 the parent of each patch before applying it, and will abort if the
4093 resulting changeset has a different ID than the one recorded in
4093 resulting changeset has a different ID than the one recorded in
4094 the patch. This will guard against various ways that portable
4094 the patch. This will guard against various ways that portable
4095 patch formats and mail systems might fail to transfer Mercurial
4095 patch formats and mail systems might fail to transfer Mercurial
4096 data or metadata. See :hg:`bundle` for lossless transmission.
4096 data or metadata. See :hg:`bundle` for lossless transmission.
4097
4097
4098 Use --partial to ensure a changeset will be created from the patch
4098 Use --partial to ensure a changeset will be created from the patch
4099 even if some hunks fail to apply. Hunks that fail to apply will be
4099 even if some hunks fail to apply. Hunks that fail to apply will be
4100 written to a <target-file>.rej file. Conflicts can then be resolved
4100 written to a <target-file>.rej file. Conflicts can then be resolved
4101 by hand before :hg:`commit --amend` is run to update the created
4101 by hand before :hg:`commit --amend` is run to update the created
4102 changeset. This flag exists to let people import patches that
4102 changeset. This flag exists to let people import patches that
4103 partially apply without losing the associated metadata (author,
4103 partially apply without losing the associated metadata (author,
4104 date, description, ...).
4104 date, description, ...).
4105
4105
4106 .. note::
4106 .. note::
4107
4107
4108 When no hunks apply cleanly, :hg:`import --partial` will create
4108 When no hunks apply cleanly, :hg:`import --partial` will create
4109 an empty changeset, importing only the patch metadata.
4109 an empty changeset, importing only the patch metadata.
4110
4110
4111 With -s/--similarity, hg will attempt to discover renames and
4111 With -s/--similarity, hg will attempt to discover renames and
4112 copies in the patch in the same way as :hg:`addremove`.
4112 copies in the patch in the same way as :hg:`addremove`.
4113
4113
4114 It is possible to use external patch programs to perform the patch
4114 It is possible to use external patch programs to perform the patch
4115 by setting the ``ui.patch`` configuration option. For the default
4115 by setting the ``ui.patch`` configuration option. For the default
4116 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4116 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4117 See :hg:`help config` for more information about configuration
4117 See :hg:`help config` for more information about configuration
4118 files and how to use these options.
4118 files and how to use these options.
4119
4119
4120 See :hg:`help dates` for a list of formats valid for -d/--date.
4120 See :hg:`help dates` for a list of formats valid for -d/--date.
4121
4121
4122 .. container:: verbose
4122 .. container:: verbose
4123
4123
4124 Examples:
4124 Examples:
4125
4125
4126 - import a traditional patch from a website and detect renames::
4126 - import a traditional patch from a website and detect renames::
4127
4127
4128 hg import -s 80 http://example.com/bugfix.patch
4128 hg import -s 80 http://example.com/bugfix.patch
4129
4129
4130 - import a changeset from an hgweb server::
4130 - import a changeset from an hgweb server::
4131
4131
4132 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4132 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4133
4133
4134 - import all the patches in an Unix-style mbox::
4134 - import all the patches in an Unix-style mbox::
4135
4135
4136 hg import incoming-patches.mbox
4136 hg import incoming-patches.mbox
4137
4137
4138 - import patches from stdin::
4138 - import patches from stdin::
4139
4139
4140 hg import -
4140 hg import -
4141
4141
4142 - attempt to exactly restore an exported changeset (not always
4142 - attempt to exactly restore an exported changeset (not always
4143 possible)::
4143 possible)::
4144
4144
4145 hg import --exact proposed-fix.patch
4145 hg import --exact proposed-fix.patch
4146
4146
4147 - use an external tool to apply a patch which is too fuzzy for
4147 - use an external tool to apply a patch which is too fuzzy for
4148 the default internal tool.
4148 the default internal tool.
4149
4149
4150 hg import --config ui.patch="patch --merge" fuzzy.patch
4150 hg import --config ui.patch="patch --merge" fuzzy.patch
4151
4151
4152 - change the default fuzzing from 2 to a less strict 7
4152 - change the default fuzzing from 2 to a less strict 7
4153
4153
4154 hg import --config ui.fuzz=7 fuzz.patch
4154 hg import --config ui.fuzz=7 fuzz.patch
4155
4155
4156 Returns 0 on success, 1 on partial success (see --partial).
4156 Returns 0 on success, 1 on partial success (see --partial).
4157 """
4157 """
4158
4158
4159 cmdutil.check_incompatible_arguments(
4159 cmdutil.check_incompatible_arguments(
4160 opts, 'no_commit', ['bypass', 'secret']
4160 opts, 'no_commit', ['bypass', 'secret']
4161 )
4161 )
4162 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4162 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4163 opts = pycompat.byteskwargs(opts)
4163 opts = pycompat.byteskwargs(opts)
4164 if not patch1:
4164 if not patch1:
4165 raise error.InputError(_(b'need at least one patch to import'))
4165 raise error.InputError(_(b'need at least one patch to import'))
4166
4166
4167 patches = (patch1,) + patches
4167 patches = (patch1,) + patches
4168
4168
4169 date = opts.get(b'date')
4169 date = opts.get(b'date')
4170 if date:
4170 if date:
4171 opts[b'date'] = dateutil.parsedate(date)
4171 opts[b'date'] = dateutil.parsedate(date)
4172
4172
4173 exact = opts.get(b'exact')
4173 exact = opts.get(b'exact')
4174 update = not opts.get(b'bypass')
4174 update = not opts.get(b'bypass')
4175 try:
4175 try:
4176 sim = float(opts.get(b'similarity') or 0)
4176 sim = float(opts.get(b'similarity') or 0)
4177 except ValueError:
4177 except ValueError:
4178 raise error.InputError(_(b'similarity must be a number'))
4178 raise error.InputError(_(b'similarity must be a number'))
4179 if sim < 0 or sim > 100:
4179 if sim < 0 or sim > 100:
4180 raise error.InputError(_(b'similarity must be between 0 and 100'))
4180 raise error.InputError(_(b'similarity must be between 0 and 100'))
4181 if sim and not update:
4181 if sim and not update:
4182 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4182 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4183
4183
4184 base = opts[b"base"]
4184 base = opts[b"base"]
4185 msgs = []
4185 msgs = []
4186 ret = 0
4186 ret = 0
4187
4187
4188 with repo.wlock():
4188 with repo.wlock():
4189 if update:
4189 if update:
4190 cmdutil.checkunfinished(repo)
4190 cmdutil.checkunfinished(repo)
4191 if exact or not opts.get(b'force'):
4191 if exact or not opts.get(b'force'):
4192 cmdutil.bailifchanged(repo)
4192 cmdutil.bailifchanged(repo)
4193
4193
4194 if not opts.get(b'no_commit'):
4194 if not opts.get(b'no_commit'):
4195 lock = repo.lock
4195 lock = repo.lock
4196 tr = lambda: repo.transaction(b'import')
4196 tr = lambda: repo.transaction(b'import')
4197 dsguard = util.nullcontextmanager
4197 dsguard = util.nullcontextmanager
4198 else:
4198 else:
4199 lock = util.nullcontextmanager
4199 lock = util.nullcontextmanager
4200 tr = util.nullcontextmanager
4200 tr = util.nullcontextmanager
4201 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4201 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4202 with lock(), tr(), dsguard():
4202 with lock(), tr(), dsguard():
4203 parents = repo[None].parents()
4203 parents = repo[None].parents()
4204 for patchurl in patches:
4204 for patchurl in patches:
4205 if patchurl == b'-':
4205 if patchurl == b'-':
4206 ui.status(_(b'applying patch from stdin\n'))
4206 ui.status(_(b'applying patch from stdin\n'))
4207 patchfile = ui.fin
4207 patchfile = ui.fin
4208 patchurl = b'stdin' # for error message
4208 patchurl = b'stdin' # for error message
4209 else:
4209 else:
4210 patchurl = os.path.join(base, patchurl)
4210 patchurl = os.path.join(base, patchurl)
4211 ui.status(_(b'applying %s\n') % patchurl)
4211 ui.status(_(b'applying %s\n') % patchurl)
4212 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4212 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4213
4213
4214 haspatch = False
4214 haspatch = False
4215 for hunk in patch.split(patchfile):
4215 for hunk in patch.split(patchfile):
4216 with patch.extract(ui, hunk) as patchdata:
4216 with patch.extract(ui, hunk) as patchdata:
4217 msg, node, rej = cmdutil.tryimportone(
4217 msg, node, rej = cmdutil.tryimportone(
4218 ui, repo, patchdata, parents, opts, msgs, hg.clean
4218 ui, repo, patchdata, parents, opts, msgs, hg.clean
4219 )
4219 )
4220 if msg:
4220 if msg:
4221 haspatch = True
4221 haspatch = True
4222 ui.note(msg + b'\n')
4222 ui.note(msg + b'\n')
4223 if update or exact:
4223 if update or exact:
4224 parents = repo[None].parents()
4224 parents = repo[None].parents()
4225 else:
4225 else:
4226 parents = [repo[node]]
4226 parents = [repo[node]]
4227 if rej:
4227 if rej:
4228 ui.write_err(_(b"patch applied partially\n"))
4228 ui.write_err(_(b"patch applied partially\n"))
4229 ui.write_err(
4229 ui.write_err(
4230 _(
4230 _(
4231 b"(fix the .rej files and run "
4231 b"(fix the .rej files and run "
4232 b"`hg commit --amend`)\n"
4232 b"`hg commit --amend`)\n"
4233 )
4233 )
4234 )
4234 )
4235 ret = 1
4235 ret = 1
4236 break
4236 break
4237
4237
4238 if not haspatch:
4238 if not haspatch:
4239 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4239 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4240
4240
4241 if msgs:
4241 if msgs:
4242 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4242 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4243 return ret
4243 return ret
4244
4244
4245
4245
4246 @command(
4246 @command(
4247 b'incoming|in',
4247 b'incoming|in',
4248 [
4248 [
4249 (
4249 (
4250 b'f',
4250 b'f',
4251 b'force',
4251 b'force',
4252 None,
4252 None,
4253 _(b'run even if remote repository is unrelated'),
4253 _(b'run even if remote repository is unrelated'),
4254 ),
4254 ),
4255 (b'n', b'newest-first', None, _(b'show newest record first')),
4255 (b'n', b'newest-first', None, _(b'show newest record first')),
4256 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4256 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4257 (
4257 (
4258 b'r',
4258 b'r',
4259 b'rev',
4259 b'rev',
4260 [],
4260 [],
4261 _(b'a remote changeset intended to be added'),
4261 _(b'a remote changeset intended to be added'),
4262 _(b'REV'),
4262 _(b'REV'),
4263 ),
4263 ),
4264 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4264 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4265 (
4265 (
4266 b'b',
4266 b'b',
4267 b'branch',
4267 b'branch',
4268 [],
4268 [],
4269 _(b'a specific branch you would like to pull'),
4269 _(b'a specific branch you would like to pull'),
4270 _(b'BRANCH'),
4270 _(b'BRANCH'),
4271 ),
4271 ),
4272 ]
4272 ]
4273 + logopts
4273 + logopts
4274 + remoteopts
4274 + remoteopts
4275 + subrepoopts,
4275 + subrepoopts,
4276 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4276 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4277 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4277 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4278 )
4278 )
4279 def incoming(ui, repo, source=b"default", **opts):
4279 def incoming(ui, repo, source=b"default", **opts):
4280 """show new changesets found in source
4280 """show new changesets found in source
4281
4281
4282 Show new changesets found in the specified path/URL or the default
4282 Show new changesets found in the specified path/URL or the default
4283 pull location. These are the changesets that would have been pulled
4283 pull location. These are the changesets that would have been pulled
4284 by :hg:`pull` at the time you issued this command.
4284 by :hg:`pull` at the time you issued this command.
4285
4285
4286 See pull for valid source format details.
4286 See pull for valid source format details.
4287
4287
4288 .. container:: verbose
4288 .. container:: verbose
4289
4289
4290 With -B/--bookmarks, the result of bookmark comparison between
4290 With -B/--bookmarks, the result of bookmark comparison between
4291 local and remote repositories is displayed. With -v/--verbose,
4291 local and remote repositories is displayed. With -v/--verbose,
4292 status is also displayed for each bookmark like below::
4292 status is also displayed for each bookmark like below::
4293
4293
4294 BM1 01234567890a added
4294 BM1 01234567890a added
4295 BM2 1234567890ab advanced
4295 BM2 1234567890ab advanced
4296 BM3 234567890abc diverged
4296 BM3 234567890abc diverged
4297 BM4 34567890abcd changed
4297 BM4 34567890abcd changed
4298
4298
4299 The action taken locally when pulling depends on the
4299 The action taken locally when pulling depends on the
4300 status of each bookmark:
4300 status of each bookmark:
4301
4301
4302 :``added``: pull will create it
4302 :``added``: pull will create it
4303 :``advanced``: pull will update it
4303 :``advanced``: pull will update it
4304 :``diverged``: pull will create a divergent bookmark
4304 :``diverged``: pull will create a divergent bookmark
4305 :``changed``: result depends on remote changesets
4305 :``changed``: result depends on remote changesets
4306
4306
4307 From the point of view of pulling behavior, bookmark
4307 From the point of view of pulling behavior, bookmark
4308 existing only in the remote repository are treated as ``added``,
4308 existing only in the remote repository are treated as ``added``,
4309 even if it is in fact locally deleted.
4309 even if it is in fact locally deleted.
4310
4310
4311 .. container:: verbose
4311 .. container:: verbose
4312
4312
4313 For remote repository, using --bundle avoids downloading the
4313 For remote repository, using --bundle avoids downloading the
4314 changesets twice if the incoming is followed by a pull.
4314 changesets twice if the incoming is followed by a pull.
4315
4315
4316 Examples:
4316 Examples:
4317
4317
4318 - show incoming changes with patches and full description::
4318 - show incoming changes with patches and full description::
4319
4319
4320 hg incoming -vp
4320 hg incoming -vp
4321
4321
4322 - show incoming changes excluding merges, store a bundle::
4322 - show incoming changes excluding merges, store a bundle::
4323
4323
4324 hg in -vpM --bundle incoming.hg
4324 hg in -vpM --bundle incoming.hg
4325 hg pull incoming.hg
4325 hg pull incoming.hg
4326
4326
4327 - briefly list changes inside a bundle::
4327 - briefly list changes inside a bundle::
4328
4328
4329 hg in changes.hg -T "{desc|firstline}\\n"
4329 hg in changes.hg -T "{desc|firstline}\\n"
4330
4330
4331 Returns 0 if there are incoming changes, 1 otherwise.
4331 Returns 0 if there are incoming changes, 1 otherwise.
4332 """
4332 """
4333 opts = pycompat.byteskwargs(opts)
4333 opts = pycompat.byteskwargs(opts)
4334 if opts.get(b'graph'):
4334 if opts.get(b'graph'):
4335 logcmdutil.checkunsupportedgraphflags([], opts)
4335 logcmdutil.checkunsupportedgraphflags([], opts)
4336
4336
4337 def display(other, chlist, displayer):
4337 def display(other, chlist, displayer):
4338 revdag = logcmdutil.graphrevs(other, chlist, opts)
4338 revdag = logcmdutil.graphrevs(other, chlist, opts)
4339 logcmdutil.displaygraph(
4339 logcmdutil.displaygraph(
4340 ui, repo, revdag, displayer, graphmod.asciiedges
4340 ui, repo, revdag, displayer, graphmod.asciiedges
4341 )
4341 )
4342
4342
4343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4344 return 0
4344 return 0
4345
4345
4346 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4346 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4347
4347
4348 if opts.get(b'bookmarks'):
4348 if opts.get(b'bookmarks'):
4349 srcs = urlutil.get_pull_paths(repo, ui, [source])
4349 srcs = urlutil.get_pull_paths(repo, ui, [source])
4350 for path in srcs:
4350 for path in srcs:
4351 source, branches = urlutil.parseurl(
4351 source, branches = urlutil.parseurl(
4352 path.rawloc, opts.get(b'branch')
4352 path.rawloc, opts.get(b'branch')
4353 )
4353 )
4354 other = hg.peer(repo, opts, source)
4354 other = hg.peer(repo, opts, source)
4355 try:
4355 try:
4356 if b'bookmarks' not in other.listkeys(b'namespaces'):
4356 if b'bookmarks' not in other.listkeys(b'namespaces'):
4357 ui.warn(_(b"remote doesn't support bookmarks\n"))
4357 ui.warn(_(b"remote doesn't support bookmarks\n"))
4358 return 0
4358 return 0
4359 ui.pager(b'incoming')
4359 ui.pager(b'incoming')
4360 ui.status(
4360 ui.status(
4361 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4361 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4362 )
4362 )
4363 return bookmarks.incoming(
4363 return bookmarks.incoming(
4364 ui, repo, other, mode=path.bookmarks_mode
4364 ui, repo, other, mode=path.bookmarks_mode
4365 )
4365 )
4366 finally:
4366 finally:
4367 other.close()
4367 other.close()
4368
4368
4369 return hg.incoming(ui, repo, source, opts)
4369 return hg.incoming(ui, repo, source, opts)
4370
4370
4371
4371
4372 @command(
4372 @command(
4373 b'init',
4373 b'init',
4374 remoteopts,
4374 remoteopts,
4375 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4375 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4376 helpcategory=command.CATEGORY_REPO_CREATION,
4376 helpcategory=command.CATEGORY_REPO_CREATION,
4377 helpbasic=True,
4377 helpbasic=True,
4378 norepo=True,
4378 norepo=True,
4379 )
4379 )
4380 def init(ui, dest=b".", **opts):
4380 def init(ui, dest=b".", **opts):
4381 """create a new repository in the given directory
4381 """create a new repository in the given directory
4382
4382
4383 Initialize a new repository in the given directory. If the given
4383 Initialize a new repository in the given directory. If the given
4384 directory does not exist, it will be created.
4384 directory does not exist, it will be created.
4385
4385
4386 If no directory is given, the current directory is used.
4386 If no directory is given, the current directory is used.
4387
4387
4388 It is possible to specify an ``ssh://`` URL as the destination.
4388 It is possible to specify an ``ssh://`` URL as the destination.
4389 See :hg:`help urls` for more information.
4389 See :hg:`help urls` for more information.
4390
4390
4391 Returns 0 on success.
4391 Returns 0 on success.
4392 """
4392 """
4393 opts = pycompat.byteskwargs(opts)
4393 opts = pycompat.byteskwargs(opts)
4394 path = urlutil.get_clone_path(ui, dest)[1]
4394 path = urlutil.get_clone_path(ui, dest)[1]
4395 peer = hg.peer(ui, opts, path, create=True)
4395 peer = hg.peer(ui, opts, path, create=True)
4396 peer.close()
4396 peer.close()
4397
4397
4398
4398
4399 @command(
4399 @command(
4400 b'locate',
4400 b'locate',
4401 [
4401 [
4402 (
4402 (
4403 b'r',
4403 b'r',
4404 b'rev',
4404 b'rev',
4405 b'',
4405 b'',
4406 _(b'search the repository as it is in REV'),
4406 _(b'search the repository as it is in REV'),
4407 _(b'REV'),
4407 _(b'REV'),
4408 ),
4408 ),
4409 (
4409 (
4410 b'0',
4410 b'0',
4411 b'print0',
4411 b'print0',
4412 None,
4412 None,
4413 _(b'end filenames with NUL, for use with xargs'),
4413 _(b'end filenames with NUL, for use with xargs'),
4414 ),
4414 ),
4415 (
4415 (
4416 b'f',
4416 b'f',
4417 b'fullpath',
4417 b'fullpath',
4418 None,
4418 None,
4419 _(b'print complete paths from the filesystem root'),
4419 _(b'print complete paths from the filesystem root'),
4420 ),
4420 ),
4421 ]
4421 ]
4422 + walkopts,
4422 + walkopts,
4423 _(b'[OPTION]... [PATTERN]...'),
4423 _(b'[OPTION]... [PATTERN]...'),
4424 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4424 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4425 )
4425 )
4426 def locate(ui, repo, *pats, **opts):
4426 def locate(ui, repo, *pats, **opts):
4427 """locate files matching specific patterns (DEPRECATED)
4427 """locate files matching specific patterns (DEPRECATED)
4428
4428
4429 Print files under Mercurial control in the working directory whose
4429 Print files under Mercurial control in the working directory whose
4430 names match the given patterns.
4430 names match the given patterns.
4431
4431
4432 By default, this command searches all directories in the working
4432 By default, this command searches all directories in the working
4433 directory. To search just the current directory and its
4433 directory. To search just the current directory and its
4434 subdirectories, use "--include .".
4434 subdirectories, use "--include .".
4435
4435
4436 If no patterns are given to match, this command prints the names
4436 If no patterns are given to match, this command prints the names
4437 of all files under Mercurial control in the working directory.
4437 of all files under Mercurial control in the working directory.
4438
4438
4439 If you want to feed the output of this command into the "xargs"
4439 If you want to feed the output of this command into the "xargs"
4440 command, use the -0 option to both this command and "xargs". This
4440 command, use the -0 option to both this command and "xargs". This
4441 will avoid the problem of "xargs" treating single filenames that
4441 will avoid the problem of "xargs" treating single filenames that
4442 contain whitespace as multiple filenames.
4442 contain whitespace as multiple filenames.
4443
4443
4444 See :hg:`help files` for a more versatile command.
4444 See :hg:`help files` for a more versatile command.
4445
4445
4446 Returns 0 if a match is found, 1 otherwise.
4446 Returns 0 if a match is found, 1 otherwise.
4447 """
4447 """
4448 opts = pycompat.byteskwargs(opts)
4448 opts = pycompat.byteskwargs(opts)
4449 if opts.get(b'print0'):
4449 if opts.get(b'print0'):
4450 end = b'\0'
4450 end = b'\0'
4451 else:
4451 else:
4452 end = b'\n'
4452 end = b'\n'
4453 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4453 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4454
4454
4455 ret = 1
4455 ret = 1
4456 m = scmutil.match(
4456 m = scmutil.match(
4457 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4457 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4458 )
4458 )
4459
4459
4460 ui.pager(b'locate')
4460 ui.pager(b'locate')
4461 if ctx.rev() is None:
4461 if ctx.rev() is None:
4462 # When run on the working copy, "locate" includes removed files, so
4462 # When run on the working copy, "locate" includes removed files, so
4463 # we get the list of files from the dirstate.
4463 # we get the list of files from the dirstate.
4464 filesgen = sorted(repo.dirstate.matches(m))
4464 filesgen = sorted(repo.dirstate.matches(m))
4465 else:
4465 else:
4466 filesgen = ctx.matches(m)
4466 filesgen = ctx.matches(m)
4467 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4467 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4468 for abs in filesgen:
4468 for abs in filesgen:
4469 if opts.get(b'fullpath'):
4469 if opts.get(b'fullpath'):
4470 ui.write(repo.wjoin(abs), end)
4470 ui.write(repo.wjoin(abs), end)
4471 else:
4471 else:
4472 ui.write(uipathfn(abs), end)
4472 ui.write(uipathfn(abs), end)
4473 ret = 0
4473 ret = 0
4474
4474
4475 return ret
4475 return ret
4476
4476
4477
4477
4478 @command(
4478 @command(
4479 b'log|history',
4479 b'log|history',
4480 [
4480 [
4481 (
4481 (
4482 b'f',
4482 b'f',
4483 b'follow',
4483 b'follow',
4484 None,
4484 None,
4485 _(
4485 _(
4486 b'follow changeset history, or file history across copies and renames'
4486 b'follow changeset history, or file history across copies and renames'
4487 ),
4487 ),
4488 ),
4488 ),
4489 (
4489 (
4490 b'',
4490 b'',
4491 b'follow-first',
4491 b'follow-first',
4492 None,
4492 None,
4493 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4493 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4494 ),
4494 ),
4495 (
4495 (
4496 b'd',
4496 b'd',
4497 b'date',
4497 b'date',
4498 b'',
4498 b'',
4499 _(b'show revisions matching date spec'),
4499 _(b'show revisions matching date spec'),
4500 _(b'DATE'),
4500 _(b'DATE'),
4501 ),
4501 ),
4502 (b'C', b'copies', None, _(b'show copied files')),
4502 (b'C', b'copies', None, _(b'show copied files')),
4503 (
4503 (
4504 b'k',
4504 b'k',
4505 b'keyword',
4505 b'keyword',
4506 [],
4506 [],
4507 _(b'do case-insensitive search for a given text'),
4507 _(b'do case-insensitive search for a given text'),
4508 _(b'TEXT'),
4508 _(b'TEXT'),
4509 ),
4509 ),
4510 (
4510 (
4511 b'r',
4511 b'r',
4512 b'rev',
4512 b'rev',
4513 [],
4513 [],
4514 _(b'revisions to select or follow from'),
4514 _(b'revisions to select or follow from'),
4515 _(b'REV'),
4515 _(b'REV'),
4516 ),
4516 ),
4517 (
4517 (
4518 b'L',
4518 b'L',
4519 b'line-range',
4519 b'line-range',
4520 [],
4520 [],
4521 _(b'follow line range of specified file (EXPERIMENTAL)'),
4521 _(b'follow line range of specified file (EXPERIMENTAL)'),
4522 _(b'FILE,RANGE'),
4522 _(b'FILE,RANGE'),
4523 ),
4523 ),
4524 (
4524 (
4525 b'',
4525 b'',
4526 b'removed',
4526 b'removed',
4527 None,
4527 None,
4528 _(b'include revisions where files were removed'),
4528 _(b'include revisions where files were removed'),
4529 ),
4529 ),
4530 (
4530 (
4531 b'm',
4531 b'm',
4532 b'only-merges',
4532 b'only-merges',
4533 None,
4533 None,
4534 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4534 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4535 ),
4535 ),
4536 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4536 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4537 (
4537 (
4538 b'',
4538 b'',
4539 b'only-branch',
4539 b'only-branch',
4540 [],
4540 [],
4541 _(
4541 _(
4542 b'show only changesets within the given named branch (DEPRECATED)'
4542 b'show only changesets within the given named branch (DEPRECATED)'
4543 ),
4543 ),
4544 _(b'BRANCH'),
4544 _(b'BRANCH'),
4545 ),
4545 ),
4546 (
4546 (
4547 b'b',
4547 b'b',
4548 b'branch',
4548 b'branch',
4549 [],
4549 [],
4550 _(b'show changesets within the given named branch'),
4550 _(b'show changesets within the given named branch'),
4551 _(b'BRANCH'),
4551 _(b'BRANCH'),
4552 ),
4552 ),
4553 (
4553 (
4554 b'B',
4554 b'B',
4555 b'bookmark',
4555 b'bookmark',
4556 [],
4556 [],
4557 _(b"show changesets within the given bookmark"),
4557 _(b"show changesets within the given bookmark"),
4558 _(b'BOOKMARK'),
4558 _(b'BOOKMARK'),
4559 ),
4559 ),
4560 (
4560 (
4561 b'P',
4561 b'P',
4562 b'prune',
4562 b'prune',
4563 [],
4563 [],
4564 _(b'do not display revision or any of its ancestors'),
4564 _(b'do not display revision or any of its ancestors'),
4565 _(b'REV'),
4565 _(b'REV'),
4566 ),
4566 ),
4567 ]
4567 ]
4568 + logopts
4568 + logopts
4569 + walkopts,
4569 + walkopts,
4570 _(b'[OPTION]... [FILE]'),
4570 _(b'[OPTION]... [FILE]'),
4571 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4571 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4572 helpbasic=True,
4572 helpbasic=True,
4573 inferrepo=True,
4573 inferrepo=True,
4574 intents={INTENT_READONLY},
4574 intents={INTENT_READONLY},
4575 )
4575 )
4576 def log(ui, repo, *pats, **opts):
4576 def log(ui, repo, *pats, **opts):
4577 """show revision history of entire repository or files
4577 """show revision history of entire repository or files
4578
4578
4579 Print the revision history of the specified files or the entire
4579 Print the revision history of the specified files or the entire
4580 project.
4580 project.
4581
4581
4582 If no revision range is specified, the default is ``tip:0`` unless
4582 If no revision range is specified, the default is ``tip:0`` unless
4583 --follow is set.
4583 --follow is set.
4584
4584
4585 File history is shown without following rename or copy history of
4585 File history is shown without following rename or copy history of
4586 files. Use -f/--follow with a filename to follow history across
4586 files. Use -f/--follow with a filename to follow history across
4587 renames and copies. --follow without a filename will only show
4587 renames and copies. --follow without a filename will only show
4588 ancestors of the starting revisions. The starting revisions can be
4588 ancestors of the starting revisions. The starting revisions can be
4589 specified by -r/--rev, which default to the working directory parent.
4589 specified by -r/--rev, which default to the working directory parent.
4590
4590
4591 By default this command prints revision number and changeset id,
4591 By default this command prints revision number and changeset id,
4592 tags, non-trivial parents, user, date and time, and a summary for
4592 tags, non-trivial parents, user, date and time, and a summary for
4593 each commit. When the -v/--verbose switch is used, the list of
4593 each commit. When the -v/--verbose switch is used, the list of
4594 changed files and full commit message are shown.
4594 changed files and full commit message are shown.
4595
4595
4596 With --graph the revisions are shown as an ASCII art DAG with the most
4596 With --graph the revisions are shown as an ASCII art DAG with the most
4597 recent changeset at the top.
4597 recent changeset at the top.
4598 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4598 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4599 involved in an unresolved merge conflict, '_' closes a branch,
4599 involved in an unresolved merge conflict, '_' closes a branch,
4600 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4600 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4601 changeset from the lines below is a parent of the 'o' merge on the same
4601 changeset from the lines below is a parent of the 'o' merge on the same
4602 line.
4602 line.
4603 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4603 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4604 of a '|' indicates one or more revisions in a path are omitted.
4604 of a '|' indicates one or more revisions in a path are omitted.
4605
4605
4606 .. container:: verbose
4606 .. container:: verbose
4607
4607
4608 Use -L/--line-range FILE,M:N options to follow the history of lines
4608 Use -L/--line-range FILE,M:N options to follow the history of lines
4609 from M to N in FILE. With -p/--patch only diff hunks affecting
4609 from M to N in FILE. With -p/--patch only diff hunks affecting
4610 specified line range will be shown. This option requires --follow;
4610 specified line range will be shown. This option requires --follow;
4611 it can be specified multiple times. Currently, this option is not
4611 it can be specified multiple times. Currently, this option is not
4612 compatible with --graph. This option is experimental.
4612 compatible with --graph. This option is experimental.
4613
4613
4614 .. note::
4614 .. note::
4615
4615
4616 :hg:`log --patch` may generate unexpected diff output for merge
4616 :hg:`log --patch` may generate unexpected diff output for merge
4617 changesets, as it will only compare the merge changeset against
4617 changesets, as it will only compare the merge changeset against
4618 its first parent. Also, only files different from BOTH parents
4618 its first parent. Also, only files different from BOTH parents
4619 will appear in files:.
4619 will appear in files:.
4620
4620
4621 .. note::
4621 .. note::
4622
4622
4623 For performance reasons, :hg:`log FILE` may omit duplicate changes
4623 For performance reasons, :hg:`log FILE` may omit duplicate changes
4624 made on branches and will not show removals or mode changes. To
4624 made on branches and will not show removals or mode changes. To
4625 see all such changes, use the --removed switch.
4625 see all such changes, use the --removed switch.
4626
4626
4627 .. container:: verbose
4627 .. container:: verbose
4628
4628
4629 .. note::
4629 .. note::
4630
4630
4631 The history resulting from -L/--line-range options depends on diff
4631 The history resulting from -L/--line-range options depends on diff
4632 options; for instance if white-spaces are ignored, respective changes
4632 options; for instance if white-spaces are ignored, respective changes
4633 with only white-spaces in specified line range will not be listed.
4633 with only white-spaces in specified line range will not be listed.
4634
4634
4635 .. container:: verbose
4635 .. container:: verbose
4636
4636
4637 Some examples:
4637 Some examples:
4638
4638
4639 - changesets with full descriptions and file lists::
4639 - changesets with full descriptions and file lists::
4640
4640
4641 hg log -v
4641 hg log -v
4642
4642
4643 - changesets ancestral to the working directory::
4643 - changesets ancestral to the working directory::
4644
4644
4645 hg log -f
4645 hg log -f
4646
4646
4647 - last 10 commits on the current branch::
4647 - last 10 commits on the current branch::
4648
4648
4649 hg log -l 10 -b .
4649 hg log -l 10 -b .
4650
4650
4651 - changesets showing all modifications of a file, including removals::
4651 - changesets showing all modifications of a file, including removals::
4652
4652
4653 hg log --removed file.c
4653 hg log --removed file.c
4654
4654
4655 - all changesets that touch a directory, with diffs, excluding merges::
4655 - all changesets that touch a directory, with diffs, excluding merges::
4656
4656
4657 hg log -Mp lib/
4657 hg log -Mp lib/
4658
4658
4659 - all revision numbers that match a keyword::
4659 - all revision numbers that match a keyword::
4660
4660
4661 hg log -k bug --template "{rev}\\n"
4661 hg log -k bug --template "{rev}\\n"
4662
4662
4663 - the full hash identifier of the working directory parent::
4663 - the full hash identifier of the working directory parent::
4664
4664
4665 hg log -r . --template "{node}\\n"
4665 hg log -r . --template "{node}\\n"
4666
4666
4667 - list available log templates::
4667 - list available log templates::
4668
4668
4669 hg log -T list
4669 hg log -T list
4670
4670
4671 - check if a given changeset is included in a tagged release::
4671 - check if a given changeset is included in a tagged release::
4672
4672
4673 hg log -r "a21ccf and ancestor(1.9)"
4673 hg log -r "a21ccf and ancestor(1.9)"
4674
4674
4675 - find all changesets by some user in a date range::
4675 - find all changesets by some user in a date range::
4676
4676
4677 hg log -k alice -d "may 2008 to jul 2008"
4677 hg log -k alice -d "may 2008 to jul 2008"
4678
4678
4679 - summary of all changesets after the last tag::
4679 - summary of all changesets after the last tag::
4680
4680
4681 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4681 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4682
4682
4683 - changesets touching lines 13 to 23 for file.c::
4683 - changesets touching lines 13 to 23 for file.c::
4684
4684
4685 hg log -L file.c,13:23
4685 hg log -L file.c,13:23
4686
4686
4687 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4687 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4688 main.c with patch::
4688 main.c with patch::
4689
4689
4690 hg log -L file.c,13:23 -L main.c,2:6 -p
4690 hg log -L file.c,13:23 -L main.c,2:6 -p
4691
4691
4692 See :hg:`help dates` for a list of formats valid for -d/--date.
4692 See :hg:`help dates` for a list of formats valid for -d/--date.
4693
4693
4694 See :hg:`help revisions` for more about specifying and ordering
4694 See :hg:`help revisions` for more about specifying and ordering
4695 revisions.
4695 revisions.
4696
4696
4697 See :hg:`help templates` for more about pre-packaged styles and
4697 See :hg:`help templates` for more about pre-packaged styles and
4698 specifying custom templates. The default template used by the log
4698 specifying custom templates. The default template used by the log
4699 command can be customized via the ``command-templates.log`` configuration
4699 command can be customized via the ``command-templates.log`` configuration
4700 setting.
4700 setting.
4701
4701
4702 Returns 0 on success.
4702 Returns 0 on success.
4703
4703
4704 """
4704 """
4705 opts = pycompat.byteskwargs(opts)
4705 opts = pycompat.byteskwargs(opts)
4706 linerange = opts.get(b'line_range')
4706 linerange = opts.get(b'line_range')
4707
4707
4708 if linerange and not opts.get(b'follow'):
4708 if linerange and not opts.get(b'follow'):
4709 raise error.InputError(_(b'--line-range requires --follow'))
4709 raise error.InputError(_(b'--line-range requires --follow'))
4710
4710
4711 if linerange and pats:
4711 if linerange and pats:
4712 # TODO: take pats as patterns with no line-range filter
4712 # TODO: take pats as patterns with no line-range filter
4713 raise error.InputError(
4713 raise error.InputError(
4714 _(b'FILE arguments are not compatible with --line-range option')
4714 _(b'FILE arguments are not compatible with --line-range option')
4715 )
4715 )
4716
4716
4717 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4717 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4718 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4718 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4719 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4719 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4720 if linerange:
4720 if linerange:
4721 # TODO: should follow file history from logcmdutil._initialrevs(),
4721 # TODO: should follow file history from logcmdutil._initialrevs(),
4722 # then filter the result by logcmdutil._makerevset() and --limit
4722 # then filter the result by logcmdutil._makerevset() and --limit
4723 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4723 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4724
4724
4725 getcopies = None
4725 getcopies = None
4726 if opts.get(b'copies'):
4726 if opts.get(b'copies'):
4727 endrev = None
4727 endrev = None
4728 if revs:
4728 if revs:
4729 endrev = revs.max() + 1
4729 endrev = revs.max() + 1
4730 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4730 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4731
4731
4732 ui.pager(b'log')
4732 ui.pager(b'log')
4733 displayer = logcmdutil.changesetdisplayer(
4733 displayer = logcmdutil.changesetdisplayer(
4734 ui, repo, opts, differ, buffered=True
4734 ui, repo, opts, differ, buffered=True
4735 )
4735 )
4736 if opts.get(b'graph'):
4736 if opts.get(b'graph'):
4737 displayfn = logcmdutil.displaygraphrevs
4737 displayfn = logcmdutil.displaygraphrevs
4738 else:
4738 else:
4739 displayfn = logcmdutil.displayrevs
4739 displayfn = logcmdutil.displayrevs
4740 displayfn(ui, repo, revs, displayer, getcopies)
4740 displayfn(ui, repo, revs, displayer, getcopies)
4741
4741
4742
4742
4743 @command(
4743 @command(
4744 b'manifest',
4744 b'manifest',
4745 [
4745 [
4746 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4746 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4747 (b'', b'all', False, _(b"list files from all revisions")),
4747 (b'', b'all', False, _(b"list files from all revisions")),
4748 ]
4748 ]
4749 + formatteropts,
4749 + formatteropts,
4750 _(b'[-r REV]'),
4750 _(b'[-r REV]'),
4751 helpcategory=command.CATEGORY_MAINTENANCE,
4751 helpcategory=command.CATEGORY_MAINTENANCE,
4752 intents={INTENT_READONLY},
4752 intents={INTENT_READONLY},
4753 )
4753 )
4754 def manifest(ui, repo, node=None, rev=None, **opts):
4754 def manifest(ui, repo, node=None, rev=None, **opts):
4755 """output the current or given revision of the project manifest
4755 """output the current or given revision of the project manifest
4756
4756
4757 Print a list of version controlled files for the given revision.
4757 Print a list of version controlled files for the given revision.
4758 If no revision is given, the first parent of the working directory
4758 If no revision is given, the first parent of the working directory
4759 is used, or the null revision if no revision is checked out.
4759 is used, or the null revision if no revision is checked out.
4760
4760
4761 With -v, print file permissions, symlink and executable bits.
4761 With -v, print file permissions, symlink and executable bits.
4762 With --debug, print file revision hashes.
4762 With --debug, print file revision hashes.
4763
4763
4764 If option --all is specified, the list of all files from all revisions
4764 If option --all is specified, the list of all files from all revisions
4765 is printed. This includes deleted and renamed files.
4765 is printed. This includes deleted and renamed files.
4766
4766
4767 Returns 0 on success.
4767 Returns 0 on success.
4768 """
4768 """
4769 opts = pycompat.byteskwargs(opts)
4769 opts = pycompat.byteskwargs(opts)
4770 fm = ui.formatter(b'manifest', opts)
4770 fm = ui.formatter(b'manifest', opts)
4771
4771
4772 if opts.get(b'all'):
4772 if opts.get(b'all'):
4773 if rev or node:
4773 if rev or node:
4774 raise error.InputError(_(b"can't specify a revision with --all"))
4774 raise error.InputError(_(b"can't specify a revision with --all"))
4775
4775
4776 res = set()
4776 res = set()
4777 for rev in repo:
4777 for rev in repo:
4778 ctx = repo[rev]
4778 ctx = repo[rev]
4779 res |= set(ctx.files())
4779 res |= set(ctx.files())
4780
4780
4781 ui.pager(b'manifest')
4781 ui.pager(b'manifest')
4782 for f in sorted(res):
4782 for f in sorted(res):
4783 fm.startitem()
4783 fm.startitem()
4784 fm.write(b"path", b'%s\n', f)
4784 fm.write(b"path", b'%s\n', f)
4785 fm.end()
4785 fm.end()
4786 return
4786 return
4787
4787
4788 if rev and node:
4788 if rev and node:
4789 raise error.InputError(_(b"please specify just one revision"))
4789 raise error.InputError(_(b"please specify just one revision"))
4790
4790
4791 if not node:
4791 if not node:
4792 node = rev
4792 node = rev
4793
4793
4794 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4794 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4795 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4795 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4796 if node:
4796 if node:
4797 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4797 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4798 ctx = logcmdutil.revsingle(repo, node)
4798 ctx = logcmdutil.revsingle(repo, node)
4799 mf = ctx.manifest()
4799 mf = ctx.manifest()
4800 ui.pager(b'manifest')
4800 ui.pager(b'manifest')
4801 for f in ctx:
4801 for f in ctx:
4802 fm.startitem()
4802 fm.startitem()
4803 fm.context(ctx=ctx)
4803 fm.context(ctx=ctx)
4804 fl = ctx[f].flags()
4804 fl = ctx[f].flags()
4805 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4805 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4806 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4806 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4807 fm.write(b'path', b'%s\n', f)
4807 fm.write(b'path', b'%s\n', f)
4808 fm.end()
4808 fm.end()
4809
4809
4810
4810
4811 @command(
4811 @command(
4812 b'merge',
4812 b'merge',
4813 [
4813 [
4814 (
4814 (
4815 b'f',
4815 b'f',
4816 b'force',
4816 b'force',
4817 None,
4817 None,
4818 _(b'force a merge including outstanding changes (DEPRECATED)'),
4818 _(b'force a merge including outstanding changes (DEPRECATED)'),
4819 ),
4819 ),
4820 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4820 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4821 (
4821 (
4822 b'P',
4822 b'P',
4823 b'preview',
4823 b'preview',
4824 None,
4824 None,
4825 _(b'review revisions to merge (no merge is performed)'),
4825 _(b'review revisions to merge (no merge is performed)'),
4826 ),
4826 ),
4827 (b'', b'abort', None, _(b'abort the ongoing merge')),
4827 (b'', b'abort', None, _(b'abort the ongoing merge')),
4828 ]
4828 ]
4829 + mergetoolopts,
4829 + mergetoolopts,
4830 _(b'[-P] [[-r] REV]'),
4830 _(b'[-P] [[-r] REV]'),
4831 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4831 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4832 helpbasic=True,
4832 helpbasic=True,
4833 )
4833 )
4834 def merge(ui, repo, node=None, **opts):
4834 def merge(ui, repo, node=None, **opts):
4835 """merge another revision into working directory
4835 """merge another revision into working directory
4836
4836
4837 The current working directory is updated with all changes made in
4837 The current working directory is updated with all changes made in
4838 the requested revision since the last common predecessor revision.
4838 the requested revision since the last common predecessor revision.
4839
4839
4840 Files that changed between either parent are marked as changed for
4840 Files that changed between either parent are marked as changed for
4841 the next commit and a commit must be performed before any further
4841 the next commit and a commit must be performed before any further
4842 updates to the repository are allowed. The next commit will have
4842 updates to the repository are allowed. The next commit will have
4843 two parents.
4843 two parents.
4844
4844
4845 ``--tool`` can be used to specify the merge tool used for file
4845 ``--tool`` can be used to specify the merge tool used for file
4846 merges. It overrides the HGMERGE environment variable and your
4846 merges. It overrides the HGMERGE environment variable and your
4847 configuration files. See :hg:`help merge-tools` for options.
4847 configuration files. See :hg:`help merge-tools` for options.
4848
4848
4849 If no revision is specified, the working directory's parent is a
4849 If no revision is specified, the working directory's parent is a
4850 head revision, and the current branch contains exactly one other
4850 head revision, and the current branch contains exactly one other
4851 head, the other head is merged with by default. Otherwise, an
4851 head, the other head is merged with by default. Otherwise, an
4852 explicit revision with which to merge must be provided.
4852 explicit revision with which to merge must be provided.
4853
4853
4854 See :hg:`help resolve` for information on handling file conflicts.
4854 See :hg:`help resolve` for information on handling file conflicts.
4855
4855
4856 To undo an uncommitted merge, use :hg:`merge --abort` which
4856 To undo an uncommitted merge, use :hg:`merge --abort` which
4857 will check out a clean copy of the original merge parent, losing
4857 will check out a clean copy of the original merge parent, losing
4858 all changes.
4858 all changes.
4859
4859
4860 Returns 0 on success, 1 if there are unresolved files.
4860 Returns 0 on success, 1 if there are unresolved files.
4861 """
4861 """
4862
4862
4863 opts = pycompat.byteskwargs(opts)
4863 opts = pycompat.byteskwargs(opts)
4864 abort = opts.get(b'abort')
4864 abort = opts.get(b'abort')
4865 if abort and repo.dirstate.p2() == repo.nullid:
4865 if abort and repo.dirstate.p2() == repo.nullid:
4866 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4866 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4867 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4867 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4868 if abort:
4868 if abort:
4869 state = cmdutil.getunfinishedstate(repo)
4869 state = cmdutil.getunfinishedstate(repo)
4870 if state and state._opname != b'merge':
4870 if state and state._opname != b'merge':
4871 raise error.StateError(
4871 raise error.StateError(
4872 _(b'cannot abort merge with %s in progress') % (state._opname),
4872 _(b'cannot abort merge with %s in progress') % (state._opname),
4873 hint=state.hint(),
4873 hint=state.hint(),
4874 )
4874 )
4875 if node:
4875 if node:
4876 raise error.InputError(_(b"cannot specify a node with --abort"))
4876 raise error.InputError(_(b"cannot specify a node with --abort"))
4877 return hg.abortmerge(repo.ui, repo)
4877 return hg.abortmerge(repo.ui, repo)
4878
4878
4879 if opts.get(b'rev') and node:
4879 if opts.get(b'rev') and node:
4880 raise error.InputError(_(b"please specify just one revision"))
4880 raise error.InputError(_(b"please specify just one revision"))
4881 if not node:
4881 if not node:
4882 node = opts.get(b'rev')
4882 node = opts.get(b'rev')
4883
4883
4884 if node:
4884 if node:
4885 ctx = logcmdutil.revsingle(repo, node)
4885 ctx = logcmdutil.revsingle(repo, node)
4886 else:
4886 else:
4887 if ui.configbool(b'commands', b'merge.require-rev'):
4887 if ui.configbool(b'commands', b'merge.require-rev'):
4888 raise error.InputError(
4888 raise error.InputError(
4889 _(
4889 _(
4890 b'configuration requires specifying revision to merge '
4890 b'configuration requires specifying revision to merge '
4891 b'with'
4891 b'with'
4892 )
4892 )
4893 )
4893 )
4894 ctx = repo[destutil.destmerge(repo)]
4894 ctx = repo[destutil.destmerge(repo)]
4895
4895
4896 if ctx.node() is None:
4896 if ctx.node() is None:
4897 raise error.InputError(
4897 raise error.InputError(
4898 _(b'merging with the working copy has no effect')
4898 _(b'merging with the working copy has no effect')
4899 )
4899 )
4900
4900
4901 if opts.get(b'preview'):
4901 if opts.get(b'preview'):
4902 # find nodes that are ancestors of p2 but not of p1
4902 # find nodes that are ancestors of p2 but not of p1
4903 p1 = repo[b'.'].node()
4903 p1 = repo[b'.'].node()
4904 p2 = ctx.node()
4904 p2 = ctx.node()
4905 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4905 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4906
4906
4907 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4907 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4908 for node in nodes:
4908 for node in nodes:
4909 displayer.show(repo[node])
4909 displayer.show(repo[node])
4910 displayer.close()
4910 displayer.close()
4911 return 0
4911 return 0
4912
4912
4913 # ui.forcemerge is an internal variable, do not document
4913 # ui.forcemerge is an internal variable, do not document
4914 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4914 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4915 with ui.configoverride(overrides, b'merge'):
4915 with ui.configoverride(overrides, b'merge'):
4916 force = opts.get(b'force')
4916 force = opts.get(b'force')
4917 labels = [b'working copy', b'merge rev']
4917 labels = [b'working copy', b'merge rev']
4918 return hg.merge(ctx, force=force, labels=labels)
4918 return hg.merge(ctx, force=force, labels=labels)
4919
4919
4920
4920
4921 statemod.addunfinished(
4921 statemod.addunfinished(
4922 b'merge',
4922 b'merge',
4923 fname=None,
4923 fname=None,
4924 clearable=True,
4924 clearable=True,
4925 allowcommit=True,
4925 allowcommit=True,
4926 cmdmsg=_(b'outstanding uncommitted merge'),
4926 cmdmsg=_(b'outstanding uncommitted merge'),
4927 abortfunc=hg.abortmerge,
4927 abortfunc=hg.abortmerge,
4928 statushint=_(
4928 statushint=_(
4929 b'To continue: hg commit\nTo abort: hg merge --abort'
4929 b'To continue: hg commit\nTo abort: hg merge --abort'
4930 ),
4930 ),
4931 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4931 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4932 )
4932 )
4933
4933
4934
4934
4935 @command(
4935 @command(
4936 b'outgoing|out',
4936 b'outgoing|out',
4937 [
4937 [
4938 (
4938 (
4939 b'f',
4939 b'f',
4940 b'force',
4940 b'force',
4941 None,
4941 None,
4942 _(b'run even when the destination is unrelated'),
4942 _(b'run even when the destination is unrelated'),
4943 ),
4943 ),
4944 (
4944 (
4945 b'r',
4945 b'r',
4946 b'rev',
4946 b'rev',
4947 [],
4947 [],
4948 _(b'a changeset intended to be included in the destination'),
4948 _(b'a changeset intended to be included in the destination'),
4949 _(b'REV'),
4949 _(b'REV'),
4950 ),
4950 ),
4951 (b'n', b'newest-first', None, _(b'show newest record first')),
4951 (b'n', b'newest-first', None, _(b'show newest record first')),
4952 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4952 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4953 (
4953 (
4954 b'b',
4954 b'b',
4955 b'branch',
4955 b'branch',
4956 [],
4956 [],
4957 _(b'a specific branch you would like to push'),
4957 _(b'a specific branch you would like to push'),
4958 _(b'BRANCH'),
4958 _(b'BRANCH'),
4959 ),
4959 ),
4960 ]
4960 ]
4961 + logopts
4961 + logopts
4962 + remoteopts
4962 + remoteopts
4963 + subrepoopts,
4963 + subrepoopts,
4964 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4964 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4965 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4965 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4966 )
4966 )
4967 def outgoing(ui, repo, *dests, **opts):
4967 def outgoing(ui, repo, *dests, **opts):
4968 """show changesets not found in the destination
4968 """show changesets not found in the destination
4969
4969
4970 Show changesets not found in the specified destination repository
4970 Show changesets not found in the specified destination repository
4971 or the default push location. These are the changesets that would
4971 or the default push location. These are the changesets that would
4972 be pushed if a push was requested.
4972 be pushed if a push was requested.
4973
4973
4974 See pull for details of valid destination formats.
4974 See pull for details of valid destination formats.
4975
4975
4976 .. container:: verbose
4976 .. container:: verbose
4977
4977
4978 With -B/--bookmarks, the result of bookmark comparison between
4978 With -B/--bookmarks, the result of bookmark comparison between
4979 local and remote repositories is displayed. With -v/--verbose,
4979 local and remote repositories is displayed. With -v/--verbose,
4980 status is also displayed for each bookmark like below::
4980 status is also displayed for each bookmark like below::
4981
4981
4982 BM1 01234567890a added
4982 BM1 01234567890a added
4983 BM2 deleted
4983 BM2 deleted
4984 BM3 234567890abc advanced
4984 BM3 234567890abc advanced
4985 BM4 34567890abcd diverged
4985 BM4 34567890abcd diverged
4986 BM5 4567890abcde changed
4986 BM5 4567890abcde changed
4987
4987
4988 The action taken when pushing depends on the
4988 The action taken when pushing depends on the
4989 status of each bookmark:
4989 status of each bookmark:
4990
4990
4991 :``added``: push with ``-B`` will create it
4991 :``added``: push with ``-B`` will create it
4992 :``deleted``: push with ``-B`` will delete it
4992 :``deleted``: push with ``-B`` will delete it
4993 :``advanced``: push will update it
4993 :``advanced``: push will update it
4994 :``diverged``: push with ``-B`` will update it
4994 :``diverged``: push with ``-B`` will update it
4995 :``changed``: push with ``-B`` will update it
4995 :``changed``: push with ``-B`` will update it
4996
4996
4997 From the point of view of pushing behavior, bookmarks
4997 From the point of view of pushing behavior, bookmarks
4998 existing only in the remote repository are treated as
4998 existing only in the remote repository are treated as
4999 ``deleted``, even if it is in fact added remotely.
4999 ``deleted``, even if it is in fact added remotely.
5000
5000
5001 Returns 0 if there are outgoing changes, 1 otherwise.
5001 Returns 0 if there are outgoing changes, 1 otherwise.
5002 """
5002 """
5003 opts = pycompat.byteskwargs(opts)
5003 opts = pycompat.byteskwargs(opts)
5004 if opts.get(b'bookmarks'):
5004 if opts.get(b'bookmarks'):
5005 for path in urlutil.get_push_paths(repo, ui, dests):
5005 for path in urlutil.get_push_paths(repo, ui, dests):
5006 dest = path.pushloc or path.loc
5006 dest = path.pushloc or path.loc
5007 other = hg.peer(repo, opts, dest)
5007 other = hg.peer(repo, opts, dest)
5008 try:
5008 try:
5009 if b'bookmarks' not in other.listkeys(b'namespaces'):
5009 if b'bookmarks' not in other.listkeys(b'namespaces'):
5010 ui.warn(_(b"remote doesn't support bookmarks\n"))
5010 ui.warn(_(b"remote doesn't support bookmarks\n"))
5011 return 0
5011 return 0
5012 ui.status(
5012 ui.status(
5013 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5013 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5014 )
5014 )
5015 ui.pager(b'outgoing')
5015 ui.pager(b'outgoing')
5016 return bookmarks.outgoing(ui, repo, other)
5016 return bookmarks.outgoing(ui, repo, other)
5017 finally:
5017 finally:
5018 other.close()
5018 other.close()
5019
5019
5020 return hg.outgoing(ui, repo, dests, opts)
5020 return hg.outgoing(ui, repo, dests, opts)
5021
5021
5022
5022
5023 @command(
5023 @command(
5024 b'parents',
5024 b'parents',
5025 [
5025 [
5026 (
5026 (
5027 b'r',
5027 b'r',
5028 b'rev',
5028 b'rev',
5029 b'',
5029 b'',
5030 _(b'show parents of the specified revision'),
5030 _(b'show parents of the specified revision'),
5031 _(b'REV'),
5031 _(b'REV'),
5032 ),
5032 ),
5033 ]
5033 ]
5034 + templateopts,
5034 + templateopts,
5035 _(b'[-r REV] [FILE]'),
5035 _(b'[-r REV] [FILE]'),
5036 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5036 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5037 inferrepo=True,
5037 inferrepo=True,
5038 )
5038 )
5039 def parents(ui, repo, file_=None, **opts):
5039 def parents(ui, repo, file_=None, **opts):
5040 """show the parents of the working directory or revision (DEPRECATED)
5040 """show the parents of the working directory or revision (DEPRECATED)
5041
5041
5042 Print the working directory's parent revisions. If a revision is
5042 Print the working directory's parent revisions. If a revision is
5043 given via -r/--rev, the parent of that revision will be printed.
5043 given via -r/--rev, the parent of that revision will be printed.
5044 If a file argument is given, the revision in which the file was
5044 If a file argument is given, the revision in which the file was
5045 last changed (before the working directory revision or the
5045 last changed (before the working directory revision or the
5046 argument to --rev if given) is printed.
5046 argument to --rev if given) is printed.
5047
5047
5048 This command is equivalent to::
5048 This command is equivalent to::
5049
5049
5050 hg log -r "p1()+p2()" or
5050 hg log -r "p1()+p2()" or
5051 hg log -r "p1(REV)+p2(REV)" or
5051 hg log -r "p1(REV)+p2(REV)" or
5052 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5052 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5053 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5053 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5054
5054
5055 See :hg:`summary` and :hg:`help revsets` for related information.
5055 See :hg:`summary` and :hg:`help revsets` for related information.
5056
5056
5057 Returns 0 on success.
5057 Returns 0 on success.
5058 """
5058 """
5059
5059
5060 opts = pycompat.byteskwargs(opts)
5060 opts = pycompat.byteskwargs(opts)
5061 rev = opts.get(b'rev')
5061 rev = opts.get(b'rev')
5062 if rev:
5062 if rev:
5063 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5063 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5064 ctx = logcmdutil.revsingle(repo, rev, None)
5064 ctx = logcmdutil.revsingle(repo, rev, None)
5065
5065
5066 if file_:
5066 if file_:
5067 m = scmutil.match(ctx, (file_,), opts)
5067 m = scmutil.match(ctx, (file_,), opts)
5068 if m.anypats() or len(m.files()) != 1:
5068 if m.anypats() or len(m.files()) != 1:
5069 raise error.InputError(_(b'can only specify an explicit filename'))
5069 raise error.InputError(_(b'can only specify an explicit filename'))
5070 file_ = m.files()[0]
5070 file_ = m.files()[0]
5071 filenodes = []
5071 filenodes = []
5072 for cp in ctx.parents():
5072 for cp in ctx.parents():
5073 if not cp:
5073 if not cp:
5074 continue
5074 continue
5075 try:
5075 try:
5076 filenodes.append(cp.filenode(file_))
5076 filenodes.append(cp.filenode(file_))
5077 except error.LookupError:
5077 except error.LookupError:
5078 pass
5078 pass
5079 if not filenodes:
5079 if not filenodes:
5080 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5080 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5081 p = []
5081 p = []
5082 for fn in filenodes:
5082 for fn in filenodes:
5083 fctx = repo.filectx(file_, fileid=fn)
5083 fctx = repo.filectx(file_, fileid=fn)
5084 p.append(fctx.node())
5084 p.append(fctx.node())
5085 else:
5085 else:
5086 p = [cp.node() for cp in ctx.parents()]
5086 p = [cp.node() for cp in ctx.parents()]
5087
5087
5088 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5088 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5089 for n in p:
5089 for n in p:
5090 if n != repo.nullid:
5090 if n != repo.nullid:
5091 displayer.show(repo[n])
5091 displayer.show(repo[n])
5092 displayer.close()
5092 displayer.close()
5093
5093
5094
5094
5095 @command(
5095 @command(
5096 b'paths',
5096 b'paths',
5097 formatteropts,
5097 formatteropts,
5098 _(b'[NAME]'),
5098 _(b'[NAME]'),
5099 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5099 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5100 optionalrepo=True,
5100 optionalrepo=True,
5101 intents={INTENT_READONLY},
5101 intents={INTENT_READONLY},
5102 )
5102 )
5103 def paths(ui, repo, search=None, **opts):
5103 def paths(ui, repo, search=None, **opts):
5104 """show aliases for remote repositories
5104 """show aliases for remote repositories
5105
5105
5106 Show definition of symbolic path name NAME. If no name is given,
5106 Show definition of symbolic path name NAME. If no name is given,
5107 show definition of all available names.
5107 show definition of all available names.
5108
5108
5109 Option -q/--quiet suppresses all output when searching for NAME
5109 Option -q/--quiet suppresses all output when searching for NAME
5110 and shows only the path names when listing all definitions.
5110 and shows only the path names when listing all definitions.
5111
5111
5112 Path names are defined in the [paths] section of your
5112 Path names are defined in the [paths] section of your
5113 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5113 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5114 repository, ``.hg/hgrc`` is used, too.
5114 repository, ``.hg/hgrc`` is used, too.
5115
5115
5116 The path names ``default`` and ``default-push`` have a special
5116 The path names ``default`` and ``default-push`` have a special
5117 meaning. When performing a push or pull operation, they are used
5117 meaning. When performing a push or pull operation, they are used
5118 as fallbacks if no location is specified on the command-line.
5118 as fallbacks if no location is specified on the command-line.
5119 When ``default-push`` is set, it will be used for push and
5119 When ``default-push`` is set, it will be used for push and
5120 ``default`` will be used for pull; otherwise ``default`` is used
5120 ``default`` will be used for pull; otherwise ``default`` is used
5121 as the fallback for both. When cloning a repository, the clone
5121 as the fallback for both. When cloning a repository, the clone
5122 source is written as ``default`` in ``.hg/hgrc``.
5122 source is written as ``default`` in ``.hg/hgrc``.
5123
5123
5124 .. note::
5124 .. note::
5125
5125
5126 ``default`` and ``default-push`` apply to all inbound (e.g.
5126 ``default`` and ``default-push`` apply to all inbound (e.g.
5127 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5127 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5128 and :hg:`bundle`) operations.
5128 and :hg:`bundle`) operations.
5129
5129
5130 See :hg:`help urls` for more information.
5130 See :hg:`help urls` for more information.
5131
5131
5132 .. container:: verbose
5132 .. container:: verbose
5133
5133
5134 Template:
5134 Template:
5135
5135
5136 The following keywords are supported. See also :hg:`help templates`.
5136 The following keywords are supported. See also :hg:`help templates`.
5137
5137
5138 :name: String. Symbolic name of the path alias.
5138 :name: String. Symbolic name of the path alias.
5139 :pushurl: String. URL for push operations.
5139 :pushurl: String. URL for push operations.
5140 :url: String. URL or directory path for the other operations.
5140 :url: String. URL or directory path for the other operations.
5141
5141
5142 Returns 0 on success.
5142 Returns 0 on success.
5143 """
5143 """
5144
5144
5145 opts = pycompat.byteskwargs(opts)
5145 opts = pycompat.byteskwargs(opts)
5146
5146
5147 pathitems = urlutil.list_paths(ui, search)
5147 pathitems = urlutil.list_paths(ui, search)
5148 ui.pager(b'paths')
5148 ui.pager(b'paths')
5149
5149
5150 fm = ui.formatter(b'paths', opts)
5150 fm = ui.formatter(b'paths', opts)
5151 if fm.isplain():
5151 if fm.isplain():
5152 hidepassword = urlutil.hidepassword
5152 hidepassword = urlutil.hidepassword
5153 else:
5153 else:
5154 hidepassword = bytes
5154 hidepassword = bytes
5155 if ui.quiet:
5155 if ui.quiet:
5156 namefmt = b'%s\n'
5156 namefmt = b'%s\n'
5157 else:
5157 else:
5158 namefmt = b'%s = '
5158 namefmt = b'%s = '
5159 showsubopts = not search and not ui.quiet
5159 showsubopts = not search and not ui.quiet
5160
5160
5161 for name, path in pathitems:
5161 for name, path in pathitems:
5162 fm.startitem()
5162 fm.startitem()
5163 fm.condwrite(not search, b'name', namefmt, name)
5163 fm.condwrite(not search, b'name', namefmt, name)
5164 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5164 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5165 for subopt, value in sorted(path.suboptions.items()):
5165 for subopt, value in sorted(path.suboptions.items()):
5166 assert subopt not in (b'name', b'url')
5166 assert subopt not in (b'name', b'url')
5167 if showsubopts:
5167 if showsubopts:
5168 fm.plain(b'%s:%s = ' % (name, subopt))
5168 fm.plain(b'%s:%s = ' % (name, subopt))
5169 if isinstance(value, bool):
5169 if isinstance(value, bool):
5170 if value:
5170 if value:
5171 value = b'yes'
5171 value = b'yes'
5172 else:
5172 else:
5173 value = b'no'
5173 value = b'no'
5174 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5174 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5175
5175
5176 fm.end()
5176 fm.end()
5177
5177
5178 if search and not pathitems:
5178 if search and not pathitems:
5179 if not ui.quiet:
5179 if not ui.quiet:
5180 ui.warn(_(b"not found!\n"))
5180 ui.warn(_(b"not found!\n"))
5181 return 1
5181 return 1
5182 else:
5182 else:
5183 return 0
5183 return 0
5184
5184
5185
5185
5186 @command(
5186 @command(
5187 b'phase',
5187 b'phase',
5188 [
5188 [
5189 (b'p', b'public', False, _(b'set changeset phase to public')),
5189 (b'p', b'public', False, _(b'set changeset phase to public')),
5190 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5190 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5191 (b's', b'secret', False, _(b'set changeset phase to secret')),
5191 (b's', b'secret', False, _(b'set changeset phase to secret')),
5192 (b'f', b'force', False, _(b'allow to move boundary backward')),
5192 (b'f', b'force', False, _(b'allow to move boundary backward')),
5193 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5193 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5194 ],
5194 ],
5195 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5195 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5196 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5196 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5197 )
5197 )
5198 def phase(ui, repo, *revs, **opts):
5198 def phase(ui, repo, *revs, **opts):
5199 """set or show the current phase name
5199 """set or show the current phase name
5200
5200
5201 With no argument, show the phase name of the current revision(s).
5201 With no argument, show the phase name of the current revision(s).
5202
5202
5203 With one of -p/--public, -d/--draft or -s/--secret, change the
5203 With one of -p/--public, -d/--draft or -s/--secret, change the
5204 phase value of the specified revisions.
5204 phase value of the specified revisions.
5205
5205
5206 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5206 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5207 lower phase to a higher phase. Phases are ordered as follows::
5207 lower phase to a higher phase. Phases are ordered as follows::
5208
5208
5209 public < draft < secret
5209 public < draft < secret
5210
5210
5211 Returns 0 on success, 1 if some phases could not be changed.
5211 Returns 0 on success, 1 if some phases could not be changed.
5212
5212
5213 (For more information about the phases concept, see :hg:`help phases`.)
5213 (For more information about the phases concept, see :hg:`help phases`.)
5214 """
5214 """
5215 opts = pycompat.byteskwargs(opts)
5215 opts = pycompat.byteskwargs(opts)
5216 # search for a unique phase argument
5216 # search for a unique phase argument
5217 targetphase = None
5217 targetphase = None
5218 for idx, name in enumerate(phases.cmdphasenames):
5218 for idx, name in enumerate(phases.cmdphasenames):
5219 if opts[name]:
5219 if opts[name]:
5220 if targetphase is not None:
5220 if targetphase is not None:
5221 raise error.InputError(_(b'only one phase can be specified'))
5221 raise error.InputError(_(b'only one phase can be specified'))
5222 targetphase = idx
5222 targetphase = idx
5223
5223
5224 # look for specified revision
5224 # look for specified revision
5225 revs = list(revs)
5225 revs = list(revs)
5226 revs.extend(opts[b'rev'])
5226 revs.extend(opts[b'rev'])
5227 if revs:
5227 if revs:
5228 revs = logcmdutil.revrange(repo, revs)
5228 revs = logcmdutil.revrange(repo, revs)
5229 else:
5229 else:
5230 # display both parents as the second parent phase can influence
5230 # display both parents as the second parent phase can influence
5231 # the phase of a merge commit
5231 # the phase of a merge commit
5232 revs = [c.rev() for c in repo[None].parents()]
5232 revs = [c.rev() for c in repo[None].parents()]
5233
5233
5234 ret = 0
5234 ret = 0
5235 if targetphase is None:
5235 if targetphase is None:
5236 # display
5236 # display
5237 for r in revs:
5237 for r in revs:
5238 ctx = repo[r]
5238 ctx = repo[r]
5239 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5239 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5240 else:
5240 else:
5241 with repo.lock(), repo.transaction(b"phase") as tr:
5241 with repo.lock(), repo.transaction(b"phase") as tr:
5242 # set phase
5242 # set phase
5243 if not revs:
5243 if not revs:
5244 raise error.InputError(_(b'empty revision set'))
5244 raise error.InputError(_(b'empty revision set'))
5245 nodes = [repo[r].node() for r in revs]
5245 nodes = [repo[r].node() for r in revs]
5246 # moving revision from public to draft may hide them
5246 # moving revision from public to draft may hide them
5247 # We have to check result on an unfiltered repository
5247 # We have to check result on an unfiltered repository
5248 unfi = repo.unfiltered()
5248 unfi = repo.unfiltered()
5249 getphase = unfi._phasecache.phase
5249 getphase = unfi._phasecache.phase
5250 olddata = [getphase(unfi, r) for r in unfi]
5250 olddata = [getphase(unfi, r) for r in unfi]
5251 phases.advanceboundary(repo, tr, targetphase, nodes)
5251 phases.advanceboundary(repo, tr, targetphase, nodes)
5252 if opts[b'force']:
5252 if opts[b'force']:
5253 phases.retractboundary(repo, tr, targetphase, nodes)
5253 phases.retractboundary(repo, tr, targetphase, nodes)
5254 getphase = unfi._phasecache.phase
5254 getphase = unfi._phasecache.phase
5255 newdata = [getphase(unfi, r) for r in unfi]
5255 newdata = [getphase(unfi, r) for r in unfi]
5256 changes = sum(newdata[r] != olddata[r] for r in unfi)
5256 changes = sum(newdata[r] != olddata[r] for r in unfi)
5257 cl = unfi.changelog
5257 cl = unfi.changelog
5258 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5258 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5259 if rejected:
5259 if rejected:
5260 ui.warn(
5260 ui.warn(
5261 _(
5261 _(
5262 b'cannot move %i changesets to a higher '
5262 b'cannot move %i changesets to a higher '
5263 b'phase, use --force\n'
5263 b'phase, use --force\n'
5264 )
5264 )
5265 % len(rejected)
5265 % len(rejected)
5266 )
5266 )
5267 ret = 1
5267 ret = 1
5268 if changes:
5268 if changes:
5269 msg = _(b'phase changed for %i changesets\n') % changes
5269 msg = _(b'phase changed for %i changesets\n') % changes
5270 if ret:
5270 if ret:
5271 ui.status(msg)
5271 ui.status(msg)
5272 else:
5272 else:
5273 ui.note(msg)
5273 ui.note(msg)
5274 else:
5274 else:
5275 ui.warn(_(b'no phases changed\n'))
5275 ui.warn(_(b'no phases changed\n'))
5276 return ret
5276 return ret
5277
5277
5278
5278
5279 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5279 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5280 """Run after a changegroup has been added via pull/unbundle
5280 """Run after a changegroup has been added via pull/unbundle
5281
5281
5282 This takes arguments below:
5282 This takes arguments below:
5283
5283
5284 :modheads: change of heads by pull/unbundle
5284 :modheads: change of heads by pull/unbundle
5285 :optupdate: updating working directory is needed or not
5285 :optupdate: updating working directory is needed or not
5286 :checkout: update destination revision (or None to default destination)
5286 :checkout: update destination revision (or None to default destination)
5287 :brev: a name, which might be a bookmark to be activated after updating
5287 :brev: a name, which might be a bookmark to be activated after updating
5288
5288
5289 return True if update raise any conflict, False otherwise.
5289 return True if update raise any conflict, False otherwise.
5290 """
5290 """
5291 if modheads == 0:
5291 if modheads == 0:
5292 return False
5292 return False
5293 if optupdate:
5293 if optupdate:
5294 try:
5294 try:
5295 return hg.updatetotally(ui, repo, checkout, brev)
5295 return hg.updatetotally(ui, repo, checkout, brev)
5296 except error.UpdateAbort as inst:
5296 except error.UpdateAbort as inst:
5297 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5297 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5298 hint = inst.hint
5298 hint = inst.hint
5299 raise error.UpdateAbort(msg, hint=hint)
5299 raise error.UpdateAbort(msg, hint=hint)
5300 if modheads is not None and modheads > 1:
5300 if modheads is not None and modheads > 1:
5301 currentbranchheads = len(repo.branchheads())
5301 currentbranchheads = len(repo.branchheads())
5302 if currentbranchheads == modheads:
5302 if currentbranchheads == modheads:
5303 ui.status(
5303 ui.status(
5304 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5304 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5305 )
5305 )
5306 elif currentbranchheads > 1:
5306 elif currentbranchheads > 1:
5307 ui.status(
5307 ui.status(
5308 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5308 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5309 )
5309 )
5310 else:
5310 else:
5311 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5311 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5312 elif not ui.configbool(b'commands', b'update.requiredest'):
5312 elif not ui.configbool(b'commands', b'update.requiredest'):
5313 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5313 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5314 return False
5314 return False
5315
5315
5316
5316
5317 @command(
5317 @command(
5318 b'pull',
5318 b'pull',
5319 [
5319 [
5320 (
5320 (
5321 b'u',
5321 b'u',
5322 b'update',
5322 b'update',
5323 None,
5323 None,
5324 _(b'update to new branch head if new descendants were pulled'),
5324 _(b'update to new branch head if new descendants were pulled'),
5325 ),
5325 ),
5326 (
5326 (
5327 b'f',
5327 b'f',
5328 b'force',
5328 b'force',
5329 None,
5329 None,
5330 _(b'run even when remote repository is unrelated'),
5330 _(b'run even when remote repository is unrelated'),
5331 ),
5331 ),
5332 (
5332 (
5333 b'',
5333 b'',
5334 b'confirm',
5334 b'confirm',
5335 None,
5335 None,
5336 _(b'confirm pull before applying changes'),
5336 _(b'confirm pull before applying changes'),
5337 ),
5337 ),
5338 (
5338 (
5339 b'r',
5339 b'r',
5340 b'rev',
5340 b'rev',
5341 [],
5341 [],
5342 _(b'a remote changeset intended to be added'),
5342 _(b'a remote changeset intended to be added'),
5343 _(b'REV'),
5343 _(b'REV'),
5344 ),
5344 ),
5345 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5345 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5346 (
5346 (
5347 b'b',
5347 b'b',
5348 b'branch',
5348 b'branch',
5349 [],
5349 [],
5350 _(b'a specific branch you would like to pull'),
5350 _(b'a specific branch you would like to pull'),
5351 _(b'BRANCH'),
5351 _(b'BRANCH'),
5352 ),
5352 ),
5353 ]
5353 ]
5354 + remoteopts,
5354 + remoteopts,
5355 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5355 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5356 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5356 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5357 helpbasic=True,
5357 helpbasic=True,
5358 )
5358 )
5359 def pull(ui, repo, *sources, **opts):
5359 def pull(ui, repo, *sources, **opts):
5360 """pull changes from the specified source
5360 """pull changes from the specified source
5361
5361
5362 Pull changes from a remote repository to a local one.
5362 Pull changes from a remote repository to a local one.
5363
5363
5364 This finds all changes from the repository at the specified path
5364 This finds all changes from the repository at the specified path
5365 or URL and adds them to a local repository (the current one unless
5365 or URL and adds them to a local repository (the current one unless
5366 -R is specified). By default, this does not update the copy of the
5366 -R is specified). By default, this does not update the copy of the
5367 project in the working directory.
5367 project in the working directory.
5368
5368
5369 When cloning from servers that support it, Mercurial may fetch
5369 When cloning from servers that support it, Mercurial may fetch
5370 pre-generated data. When this is done, hooks operating on incoming
5370 pre-generated data. When this is done, hooks operating on incoming
5371 changesets and changegroups may fire more than once, once for each
5371 changesets and changegroups may fire more than once, once for each
5372 pre-generated bundle and as well as for any additional remaining
5372 pre-generated bundle and as well as for any additional remaining
5373 data. See :hg:`help -e clonebundles` for more.
5373 data. See :hg:`help -e clonebundles` for more.
5374
5374
5375 Use :hg:`incoming` if you want to see what would have been added
5375 Use :hg:`incoming` if you want to see what would have been added
5376 by a pull at the time you issued this command. If you then decide
5376 by a pull at the time you issued this command. If you then decide
5377 to add those changes to the repository, you should use :hg:`pull
5377 to add those changes to the repository, you should use :hg:`pull
5378 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5378 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5379
5379
5380 If SOURCE is omitted, the 'default' path will be used.
5380 If SOURCE is omitted, the 'default' path will be used.
5381 See :hg:`help urls` for more information.
5381 See :hg:`help urls` for more information.
5382
5382
5383 If multiple sources are specified, they will be pulled sequentially as if
5383 If multiple sources are specified, they will be pulled sequentially as if
5384 the command was run multiple time. If --update is specify and the command
5384 the command was run multiple time. If --update is specify and the command
5385 will stop at the first failed --update.
5385 will stop at the first failed --update.
5386
5386
5387 Specifying bookmark as ``.`` is equivalent to specifying the active
5387 Specifying bookmark as ``.`` is equivalent to specifying the active
5388 bookmark's name.
5388 bookmark's name.
5389
5389
5390 Returns 0 on success, 1 if an update had unresolved files.
5390 Returns 0 on success, 1 if an update had unresolved files.
5391 """
5391 """
5392
5392
5393 opts = pycompat.byteskwargs(opts)
5393 opts = pycompat.byteskwargs(opts)
5394 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5394 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5395 b'update'
5395 b'update'
5396 ):
5396 ):
5397 msg = _(b'update destination required by configuration')
5397 msg = _(b'update destination required by configuration')
5398 hint = _(b'use hg pull followed by hg update DEST')
5398 hint = _(b'use hg pull followed by hg update DEST')
5399 raise error.InputError(msg, hint=hint)
5399 raise error.InputError(msg, hint=hint)
5400
5400
5401 for path in urlutil.get_pull_paths(repo, ui, sources):
5401 for path in urlutil.get_pull_paths(repo, ui, sources):
5402 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
5402 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
5403 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5403 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5404 ui.flush()
5404 ui.flush()
5405 other = hg.peer(repo, opts, source)
5405 other = hg.peer(repo, opts, source)
5406 update_conflict = None
5406 update_conflict = None
5407 try:
5407 try:
5408 revs, checkout = hg.addbranchrevs(
5408 revs, checkout = hg.addbranchrevs(
5409 repo, other, branches, opts.get(b'rev')
5409 repo, other, branches, opts.get(b'rev')
5410 )
5410 )
5411
5411
5412 pullopargs = {}
5412 pullopargs = {}
5413
5413
5414 nodes = None
5414 nodes = None
5415 if opts.get(b'bookmark') or revs:
5415 if opts.get(b'bookmark') or revs:
5416 # The list of bookmark used here is the same used to actually update
5416 # The list of bookmark used here is the same used to actually update
5417 # the bookmark names, to avoid the race from issue 4689 and we do
5417 # the bookmark names, to avoid the race from issue 4689 and we do
5418 # all lookup and bookmark queries in one go so they see the same
5418 # all lookup and bookmark queries in one go so they see the same
5419 # version of the server state (issue 4700).
5419 # version of the server state (issue 4700).
5420 nodes = []
5420 nodes = []
5421 fnodes = []
5421 fnodes = []
5422 revs = revs or []
5422 revs = revs or []
5423 if revs and not other.capable(b'lookup'):
5423 if revs and not other.capable(b'lookup'):
5424 err = _(
5424 err = _(
5425 b"other repository doesn't support revision lookup, "
5425 b"other repository doesn't support revision lookup, "
5426 b"so a rev cannot be specified."
5426 b"so a rev cannot be specified."
5427 )
5427 )
5428 raise error.Abort(err)
5428 raise error.Abort(err)
5429 with other.commandexecutor() as e:
5429 with other.commandexecutor() as e:
5430 fremotebookmarks = e.callcommand(
5430 fremotebookmarks = e.callcommand(
5431 b'listkeys', {b'namespace': b'bookmarks'}
5431 b'listkeys', {b'namespace': b'bookmarks'}
5432 )
5432 )
5433 for r in revs:
5433 for r in revs:
5434 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5434 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5435 remotebookmarks = fremotebookmarks.result()
5435 remotebookmarks = fremotebookmarks.result()
5436 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5436 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5437 pullopargs[b'remotebookmarks'] = remotebookmarks
5437 pullopargs[b'remotebookmarks'] = remotebookmarks
5438 for b in opts.get(b'bookmark', []):
5438 for b in opts.get(b'bookmark', []):
5439 b = repo._bookmarks.expandname(b)
5439 b = repo._bookmarks.expandname(b)
5440 if b not in remotebookmarks:
5440 if b not in remotebookmarks:
5441 raise error.InputError(
5441 raise error.InputError(
5442 _(b'remote bookmark %s not found!') % b
5442 _(b'remote bookmark %s not found!') % b
5443 )
5443 )
5444 nodes.append(remotebookmarks[b])
5444 nodes.append(remotebookmarks[b])
5445 for i, rev in enumerate(revs):
5445 for i, rev in enumerate(revs):
5446 node = fnodes[i].result()
5446 node = fnodes[i].result()
5447 nodes.append(node)
5447 nodes.append(node)
5448 if rev == checkout:
5448 if rev == checkout:
5449 checkout = node
5449 checkout = node
5450
5450
5451 wlock = util.nullcontextmanager()
5451 wlock = util.nullcontextmanager()
5452 if opts.get(b'update'):
5452 if opts.get(b'update'):
5453 wlock = repo.wlock()
5453 wlock = repo.wlock()
5454 with wlock:
5454 with wlock:
5455 pullopargs.update(opts.get(b'opargs', {}))
5455 pullopargs.update(opts.get(b'opargs', {}))
5456 modheads = exchange.pull(
5456 modheads = exchange.pull(
5457 repo,
5457 repo,
5458 other,
5458 other,
5459 path=path,
5459 path=path,
5460 heads=nodes,
5460 heads=nodes,
5461 force=opts.get(b'force'),
5461 force=opts.get(b'force'),
5462 bookmarks=opts.get(b'bookmark', ()),
5462 bookmarks=opts.get(b'bookmark', ()),
5463 opargs=pullopargs,
5463 opargs=pullopargs,
5464 confirm=opts.get(b'confirm'),
5464 confirm=opts.get(b'confirm'),
5465 ).cgresult
5465 ).cgresult
5466
5466
5467 # brev is a name, which might be a bookmark to be activated at
5467 # brev is a name, which might be a bookmark to be activated at
5468 # the end of the update. In other words, it is an explicit
5468 # the end of the update. In other words, it is an explicit
5469 # destination of the update
5469 # destination of the update
5470 brev = None
5470 brev = None
5471
5471
5472 if checkout:
5472 if checkout:
5473 checkout = repo.unfiltered().changelog.rev(checkout)
5473 checkout = repo.unfiltered().changelog.rev(checkout)
5474
5474
5475 # order below depends on implementation of
5475 # order below depends on implementation of
5476 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5476 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5477 # because 'checkout' is determined without it.
5477 # because 'checkout' is determined without it.
5478 if opts.get(b'rev'):
5478 if opts.get(b'rev'):
5479 brev = opts[b'rev'][0]
5479 brev = opts[b'rev'][0]
5480 elif opts.get(b'branch'):
5480 elif opts.get(b'branch'):
5481 brev = opts[b'branch'][0]
5481 brev = opts[b'branch'][0]
5482 else:
5482 else:
5483 brev = branches[0]
5483 brev = branches[0]
5484 repo._subtoppath = source
5484 repo._subtoppath = source
5485 try:
5485 try:
5486 update_conflict = postincoming(
5486 update_conflict = postincoming(
5487 ui, repo, modheads, opts.get(b'update'), checkout, brev
5487 ui, repo, modheads, opts.get(b'update'), checkout, brev
5488 )
5488 )
5489 except error.FilteredRepoLookupError as exc:
5489 except error.FilteredRepoLookupError as exc:
5490 msg = _(b'cannot update to target: %s') % exc.args[0]
5490 msg = _(b'cannot update to target: %s') % exc.args[0]
5491 exc.args = (msg,) + exc.args[1:]
5491 exc.args = (msg,) + exc.args[1:]
5492 raise
5492 raise
5493 finally:
5493 finally:
5494 del repo._subtoppath
5494 del repo._subtoppath
5495
5495
5496 finally:
5496 finally:
5497 other.close()
5497 other.close()
5498 # skip the remaining pull source if they are some conflict.
5498 # skip the remaining pull source if they are some conflict.
5499 if update_conflict:
5499 if update_conflict:
5500 break
5500 break
5501 if update_conflict:
5501 if update_conflict:
5502 return 1
5502 return 1
5503 else:
5503 else:
5504 return 0
5504 return 0
5505
5505
5506
5506
5507 @command(
5507 @command(
5508 b'purge|clean',
5508 b'purge|clean',
5509 [
5509 [
5510 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5510 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5511 (b'', b'all', None, _(b'purge ignored files too')),
5511 (b'', b'all', None, _(b'purge ignored files too')),
5512 (b'i', b'ignored', None, _(b'purge only ignored files')),
5512 (b'i', b'ignored', None, _(b'purge only ignored files')),
5513 (b'', b'dirs', None, _(b'purge empty directories')),
5513 (b'', b'dirs', None, _(b'purge empty directories')),
5514 (b'', b'files', None, _(b'purge files')),
5514 (b'', b'files', None, _(b'purge files')),
5515 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5515 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5516 (
5516 (
5517 b'0',
5517 b'0',
5518 b'print0',
5518 b'print0',
5519 None,
5519 None,
5520 _(
5520 _(
5521 b'end filenames with NUL, for use with xargs'
5521 b'end filenames with NUL, for use with xargs'
5522 b' (implies -p/--print)'
5522 b' (implies -p/--print)'
5523 ),
5523 ),
5524 ),
5524 ),
5525 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5525 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5526 ]
5526 ]
5527 + cmdutil.walkopts,
5527 + cmdutil.walkopts,
5528 _(b'hg purge [OPTION]... [DIR]...'),
5528 _(b'hg purge [OPTION]... [DIR]...'),
5529 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5529 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5530 )
5530 )
5531 def purge(ui, repo, *dirs, **opts):
5531 def purge(ui, repo, *dirs, **opts):
5532 """removes files not tracked by Mercurial
5532 """removes files not tracked by Mercurial
5533
5533
5534 Delete files not known to Mercurial. This is useful to test local
5534 Delete files not known to Mercurial. This is useful to test local
5535 and uncommitted changes in an otherwise-clean source tree.
5535 and uncommitted changes in an otherwise-clean source tree.
5536
5536
5537 This means that purge will delete the following by default:
5537 This means that purge will delete the following by default:
5538
5538
5539 - Unknown files: files marked with "?" by :hg:`status`
5539 - Unknown files: files marked with "?" by :hg:`status`
5540 - Empty directories: in fact Mercurial ignores directories unless
5540 - Empty directories: in fact Mercurial ignores directories unless
5541 they contain files under source control management
5541 they contain files under source control management
5542
5542
5543 But it will leave untouched:
5543 But it will leave untouched:
5544
5544
5545 - Modified and unmodified tracked files
5545 - Modified and unmodified tracked files
5546 - Ignored files (unless -i or --all is specified)
5546 - Ignored files (unless -i or --all is specified)
5547 - New files added to the repository (with :hg:`add`)
5547 - New files added to the repository (with :hg:`add`)
5548
5548
5549 The --files and --dirs options can be used to direct purge to delete
5549 The --files and --dirs options can be used to direct purge to delete
5550 only files, only directories, or both. If neither option is given,
5550 only files, only directories, or both. If neither option is given,
5551 both will be deleted.
5551 both will be deleted.
5552
5552
5553 If directories are given on the command line, only files in these
5553 If directories are given on the command line, only files in these
5554 directories are considered.
5554 directories are considered.
5555
5555
5556 Be careful with purge, as you could irreversibly delete some files
5556 Be careful with purge, as you could irreversibly delete some files
5557 you forgot to add to the repository. If you only want to print the
5557 you forgot to add to the repository. If you only want to print the
5558 list of files that this program would delete, use the --print
5558 list of files that this program would delete, use the --print
5559 option.
5559 option.
5560 """
5560 """
5561 opts = pycompat.byteskwargs(opts)
5561 opts = pycompat.byteskwargs(opts)
5562 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5562 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5563
5563
5564 act = not opts.get(b'print')
5564 act = not opts.get(b'print')
5565 eol = b'\n'
5565 eol = b'\n'
5566 if opts.get(b'print0'):
5566 if opts.get(b'print0'):
5567 eol = b'\0'
5567 eol = b'\0'
5568 act = False # --print0 implies --print
5568 act = False # --print0 implies --print
5569 if opts.get(b'all', False):
5569 if opts.get(b'all', False):
5570 ignored = True
5570 ignored = True
5571 unknown = True
5571 unknown = True
5572 else:
5572 else:
5573 ignored = opts.get(b'ignored', False)
5573 ignored = opts.get(b'ignored', False)
5574 unknown = not ignored
5574 unknown = not ignored
5575
5575
5576 removefiles = opts.get(b'files')
5576 removefiles = opts.get(b'files')
5577 removedirs = opts.get(b'dirs')
5577 removedirs = opts.get(b'dirs')
5578 confirm = opts.get(b'confirm')
5578 confirm = opts.get(b'confirm')
5579 if confirm is None:
5579 if confirm is None:
5580 try:
5580 try:
5581 extensions.find(b'purge')
5581 extensions.find(b'purge')
5582 confirm = False
5582 confirm = False
5583 except KeyError:
5583 except KeyError:
5584 confirm = True
5584 confirm = True
5585
5585
5586 if not removefiles and not removedirs:
5586 if not removefiles and not removedirs:
5587 removefiles = True
5587 removefiles = True
5588 removedirs = True
5588 removedirs = True
5589
5589
5590 match = scmutil.match(repo[None], dirs, opts)
5590 match = scmutil.match(repo[None], dirs, opts)
5591
5591
5592 paths = mergemod.purge(
5592 paths = mergemod.purge(
5593 repo,
5593 repo,
5594 match,
5594 match,
5595 unknown=unknown,
5595 unknown=unknown,
5596 ignored=ignored,
5596 ignored=ignored,
5597 removeemptydirs=removedirs,
5597 removeemptydirs=removedirs,
5598 removefiles=removefiles,
5598 removefiles=removefiles,
5599 abortonerror=opts.get(b'abort_on_err'),
5599 abortonerror=opts.get(b'abort_on_err'),
5600 noop=not act,
5600 noop=not act,
5601 confirm=confirm,
5601 confirm=confirm,
5602 )
5602 )
5603
5603
5604 for path in paths:
5604 for path in paths:
5605 if not act:
5605 if not act:
5606 ui.write(b'%s%s' % (path, eol))
5606 ui.write(b'%s%s' % (path, eol))
5607
5607
5608
5608
5609 @command(
5609 @command(
5610 b'push',
5610 b'push',
5611 [
5611 [
5612 (b'f', b'force', None, _(b'force push')),
5612 (b'f', b'force', None, _(b'force push')),
5613 (
5613 (
5614 b'r',
5614 b'r',
5615 b'rev',
5615 b'rev',
5616 [],
5616 [],
5617 _(b'a changeset intended to be included in the destination'),
5617 _(b'a changeset intended to be included in the destination'),
5618 _(b'REV'),
5618 _(b'REV'),
5619 ),
5619 ),
5620 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5620 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5621 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5621 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5622 (
5622 (
5623 b'b',
5623 b'b',
5624 b'branch',
5624 b'branch',
5625 [],
5625 [],
5626 _(b'a specific branch you would like to push'),
5626 _(b'a specific branch you would like to push'),
5627 _(b'BRANCH'),
5627 _(b'BRANCH'),
5628 ),
5628 ),
5629 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5629 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5630 (
5630 (
5631 b'',
5631 b'',
5632 b'pushvars',
5632 b'pushvars',
5633 [],
5633 [],
5634 _(b'variables that can be sent to server (ADVANCED)'),
5634 _(b'variables that can be sent to server (ADVANCED)'),
5635 ),
5635 ),
5636 (
5636 (
5637 b'',
5637 b'',
5638 b'publish',
5638 b'publish',
5639 False,
5639 False,
5640 _(b'push the changeset as public (EXPERIMENTAL)'),
5640 _(b'push the changeset as public (EXPERIMENTAL)'),
5641 ),
5641 ),
5642 ]
5642 ]
5643 + remoteopts,
5643 + remoteopts,
5644 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5644 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5645 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5645 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5646 helpbasic=True,
5646 helpbasic=True,
5647 )
5647 )
5648 def push(ui, repo, *dests, **opts):
5648 def push(ui, repo, *dests, **opts):
5649 """push changes to the specified destination
5649 """push changes to the specified destination
5650
5650
5651 Push changesets from the local repository to the specified
5651 Push changesets from the local repository to the specified
5652 destination.
5652 destination.
5653
5653
5654 This operation is symmetrical to pull: it is identical to a pull
5654 This operation is symmetrical to pull: it is identical to a pull
5655 in the destination repository from the current one.
5655 in the destination repository from the current one.
5656
5656
5657 By default, push will not allow creation of new heads at the
5657 By default, push will not allow creation of new heads at the
5658 destination, since multiple heads would make it unclear which head
5658 destination, since multiple heads would make it unclear which head
5659 to use. In this situation, it is recommended to pull and merge
5659 to use. In this situation, it is recommended to pull and merge
5660 before pushing.
5660 before pushing.
5661
5661
5662 Use --new-branch if you want to allow push to create a new named
5662 Use --new-branch if you want to allow push to create a new named
5663 branch that is not present at the destination. This allows you to
5663 branch that is not present at the destination. This allows you to
5664 only create a new branch without forcing other changes.
5664 only create a new branch without forcing other changes.
5665
5665
5666 .. note::
5666 .. note::
5667
5667
5668 Extra care should be taken with the -f/--force option,
5668 Extra care should be taken with the -f/--force option,
5669 which will push all new heads on all branches, an action which will
5669 which will push all new heads on all branches, an action which will
5670 almost always cause confusion for collaborators.
5670 almost always cause confusion for collaborators.
5671
5671
5672 If -r/--rev is used, the specified revision and all its ancestors
5672 If -r/--rev is used, the specified revision and all its ancestors
5673 will be pushed to the remote repository.
5673 will be pushed to the remote repository.
5674
5674
5675 If -B/--bookmark is used, the specified bookmarked revision, its
5675 If -B/--bookmark is used, the specified bookmarked revision, its
5676 ancestors, and the bookmark will be pushed to the remote
5676 ancestors, and the bookmark will be pushed to the remote
5677 repository. Specifying ``.`` is equivalent to specifying the active
5677 repository. Specifying ``.`` is equivalent to specifying the active
5678 bookmark's name. Use the --all-bookmarks option for pushing all
5678 bookmark's name. Use the --all-bookmarks option for pushing all
5679 current bookmarks.
5679 current bookmarks.
5680
5680
5681 Please see :hg:`help urls` for important details about ``ssh://``
5681 Please see :hg:`help urls` for important details about ``ssh://``
5682 URLs. If DESTINATION is omitted, a default path will be used.
5682 URLs. If DESTINATION is omitted, a default path will be used.
5683
5683
5684 When passed multiple destinations, push will process them one after the
5684 When passed multiple destinations, push will process them one after the
5685 other, but stop should an error occur.
5685 other, but stop should an error occur.
5686
5686
5687 .. container:: verbose
5687 .. container:: verbose
5688
5688
5689 The --pushvars option sends strings to the server that become
5689 The --pushvars option sends strings to the server that become
5690 environment variables prepended with ``HG_USERVAR_``. For example,
5690 environment variables prepended with ``HG_USERVAR_``. For example,
5691 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5691 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5692 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5692 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5693
5693
5694 pushvars can provide for user-overridable hooks as well as set debug
5694 pushvars can provide for user-overridable hooks as well as set debug
5695 levels. One example is having a hook that blocks commits containing
5695 levels. One example is having a hook that blocks commits containing
5696 conflict markers, but enables the user to override the hook if the file
5696 conflict markers, but enables the user to override the hook if the file
5697 is using conflict markers for testing purposes or the file format has
5697 is using conflict markers for testing purposes or the file format has
5698 strings that look like conflict markers.
5698 strings that look like conflict markers.
5699
5699
5700 By default, servers will ignore `--pushvars`. To enable it add the
5700 By default, servers will ignore `--pushvars`. To enable it add the
5701 following to your configuration file::
5701 following to your configuration file::
5702
5702
5703 [push]
5703 [push]
5704 pushvars.server = true
5704 pushvars.server = true
5705
5705
5706 Returns 0 if push was successful, 1 if nothing to push.
5706 Returns 0 if push was successful, 1 if nothing to push.
5707 """
5707 """
5708
5708
5709 opts = pycompat.byteskwargs(opts)
5709 opts = pycompat.byteskwargs(opts)
5710
5710
5711 if opts.get(b'all_bookmarks'):
5711 if opts.get(b'all_bookmarks'):
5712 cmdutil.check_incompatible_arguments(
5712 cmdutil.check_incompatible_arguments(
5713 opts,
5713 opts,
5714 b'all_bookmarks',
5714 b'all_bookmarks',
5715 [b'bookmark', b'rev'],
5715 [b'bookmark', b'rev'],
5716 )
5716 )
5717 opts[b'bookmark'] = list(repo._bookmarks)
5717 opts[b'bookmark'] = list(repo._bookmarks)
5718
5718
5719 if opts.get(b'bookmark'):
5719 if opts.get(b'bookmark'):
5720 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5720 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5721 for b in opts[b'bookmark']:
5721 for b in opts[b'bookmark']:
5722 # translate -B options to -r so changesets get pushed
5722 # translate -B options to -r so changesets get pushed
5723 b = repo._bookmarks.expandname(b)
5723 b = repo._bookmarks.expandname(b)
5724 if b in repo._bookmarks:
5724 if b in repo._bookmarks:
5725 opts.setdefault(b'rev', []).append(b)
5725 opts.setdefault(b'rev', []).append(b)
5726 else:
5726 else:
5727 # if we try to push a deleted bookmark, translate it to null
5727 # if we try to push a deleted bookmark, translate it to null
5728 # this lets simultaneous -r, -b options continue working
5728 # this lets simultaneous -r, -b options continue working
5729 opts.setdefault(b'rev', []).append(b"null")
5729 opts.setdefault(b'rev', []).append(b"null")
5730
5730
5731 some_pushed = False
5731 some_pushed = False
5732 result = 0
5732 result = 0
5733 for path in urlutil.get_push_paths(repo, ui, dests):
5733 for path in urlutil.get_push_paths(repo, ui, dests):
5734 dest = path.pushloc or path.loc
5734 dest = path.pushloc or path.loc
5735 branches = (path.branch, opts.get(b'branch') or [])
5735 branches = (path.branch, opts.get(b'branch') or [])
5736 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5736 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5737 revs, checkout = hg.addbranchrevs(
5737 revs, checkout = hg.addbranchrevs(
5738 repo, repo, branches, opts.get(b'rev')
5738 repo, repo, branches, opts.get(b'rev')
5739 )
5739 )
5740 other = hg.peer(repo, opts, dest)
5740 other = hg.peer(repo, opts, dest)
5741
5741
5742 try:
5742 try:
5743 if revs:
5743 if revs:
5744 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5744 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5745 if not revs:
5745 if not revs:
5746 raise error.InputError(
5746 raise error.InputError(
5747 _(b"specified revisions evaluate to an empty set"),
5747 _(b"specified revisions evaluate to an empty set"),
5748 hint=_(b"use different revision arguments"),
5748 hint=_(b"use different revision arguments"),
5749 )
5749 )
5750 elif path.pushrev:
5750 elif path.pushrev:
5751 # It doesn't make any sense to specify ancestor revisions. So limit
5751 # It doesn't make any sense to specify ancestor revisions. So limit
5752 # to DAG heads to make discovery simpler.
5752 # to DAG heads to make discovery simpler.
5753 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5753 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5754 revs = scmutil.revrange(repo, [expr])
5754 revs = scmutil.revrange(repo, [expr])
5755 revs = [repo[rev].node() for rev in revs]
5755 revs = [repo[rev].node() for rev in revs]
5756 if not revs:
5756 if not revs:
5757 raise error.InputError(
5757 raise error.InputError(
5758 _(
5758 _(
5759 b'default push revset for path evaluates to an empty set'
5759 b'default push revset for path evaluates to an empty set'
5760 )
5760 )
5761 )
5761 )
5762 elif ui.configbool(b'commands', b'push.require-revs'):
5762 elif ui.configbool(b'commands', b'push.require-revs'):
5763 raise error.InputError(
5763 raise error.InputError(
5764 _(b'no revisions specified to push'),
5764 _(b'no revisions specified to push'),
5765 hint=_(b'did you mean "hg push -r ."?'),
5765 hint=_(b'did you mean "hg push -r ."?'),
5766 )
5766 )
5767
5767
5768 repo._subtoppath = dest
5768 repo._subtoppath = dest
5769 try:
5769 try:
5770 # push subrepos depth-first for coherent ordering
5770 # push subrepos depth-first for coherent ordering
5771 c = repo[b'.']
5771 c = repo[b'.']
5772 subs = c.substate # only repos that are committed
5772 subs = c.substate # only repos that are committed
5773 for s in sorted(subs):
5773 for s in sorted(subs):
5774 sub_result = c.sub(s).push(opts)
5774 sub_result = c.sub(s).push(opts)
5775 if sub_result == 0:
5775 if sub_result == 0:
5776 return 1
5776 return 1
5777 finally:
5777 finally:
5778 del repo._subtoppath
5778 del repo._subtoppath
5779
5779
5780 opargs = dict(
5780 opargs = dict(
5781 opts.get(b'opargs', {})
5781 opts.get(b'opargs', {})
5782 ) # copy opargs since we may mutate it
5782 ) # copy opargs since we may mutate it
5783 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5783 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5784
5784
5785 pushop = exchange.push(
5785 pushop = exchange.push(
5786 repo,
5786 repo,
5787 other,
5787 other,
5788 opts.get(b'force'),
5788 opts.get(b'force'),
5789 revs=revs,
5789 revs=revs,
5790 newbranch=opts.get(b'new_branch'),
5790 newbranch=opts.get(b'new_branch'),
5791 bookmarks=opts.get(b'bookmark', ()),
5791 bookmarks=opts.get(b'bookmark', ()),
5792 publish=opts.get(b'publish'),
5792 publish=opts.get(b'publish'),
5793 opargs=opargs,
5793 opargs=opargs,
5794 )
5794 )
5795
5795
5796 if pushop.cgresult == 0:
5796 if pushop.cgresult == 0:
5797 result = 1
5797 result = 1
5798 elif pushop.cgresult is not None:
5798 elif pushop.cgresult is not None:
5799 some_pushed = True
5799 some_pushed = True
5800
5800
5801 if pushop.bkresult is not None:
5801 if pushop.bkresult is not None:
5802 if pushop.bkresult == 2:
5802 if pushop.bkresult == 2:
5803 result = 2
5803 result = 2
5804 elif not result and pushop.bkresult:
5804 elif not result and pushop.bkresult:
5805 result = 2
5805 result = 2
5806
5806
5807 if result:
5807 if result:
5808 break
5808 break
5809
5809
5810 finally:
5810 finally:
5811 other.close()
5811 other.close()
5812 if result == 0 and not some_pushed:
5812 if result == 0 and not some_pushed:
5813 result = 1
5813 result = 1
5814 return result
5814 return result
5815
5815
5816
5816
5817 @command(
5817 @command(
5818 b'recover',
5818 b'recover',
5819 [
5819 [
5820 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5820 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5821 ],
5821 ],
5822 helpcategory=command.CATEGORY_MAINTENANCE,
5822 helpcategory=command.CATEGORY_MAINTENANCE,
5823 )
5823 )
5824 def recover(ui, repo, **opts):
5824 def recover(ui, repo, **opts):
5825 """roll back an interrupted transaction
5825 """roll back an interrupted transaction
5826
5826
5827 Recover from an interrupted commit or pull.
5827 Recover from an interrupted commit or pull.
5828
5828
5829 This command tries to fix the repository status after an
5829 This command tries to fix the repository status after an
5830 interrupted operation. It should only be necessary when Mercurial
5830 interrupted operation. It should only be necessary when Mercurial
5831 suggests it.
5831 suggests it.
5832
5832
5833 Returns 0 if successful, 1 if nothing to recover or verify fails.
5833 Returns 0 if successful, 1 if nothing to recover or verify fails.
5834 """
5834 """
5835 ret = repo.recover()
5835 ret = repo.recover()
5836 if ret:
5836 if ret:
5837 if opts['verify']:
5837 if opts['verify']:
5838 return hg.verify(repo)
5838 return hg.verify(repo)
5839 else:
5839 else:
5840 msg = _(
5840 msg = _(
5841 b"(verify step skipped, run `hg verify` to check your "
5841 b"(verify step skipped, run `hg verify` to check your "
5842 b"repository content)\n"
5842 b"repository content)\n"
5843 )
5843 )
5844 ui.warn(msg)
5844 ui.warn(msg)
5845 return 0
5845 return 0
5846 return 1
5846 return 1
5847
5847
5848
5848
5849 @command(
5849 @command(
5850 b'remove|rm',
5850 b'remove|rm',
5851 [
5851 [
5852 (b'A', b'after', None, _(b'record delete for missing files')),
5852 (b'A', b'after', None, _(b'record delete for missing files')),
5853 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5853 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5854 ]
5854 ]
5855 + subrepoopts
5855 + subrepoopts
5856 + walkopts
5856 + walkopts
5857 + dryrunopts,
5857 + dryrunopts,
5858 _(b'[OPTION]... FILE...'),
5858 _(b'[OPTION]... FILE...'),
5859 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5859 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5860 helpbasic=True,
5860 helpbasic=True,
5861 inferrepo=True,
5861 inferrepo=True,
5862 )
5862 )
5863 def remove(ui, repo, *pats, **opts):
5863 def remove(ui, repo, *pats, **opts):
5864 """remove the specified files on the next commit
5864 """remove the specified files on the next commit
5865
5865
5866 Schedule the indicated files for removal from the current branch.
5866 Schedule the indicated files for removal from the current branch.
5867
5867
5868 This command schedules the files to be removed at the next commit.
5868 This command schedules the files to be removed at the next commit.
5869 To undo a remove before that, see :hg:`revert`. To undo added
5869 To undo a remove before that, see :hg:`revert`. To undo added
5870 files, see :hg:`forget`.
5870 files, see :hg:`forget`.
5871
5871
5872 .. container:: verbose
5872 .. container:: verbose
5873
5873
5874 -A/--after can be used to remove only files that have already
5874 -A/--after can be used to remove only files that have already
5875 been deleted, -f/--force can be used to force deletion, and -Af
5875 been deleted, -f/--force can be used to force deletion, and -Af
5876 can be used to remove files from the next revision without
5876 can be used to remove files from the next revision without
5877 deleting them from the working directory.
5877 deleting them from the working directory.
5878
5878
5879 The following table details the behavior of remove for different
5879 The following table details the behavior of remove for different
5880 file states (columns) and option combinations (rows). The file
5880 file states (columns) and option combinations (rows). The file
5881 states are Added [A], Clean [C], Modified [M] and Missing [!]
5881 states are Added [A], Clean [C], Modified [M] and Missing [!]
5882 (as reported by :hg:`status`). The actions are Warn, Remove
5882 (as reported by :hg:`status`). The actions are Warn, Remove
5883 (from branch) and Delete (from disk):
5883 (from branch) and Delete (from disk):
5884
5884
5885 ========= == == == ==
5885 ========= == == == ==
5886 opt/state A C M !
5886 opt/state A C M !
5887 ========= == == == ==
5887 ========= == == == ==
5888 none W RD W R
5888 none W RD W R
5889 -f R RD RD R
5889 -f R RD RD R
5890 -A W W W R
5890 -A W W W R
5891 -Af R R R R
5891 -Af R R R R
5892 ========= == == == ==
5892 ========= == == == ==
5893
5893
5894 .. note::
5894 .. note::
5895
5895
5896 :hg:`remove` never deletes files in Added [A] state from the
5896 :hg:`remove` never deletes files in Added [A] state from the
5897 working directory, not even if ``--force`` is specified.
5897 working directory, not even if ``--force`` is specified.
5898
5898
5899 Returns 0 on success, 1 if any warnings encountered.
5899 Returns 0 on success, 1 if any warnings encountered.
5900 """
5900 """
5901
5901
5902 opts = pycompat.byteskwargs(opts)
5902 opts = pycompat.byteskwargs(opts)
5903 after, force = opts.get(b'after'), opts.get(b'force')
5903 after, force = opts.get(b'after'), opts.get(b'force')
5904 dryrun = opts.get(b'dry_run')
5904 dryrun = opts.get(b'dry_run')
5905 if not pats and not after:
5905 if not pats and not after:
5906 raise error.InputError(_(b'no files specified'))
5906 raise error.InputError(_(b'no files specified'))
5907
5907
5908 m = scmutil.match(repo[None], pats, opts)
5908 m = scmutil.match(repo[None], pats, opts)
5909 subrepos = opts.get(b'subrepos')
5909 subrepos = opts.get(b'subrepos')
5910 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5910 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5911 return cmdutil.remove(
5911 return cmdutil.remove(
5912 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5912 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5913 )
5913 )
5914
5914
5915
5915
5916 @command(
5916 @command(
5917 b'rename|move|mv',
5917 b'rename|move|mv',
5918 [
5918 [
5919 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5919 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5920 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5920 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5921 (
5921 (
5922 b'',
5922 b'',
5923 b'at-rev',
5923 b'at-rev',
5924 b'',
5924 b'',
5925 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5925 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5926 _(b'REV'),
5926 _(b'REV'),
5927 ),
5927 ),
5928 (
5928 (
5929 b'f',
5929 b'f',
5930 b'force',
5930 b'force',
5931 None,
5931 None,
5932 _(b'forcibly move over an existing managed file'),
5932 _(b'forcibly move over an existing managed file'),
5933 ),
5933 ),
5934 ]
5934 ]
5935 + walkopts
5935 + walkopts
5936 + dryrunopts,
5936 + dryrunopts,
5937 _(b'[OPTION]... SOURCE... DEST'),
5937 _(b'[OPTION]... SOURCE... DEST'),
5938 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5938 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5939 )
5939 )
5940 def rename(ui, repo, *pats, **opts):
5940 def rename(ui, repo, *pats, **opts):
5941 """rename files; equivalent of copy + remove
5941 """rename files; equivalent of copy + remove
5942
5942
5943 Mark dest as copies of sources; mark sources for deletion. If dest
5943 Mark dest as copies of sources; mark sources for deletion. If dest
5944 is a directory, copies are put in that directory. If dest is a
5944 is a directory, copies are put in that directory. If dest is a
5945 file, there can only be one source.
5945 file, there can only be one source.
5946
5946
5947 By default, this command copies the contents of files as they
5947 By default, this command copies the contents of files as they
5948 exist in the working directory. If invoked with -A/--after, the
5948 exist in the working directory. If invoked with -A/--after, the
5949 operation is recorded, but no copying is performed.
5949 operation is recorded, but no copying is performed.
5950
5950
5951 To undo marking a destination file as renamed, use --forget. With that
5951 To undo marking a destination file as renamed, use --forget. With that
5952 option, all given (positional) arguments are unmarked as renames. The
5952 option, all given (positional) arguments are unmarked as renames. The
5953 destination file(s) will be left in place (still tracked). The source
5953 destination file(s) will be left in place (still tracked). The source
5954 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5954 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5955 the same way as :hg:`copy --forget`.
5955 the same way as :hg:`copy --forget`.
5956
5956
5957 This command takes effect with the next commit by default.
5957 This command takes effect with the next commit by default.
5958
5958
5959 Returns 0 on success, 1 if errors are encountered.
5959 Returns 0 on success, 1 if errors are encountered.
5960 """
5960 """
5961 opts = pycompat.byteskwargs(opts)
5961 opts = pycompat.byteskwargs(opts)
5962 with repo.wlock():
5962 with repo.wlock():
5963 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5963 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5964
5964
5965
5965
5966 @command(
5966 @command(
5967 b'resolve',
5967 b'resolve',
5968 [
5968 [
5969 (b'a', b'all', None, _(b'select all unresolved files')),
5969 (b'a', b'all', None, _(b'select all unresolved files')),
5970 (b'l', b'list', None, _(b'list state of files needing merge')),
5970 (b'l', b'list', None, _(b'list state of files needing merge')),
5971 (b'm', b'mark', None, _(b'mark files as resolved')),
5971 (b'm', b'mark', None, _(b'mark files as resolved')),
5972 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5972 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5973 (b'n', b'no-status', None, _(b'hide status prefix')),
5973 (b'n', b'no-status', None, _(b'hide status prefix')),
5974 (b'', b're-merge', None, _(b're-merge files')),
5974 (b'', b're-merge', None, _(b're-merge files')),
5975 ]
5975 ]
5976 + mergetoolopts
5976 + mergetoolopts
5977 + walkopts
5977 + walkopts
5978 + formatteropts,
5978 + formatteropts,
5979 _(b'[OPTION]... [FILE]...'),
5979 _(b'[OPTION]... [FILE]...'),
5980 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5980 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5981 inferrepo=True,
5981 inferrepo=True,
5982 )
5982 )
5983 def resolve(ui, repo, *pats, **opts):
5983 def resolve(ui, repo, *pats, **opts):
5984 """redo merges or set/view the merge status of files
5984 """redo merges or set/view the merge status of files
5985
5985
5986 Merges with unresolved conflicts are often the result of
5986 Merges with unresolved conflicts are often the result of
5987 non-interactive merging using the ``internal:merge`` configuration
5987 non-interactive merging using the ``internal:merge`` configuration
5988 setting, or a command-line merge tool like ``diff3``. The resolve
5988 setting, or a command-line merge tool like ``diff3``. The resolve
5989 command is used to manage the files involved in a merge, after
5989 command is used to manage the files involved in a merge, after
5990 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5990 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5991 working directory must have two parents). See :hg:`help
5991 working directory must have two parents). See :hg:`help
5992 merge-tools` for information on configuring merge tools.
5992 merge-tools` for information on configuring merge tools.
5993
5993
5994 The resolve command can be used in the following ways:
5994 The resolve command can be used in the following ways:
5995
5995
5996 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5996 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5997 the specified files, discarding any previous merge attempts. Re-merging
5997 the specified files, discarding any previous merge attempts. Re-merging
5998 is not performed for files already marked as resolved. Use ``--all/-a``
5998 is not performed for files already marked as resolved. Use ``--all/-a``
5999 to select all unresolved files. ``--tool`` can be used to specify
5999 to select all unresolved files. ``--tool`` can be used to specify
6000 the merge tool used for the given files. It overrides the HGMERGE
6000 the merge tool used for the given files. It overrides the HGMERGE
6001 environment variable and your configuration files. Previous file
6001 environment variable and your configuration files. Previous file
6002 contents are saved with a ``.orig`` suffix.
6002 contents are saved with a ``.orig`` suffix.
6003
6003
6004 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6004 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6005 (e.g. after having manually fixed-up the files). The default is
6005 (e.g. after having manually fixed-up the files). The default is
6006 to mark all unresolved files.
6006 to mark all unresolved files.
6007
6007
6008 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6008 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6009 default is to mark all resolved files.
6009 default is to mark all resolved files.
6010
6010
6011 - :hg:`resolve -l`: list files which had or still have conflicts.
6011 - :hg:`resolve -l`: list files which had or still have conflicts.
6012 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6012 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6013 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6013 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6014 the list. See :hg:`help filesets` for details.
6014 the list. See :hg:`help filesets` for details.
6015
6015
6016 .. note::
6016 .. note::
6017
6017
6018 Mercurial will not let you commit files with unresolved merge
6018 Mercurial will not let you commit files with unresolved merge
6019 conflicts. You must use :hg:`resolve -m ...` before you can
6019 conflicts. You must use :hg:`resolve -m ...` before you can
6020 commit after a conflicting merge.
6020 commit after a conflicting merge.
6021
6021
6022 .. container:: verbose
6022 .. container:: verbose
6023
6023
6024 Template:
6024 Template:
6025
6025
6026 The following keywords are supported in addition to the common template
6026 The following keywords are supported in addition to the common template
6027 keywords and functions. See also :hg:`help templates`.
6027 keywords and functions. See also :hg:`help templates`.
6028
6028
6029 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6029 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6030 :path: String. Repository-absolute path of the file.
6030 :path: String. Repository-absolute path of the file.
6031
6031
6032 Returns 0 on success, 1 if any files fail a resolve attempt.
6032 Returns 0 on success, 1 if any files fail a resolve attempt.
6033 """
6033 """
6034
6034
6035 opts = pycompat.byteskwargs(opts)
6035 opts = pycompat.byteskwargs(opts)
6036 confirm = ui.configbool(b'commands', b'resolve.confirm')
6036 confirm = ui.configbool(b'commands', b'resolve.confirm')
6037 flaglist = b'all mark unmark list no_status re_merge'.split()
6037 flaglist = b'all mark unmark list no_status re_merge'.split()
6038 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6038 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6039
6039
6040 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6040 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6041 if actioncount > 1:
6041 if actioncount > 1:
6042 raise error.InputError(_(b"too many actions specified"))
6042 raise error.InputError(_(b"too many actions specified"))
6043 elif actioncount == 0 and ui.configbool(
6043 elif actioncount == 0 and ui.configbool(
6044 b'commands', b'resolve.explicit-re-merge'
6044 b'commands', b'resolve.explicit-re-merge'
6045 ):
6045 ):
6046 hint = _(b'use --mark, --unmark, --list or --re-merge')
6046 hint = _(b'use --mark, --unmark, --list or --re-merge')
6047 raise error.InputError(_(b'no action specified'), hint=hint)
6047 raise error.InputError(_(b'no action specified'), hint=hint)
6048 if pats and all:
6048 if pats and all:
6049 raise error.InputError(_(b"can't specify --all and patterns"))
6049 raise error.InputError(_(b"can't specify --all and patterns"))
6050 if not (all or pats or show or mark or unmark):
6050 if not (all or pats or show or mark or unmark):
6051 raise error.InputError(
6051 raise error.InputError(
6052 _(b'no files or directories specified'),
6052 _(b'no files or directories specified'),
6053 hint=b'use --all to re-merge all unresolved files',
6053 hint=b'use --all to re-merge all unresolved files',
6054 )
6054 )
6055
6055
6056 if confirm:
6056 if confirm:
6057 if all:
6057 if all:
6058 if ui.promptchoice(
6058 if ui.promptchoice(
6059 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6059 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6060 ):
6060 ):
6061 raise error.CanceledError(_(b'user quit'))
6061 raise error.CanceledError(_(b'user quit'))
6062 if mark and not pats:
6062 if mark and not pats:
6063 if ui.promptchoice(
6063 if ui.promptchoice(
6064 _(
6064 _(
6065 b'mark all unresolved files as resolved (yn)?'
6065 b'mark all unresolved files as resolved (yn)?'
6066 b'$$ &Yes $$ &No'
6066 b'$$ &Yes $$ &No'
6067 )
6067 )
6068 ):
6068 ):
6069 raise error.CanceledError(_(b'user quit'))
6069 raise error.CanceledError(_(b'user quit'))
6070 if unmark and not pats:
6070 if unmark and not pats:
6071 if ui.promptchoice(
6071 if ui.promptchoice(
6072 _(
6072 _(
6073 b'mark all resolved files as unresolved (yn)?'
6073 b'mark all resolved files as unresolved (yn)?'
6074 b'$$ &Yes $$ &No'
6074 b'$$ &Yes $$ &No'
6075 )
6075 )
6076 ):
6076 ):
6077 raise error.CanceledError(_(b'user quit'))
6077 raise error.CanceledError(_(b'user quit'))
6078
6078
6079 uipathfn = scmutil.getuipathfn(repo)
6079 uipathfn = scmutil.getuipathfn(repo)
6080
6080
6081 if show:
6081 if show:
6082 ui.pager(b'resolve')
6082 ui.pager(b'resolve')
6083 fm = ui.formatter(b'resolve', opts)
6083 fm = ui.formatter(b'resolve', opts)
6084 ms = mergestatemod.mergestate.read(repo)
6084 ms = mergestatemod.mergestate.read(repo)
6085 wctx = repo[None]
6085 wctx = repo[None]
6086 m = scmutil.match(wctx, pats, opts)
6086 m = scmutil.match(wctx, pats, opts)
6087
6087
6088 # Labels and keys based on merge state. Unresolved path conflicts show
6088 # Labels and keys based on merge state. Unresolved path conflicts show
6089 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6089 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6090 # resolved conflicts.
6090 # resolved conflicts.
6091 mergestateinfo = {
6091 mergestateinfo = {
6092 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6092 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6093 b'resolve.unresolved',
6093 b'resolve.unresolved',
6094 b'U',
6094 b'U',
6095 ),
6095 ),
6096 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6096 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6097 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6097 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6098 b'resolve.unresolved',
6098 b'resolve.unresolved',
6099 b'P',
6099 b'P',
6100 ),
6100 ),
6101 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6101 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6102 b'resolve.resolved',
6102 b'resolve.resolved',
6103 b'R',
6103 b'R',
6104 ),
6104 ),
6105 }
6105 }
6106
6106
6107 for f in ms:
6107 for f in ms:
6108 if not m(f):
6108 if not m(f):
6109 continue
6109 continue
6110
6110
6111 label, key = mergestateinfo[ms[f]]
6111 label, key = mergestateinfo[ms[f]]
6112 fm.startitem()
6112 fm.startitem()
6113 fm.context(ctx=wctx)
6113 fm.context(ctx=wctx)
6114 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6114 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6115 fm.data(path=f)
6115 fm.data(path=f)
6116 fm.plain(b'%s\n' % uipathfn(f), label=label)
6116 fm.plain(b'%s\n' % uipathfn(f), label=label)
6117 fm.end()
6117 fm.end()
6118 return 0
6118 return 0
6119
6119
6120 with repo.wlock():
6120 with repo.wlock():
6121 ms = mergestatemod.mergestate.read(repo)
6121 ms = mergestatemod.mergestate.read(repo)
6122
6122
6123 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6123 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6124 raise error.StateError(
6124 raise error.StateError(
6125 _(b'resolve command not applicable when not merging')
6125 _(b'resolve command not applicable when not merging')
6126 )
6126 )
6127
6127
6128 wctx = repo[None]
6128 wctx = repo[None]
6129 m = scmutil.match(wctx, pats, opts)
6129 m = scmutil.match(wctx, pats, opts)
6130 ret = 0
6130 ret = 0
6131 didwork = False
6131 didwork = False
6132
6132
6133 tocomplete = []
6134 hasconflictmarkers = []
6133 hasconflictmarkers = []
6135 if mark:
6134 if mark:
6136 markcheck = ui.config(b'commands', b'resolve.mark-check')
6135 markcheck = ui.config(b'commands', b'resolve.mark-check')
6137 if markcheck not in [b'warn', b'abort']:
6136 if markcheck not in [b'warn', b'abort']:
6138 # Treat all invalid / unrecognized values as 'none'.
6137 # Treat all invalid / unrecognized values as 'none'.
6139 markcheck = False
6138 markcheck = False
6140 for f in ms:
6139 for f in ms:
6141 if not m(f):
6140 if not m(f):
6142 continue
6141 continue
6143
6142
6144 didwork = True
6143 didwork = True
6145
6144
6146 # path conflicts must be resolved manually
6145 # path conflicts must be resolved manually
6147 if ms[f] in (
6146 if ms[f] in (
6148 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6147 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6149 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6148 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6150 ):
6149 ):
6151 if mark:
6150 if mark:
6152 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6151 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6153 elif unmark:
6152 elif unmark:
6154 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6153 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6155 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6154 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6156 ui.warn(
6155 ui.warn(
6157 _(b'%s: path conflict must be resolved manually\n')
6156 _(b'%s: path conflict must be resolved manually\n')
6158 % uipathfn(f)
6157 % uipathfn(f)
6159 )
6158 )
6160 continue
6159 continue
6161
6160
6162 if mark:
6161 if mark:
6163 if markcheck:
6162 if markcheck:
6164 fdata = repo.wvfs.tryread(f)
6163 fdata = repo.wvfs.tryread(f)
6165 if (
6164 if (
6166 filemerge.hasconflictmarkers(fdata)
6165 filemerge.hasconflictmarkers(fdata)
6167 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6166 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6168 ):
6167 ):
6169 hasconflictmarkers.append(f)
6168 hasconflictmarkers.append(f)
6170 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6169 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6171 elif unmark:
6170 elif unmark:
6172 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6171 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6173 else:
6172 else:
6174 # backup pre-resolve (merge uses .orig for its own purposes)
6173 # backup pre-resolve (merge uses .orig for its own purposes)
6175 a = repo.wjoin(f)
6174 a = repo.wjoin(f)
6176 try:
6175 try:
6177 util.copyfile(a, a + b".resolve")
6176 util.copyfile(a, a + b".resolve")
6178 except (IOError, OSError) as inst:
6177 except (IOError, OSError) as inst:
6179 if inst.errno != errno.ENOENT:
6178 if inst.errno != errno.ENOENT:
6180 raise
6179 raise
6181
6180
6182 try:
6181 try:
6183 # preresolve file
6182 # preresolve file
6184 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6183 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6185 with ui.configoverride(overrides, b'resolve'):
6184 with ui.configoverride(overrides, b'resolve'):
6186 complete, r = ms.preresolve(f, wctx)
6185 r = ms.resolve(f, wctx)
6187 if not complete:
6186 if r:
6188 tocomplete.append(f)
6189 elif r:
6190 ret = 1
6187 ret = 1
6191 finally:
6188 finally:
6192 ms.commit()
6189 ms.commit()
6193
6190
6194 # replace filemerge's .orig file with our resolve file, but only
6191 # replace filemerge's .orig file with our resolve file
6195 # for merges that are complete
6196 if complete:
6197 try:
6192 try:
6198 util.rename(
6193 util.rename(
6199 a + b".resolve", scmutil.backuppath(ui, repo, f)
6194 a + b".resolve", scmutil.backuppath(ui, repo, f)
6200 )
6195 )
6201 except OSError as inst:
6196 except OSError as inst:
6202 if inst.errno != errno.ENOENT:
6197 if inst.errno != errno.ENOENT:
6203 raise
6198 raise
6204
6199
6205 if hasconflictmarkers:
6200 if hasconflictmarkers:
6206 ui.warn(
6201 ui.warn(
6207 _(
6202 _(
6208 b'warning: the following files still have conflict '
6203 b'warning: the following files still have conflict '
6209 b'markers:\n'
6204 b'markers:\n'
6210 )
6205 )
6211 + b''.join(
6206 + b''.join(
6212 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6207 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6213 )
6208 )
6214 )
6209 )
6215 if markcheck == b'abort' and not all and not pats:
6210 if markcheck == b'abort' and not all and not pats:
6216 raise error.StateError(
6211 raise error.StateError(
6217 _(b'conflict markers detected'),
6212 _(b'conflict markers detected'),
6218 hint=_(b'use --all to mark anyway'),
6213 hint=_(b'use --all to mark anyway'),
6219 )
6214 )
6220
6215
6221 for f in tocomplete:
6222 try:
6223 # resolve file
6224 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6225 with ui.configoverride(overrides, b'resolve'):
6226 r = ms.resolve(f, wctx)
6227 if r:
6228 ret = 1
6229 finally:
6230 ms.commit()
6231
6232 # replace filemerge's .orig file with our resolve file
6233 a = repo.wjoin(f)
6234 try:
6235 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6236 except OSError as inst:
6237 if inst.errno != errno.ENOENT:
6238 raise
6239
6240 ms.commit()
6216 ms.commit()
6241 branchmerge = repo.dirstate.p2() != repo.nullid
6217 branchmerge = repo.dirstate.p2() != repo.nullid
6242 # resolve is not doing a parent change here, however, `record updates`
6218 # resolve is not doing a parent change here, however, `record updates`
6243 # will call some dirstate API that at intended for parent changes call.
6219 # will call some dirstate API that at intended for parent changes call.
6244 # Ideally we would not need this and could implement a lighter version
6220 # Ideally we would not need this and could implement a lighter version
6245 # of the recordupdateslogic that will not have to deal with the part
6221 # of the recordupdateslogic that will not have to deal with the part
6246 # related to parent changes. However this would requires that:
6222 # related to parent changes. However this would requires that:
6247 # - we are sure we passed around enough information at update/merge
6223 # - we are sure we passed around enough information at update/merge
6248 # time to no longer needs it at `hg resolve time`
6224 # time to no longer needs it at `hg resolve time`
6249 # - we are sure we store that information well enough to be able to reuse it
6225 # - we are sure we store that information well enough to be able to reuse it
6250 # - we are the necessary logic to reuse it right.
6226 # - we are the necessary logic to reuse it right.
6251 #
6227 #
6252 # All this should eventually happens, but in the mean time, we use this
6228 # All this should eventually happens, but in the mean time, we use this
6253 # context manager slightly out of the context it should be.
6229 # context manager slightly out of the context it should be.
6254 with repo.dirstate.parentchange():
6230 with repo.dirstate.parentchange():
6255 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6231 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6256
6232
6257 if not didwork and pats:
6233 if not didwork and pats:
6258 hint = None
6234 hint = None
6259 if not any([p for p in pats if p.find(b':') >= 0]):
6235 if not any([p for p in pats if p.find(b':') >= 0]):
6260 pats = [b'path:%s' % p for p in pats]
6236 pats = [b'path:%s' % p for p in pats]
6261 m = scmutil.match(wctx, pats, opts)
6237 m = scmutil.match(wctx, pats, opts)
6262 for f in ms:
6238 for f in ms:
6263 if not m(f):
6239 if not m(f):
6264 continue
6240 continue
6265
6241
6266 def flag(o):
6242 def flag(o):
6267 if o == b're_merge':
6243 if o == b're_merge':
6268 return b'--re-merge '
6244 return b'--re-merge '
6269 return b'-%s ' % o[0:1]
6245 return b'-%s ' % o[0:1]
6270
6246
6271 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6247 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6272 hint = _(b"(try: hg resolve %s%s)\n") % (
6248 hint = _(b"(try: hg resolve %s%s)\n") % (
6273 flags,
6249 flags,
6274 b' '.join(pats),
6250 b' '.join(pats),
6275 )
6251 )
6276 break
6252 break
6277 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6253 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6278 if hint:
6254 if hint:
6279 ui.warn(hint)
6255 ui.warn(hint)
6280
6256
6281 unresolvedf = ms.unresolvedcount()
6257 unresolvedf = ms.unresolvedcount()
6282 if not unresolvedf:
6258 if not unresolvedf:
6283 ui.status(_(b'(no more unresolved files)\n'))
6259 ui.status(_(b'(no more unresolved files)\n'))
6284 cmdutil.checkafterresolved(repo)
6260 cmdutil.checkafterresolved(repo)
6285
6261
6286 return ret
6262 return ret
6287
6263
6288
6264
6289 @command(
6265 @command(
6290 b'revert',
6266 b'revert',
6291 [
6267 [
6292 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6268 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6293 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6269 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6294 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6270 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6295 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6271 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6296 (b'i', b'interactive', None, _(b'interactively select the changes')),
6272 (b'i', b'interactive', None, _(b'interactively select the changes')),
6297 ]
6273 ]
6298 + walkopts
6274 + walkopts
6299 + dryrunopts,
6275 + dryrunopts,
6300 _(b'[OPTION]... [-r REV] [NAME]...'),
6276 _(b'[OPTION]... [-r REV] [NAME]...'),
6301 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6277 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6302 )
6278 )
6303 def revert(ui, repo, *pats, **opts):
6279 def revert(ui, repo, *pats, **opts):
6304 """restore files to their checkout state
6280 """restore files to their checkout state
6305
6281
6306 .. note::
6282 .. note::
6307
6283
6308 To check out earlier revisions, you should use :hg:`update REV`.
6284 To check out earlier revisions, you should use :hg:`update REV`.
6309 To cancel an uncommitted merge (and lose your changes),
6285 To cancel an uncommitted merge (and lose your changes),
6310 use :hg:`merge --abort`.
6286 use :hg:`merge --abort`.
6311
6287
6312 With no revision specified, revert the specified files or directories
6288 With no revision specified, revert the specified files or directories
6313 to the contents they had in the parent of the working directory.
6289 to the contents they had in the parent of the working directory.
6314 This restores the contents of files to an unmodified
6290 This restores the contents of files to an unmodified
6315 state and unschedules adds, removes, copies, and renames. If the
6291 state and unschedules adds, removes, copies, and renames. If the
6316 working directory has two parents, you must explicitly specify a
6292 working directory has two parents, you must explicitly specify a
6317 revision.
6293 revision.
6318
6294
6319 Using the -r/--rev or -d/--date options, revert the given files or
6295 Using the -r/--rev or -d/--date options, revert the given files or
6320 directories to their states as of a specific revision. Because
6296 directories to their states as of a specific revision. Because
6321 revert does not change the working directory parents, this will
6297 revert does not change the working directory parents, this will
6322 cause these files to appear modified. This can be helpful to "back
6298 cause these files to appear modified. This can be helpful to "back
6323 out" some or all of an earlier change. See :hg:`backout` for a
6299 out" some or all of an earlier change. See :hg:`backout` for a
6324 related method.
6300 related method.
6325
6301
6326 Modified files are saved with a .orig suffix before reverting.
6302 Modified files are saved with a .orig suffix before reverting.
6327 To disable these backups, use --no-backup. It is possible to store
6303 To disable these backups, use --no-backup. It is possible to store
6328 the backup files in a custom directory relative to the root of the
6304 the backup files in a custom directory relative to the root of the
6329 repository by setting the ``ui.origbackuppath`` configuration
6305 repository by setting the ``ui.origbackuppath`` configuration
6330 option.
6306 option.
6331
6307
6332 See :hg:`help dates` for a list of formats valid for -d/--date.
6308 See :hg:`help dates` for a list of formats valid for -d/--date.
6333
6309
6334 See :hg:`help backout` for a way to reverse the effect of an
6310 See :hg:`help backout` for a way to reverse the effect of an
6335 earlier changeset.
6311 earlier changeset.
6336
6312
6337 Returns 0 on success.
6313 Returns 0 on success.
6338 """
6314 """
6339
6315
6340 opts = pycompat.byteskwargs(opts)
6316 opts = pycompat.byteskwargs(opts)
6341 if opts.get(b"date"):
6317 if opts.get(b"date"):
6342 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6318 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6343 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6319 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6344
6320
6345 parent, p2 = repo.dirstate.parents()
6321 parent, p2 = repo.dirstate.parents()
6346 if not opts.get(b'rev') and p2 != repo.nullid:
6322 if not opts.get(b'rev') and p2 != repo.nullid:
6347 # revert after merge is a trap for new users (issue2915)
6323 # revert after merge is a trap for new users (issue2915)
6348 raise error.InputError(
6324 raise error.InputError(
6349 _(b'uncommitted merge with no revision specified'),
6325 _(b'uncommitted merge with no revision specified'),
6350 hint=_(b"use 'hg update' or see 'hg help revert'"),
6326 hint=_(b"use 'hg update' or see 'hg help revert'"),
6351 )
6327 )
6352
6328
6353 rev = opts.get(b'rev')
6329 rev = opts.get(b'rev')
6354 if rev:
6330 if rev:
6355 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6331 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6356 ctx = logcmdutil.revsingle(repo, rev)
6332 ctx = logcmdutil.revsingle(repo, rev)
6357
6333
6358 if not (
6334 if not (
6359 pats
6335 pats
6360 or opts.get(b'include')
6336 or opts.get(b'include')
6361 or opts.get(b'exclude')
6337 or opts.get(b'exclude')
6362 or opts.get(b'all')
6338 or opts.get(b'all')
6363 or opts.get(b'interactive')
6339 or opts.get(b'interactive')
6364 ):
6340 ):
6365 msg = _(b"no files or directories specified")
6341 msg = _(b"no files or directories specified")
6366 if p2 != repo.nullid:
6342 if p2 != repo.nullid:
6367 hint = _(
6343 hint = _(
6368 b"uncommitted merge, use --all to discard all changes,"
6344 b"uncommitted merge, use --all to discard all changes,"
6369 b" or 'hg update -C .' to abort the merge"
6345 b" or 'hg update -C .' to abort the merge"
6370 )
6346 )
6371 raise error.InputError(msg, hint=hint)
6347 raise error.InputError(msg, hint=hint)
6372 dirty = any(repo.status())
6348 dirty = any(repo.status())
6373 node = ctx.node()
6349 node = ctx.node()
6374 if node != parent:
6350 if node != parent:
6375 if dirty:
6351 if dirty:
6376 hint = (
6352 hint = (
6377 _(
6353 _(
6378 b"uncommitted changes, use --all to discard all"
6354 b"uncommitted changes, use --all to discard all"
6379 b" changes, or 'hg update %d' to update"
6355 b" changes, or 'hg update %d' to update"
6380 )
6356 )
6381 % ctx.rev()
6357 % ctx.rev()
6382 )
6358 )
6383 else:
6359 else:
6384 hint = (
6360 hint = (
6385 _(
6361 _(
6386 b"use --all to revert all files,"
6362 b"use --all to revert all files,"
6387 b" or 'hg update %d' to update"
6363 b" or 'hg update %d' to update"
6388 )
6364 )
6389 % ctx.rev()
6365 % ctx.rev()
6390 )
6366 )
6391 elif dirty:
6367 elif dirty:
6392 hint = _(b"uncommitted changes, use --all to discard all changes")
6368 hint = _(b"uncommitted changes, use --all to discard all changes")
6393 else:
6369 else:
6394 hint = _(b"use --all to revert all files")
6370 hint = _(b"use --all to revert all files")
6395 raise error.InputError(msg, hint=hint)
6371 raise error.InputError(msg, hint=hint)
6396
6372
6397 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6373 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6398
6374
6399
6375
6400 @command(
6376 @command(
6401 b'rollback',
6377 b'rollback',
6402 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6378 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6403 helpcategory=command.CATEGORY_MAINTENANCE,
6379 helpcategory=command.CATEGORY_MAINTENANCE,
6404 )
6380 )
6405 def rollback(ui, repo, **opts):
6381 def rollback(ui, repo, **opts):
6406 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6382 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6407
6383
6408 Please use :hg:`commit --amend` instead of rollback to correct
6384 Please use :hg:`commit --amend` instead of rollback to correct
6409 mistakes in the last commit.
6385 mistakes in the last commit.
6410
6386
6411 This command should be used with care. There is only one level of
6387 This command should be used with care. There is only one level of
6412 rollback, and there is no way to undo a rollback. It will also
6388 rollback, and there is no way to undo a rollback. It will also
6413 restore the dirstate at the time of the last transaction, losing
6389 restore the dirstate at the time of the last transaction, losing
6414 any dirstate changes since that time. This command does not alter
6390 any dirstate changes since that time. This command does not alter
6415 the working directory.
6391 the working directory.
6416
6392
6417 Transactions are used to encapsulate the effects of all commands
6393 Transactions are used to encapsulate the effects of all commands
6418 that create new changesets or propagate existing changesets into a
6394 that create new changesets or propagate existing changesets into a
6419 repository.
6395 repository.
6420
6396
6421 .. container:: verbose
6397 .. container:: verbose
6422
6398
6423 For example, the following commands are transactional, and their
6399 For example, the following commands are transactional, and their
6424 effects can be rolled back:
6400 effects can be rolled back:
6425
6401
6426 - commit
6402 - commit
6427 - import
6403 - import
6428 - pull
6404 - pull
6429 - push (with this repository as the destination)
6405 - push (with this repository as the destination)
6430 - unbundle
6406 - unbundle
6431
6407
6432 To avoid permanent data loss, rollback will refuse to rollback a
6408 To avoid permanent data loss, rollback will refuse to rollback a
6433 commit transaction if it isn't checked out. Use --force to
6409 commit transaction if it isn't checked out. Use --force to
6434 override this protection.
6410 override this protection.
6435
6411
6436 The rollback command can be entirely disabled by setting the
6412 The rollback command can be entirely disabled by setting the
6437 ``ui.rollback`` configuration setting to false. If you're here
6413 ``ui.rollback`` configuration setting to false. If you're here
6438 because you want to use rollback and it's disabled, you can
6414 because you want to use rollback and it's disabled, you can
6439 re-enable the command by setting ``ui.rollback`` to true.
6415 re-enable the command by setting ``ui.rollback`` to true.
6440
6416
6441 This command is not intended for use on public repositories. Once
6417 This command is not intended for use on public repositories. Once
6442 changes are visible for pull by other users, rolling a transaction
6418 changes are visible for pull by other users, rolling a transaction
6443 back locally is ineffective (someone else may already have pulled
6419 back locally is ineffective (someone else may already have pulled
6444 the changes). Furthermore, a race is possible with readers of the
6420 the changes). Furthermore, a race is possible with readers of the
6445 repository; for example an in-progress pull from the repository
6421 repository; for example an in-progress pull from the repository
6446 may fail if a rollback is performed.
6422 may fail if a rollback is performed.
6447
6423
6448 Returns 0 on success, 1 if no rollback data is available.
6424 Returns 0 on success, 1 if no rollback data is available.
6449 """
6425 """
6450 if not ui.configbool(b'ui', b'rollback'):
6426 if not ui.configbool(b'ui', b'rollback'):
6451 raise error.Abort(
6427 raise error.Abort(
6452 _(b'rollback is disabled because it is unsafe'),
6428 _(b'rollback is disabled because it is unsafe'),
6453 hint=b'see `hg help -v rollback` for information',
6429 hint=b'see `hg help -v rollback` for information',
6454 )
6430 )
6455 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6431 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6456
6432
6457
6433
6458 @command(
6434 @command(
6459 b'root',
6435 b'root',
6460 [] + formatteropts,
6436 [] + formatteropts,
6461 intents={INTENT_READONLY},
6437 intents={INTENT_READONLY},
6462 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6438 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6463 )
6439 )
6464 def root(ui, repo, **opts):
6440 def root(ui, repo, **opts):
6465 """print the root (top) of the current working directory
6441 """print the root (top) of the current working directory
6466
6442
6467 Print the root directory of the current repository.
6443 Print the root directory of the current repository.
6468
6444
6469 .. container:: verbose
6445 .. container:: verbose
6470
6446
6471 Template:
6447 Template:
6472
6448
6473 The following keywords are supported in addition to the common template
6449 The following keywords are supported in addition to the common template
6474 keywords and functions. See also :hg:`help templates`.
6450 keywords and functions. See also :hg:`help templates`.
6475
6451
6476 :hgpath: String. Path to the .hg directory.
6452 :hgpath: String. Path to the .hg directory.
6477 :storepath: String. Path to the directory holding versioned data.
6453 :storepath: String. Path to the directory holding versioned data.
6478
6454
6479 Returns 0 on success.
6455 Returns 0 on success.
6480 """
6456 """
6481 opts = pycompat.byteskwargs(opts)
6457 opts = pycompat.byteskwargs(opts)
6482 with ui.formatter(b'root', opts) as fm:
6458 with ui.formatter(b'root', opts) as fm:
6483 fm.startitem()
6459 fm.startitem()
6484 fm.write(b'reporoot', b'%s\n', repo.root)
6460 fm.write(b'reporoot', b'%s\n', repo.root)
6485 fm.data(hgpath=repo.path, storepath=repo.spath)
6461 fm.data(hgpath=repo.path, storepath=repo.spath)
6486
6462
6487
6463
6488 @command(
6464 @command(
6489 b'serve',
6465 b'serve',
6490 [
6466 [
6491 (
6467 (
6492 b'A',
6468 b'A',
6493 b'accesslog',
6469 b'accesslog',
6494 b'',
6470 b'',
6495 _(b'name of access log file to write to'),
6471 _(b'name of access log file to write to'),
6496 _(b'FILE'),
6472 _(b'FILE'),
6497 ),
6473 ),
6498 (b'd', b'daemon', None, _(b'run server in background')),
6474 (b'd', b'daemon', None, _(b'run server in background')),
6499 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6475 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6500 (
6476 (
6501 b'E',
6477 b'E',
6502 b'errorlog',
6478 b'errorlog',
6503 b'',
6479 b'',
6504 _(b'name of error log file to write to'),
6480 _(b'name of error log file to write to'),
6505 _(b'FILE'),
6481 _(b'FILE'),
6506 ),
6482 ),
6507 # use string type, then we can check if something was passed
6483 # use string type, then we can check if something was passed
6508 (
6484 (
6509 b'p',
6485 b'p',
6510 b'port',
6486 b'port',
6511 b'',
6487 b'',
6512 _(b'port to listen on (default: 8000)'),
6488 _(b'port to listen on (default: 8000)'),
6513 _(b'PORT'),
6489 _(b'PORT'),
6514 ),
6490 ),
6515 (
6491 (
6516 b'a',
6492 b'a',
6517 b'address',
6493 b'address',
6518 b'',
6494 b'',
6519 _(b'address to listen on (default: all interfaces)'),
6495 _(b'address to listen on (default: all interfaces)'),
6520 _(b'ADDR'),
6496 _(b'ADDR'),
6521 ),
6497 ),
6522 (
6498 (
6523 b'',
6499 b'',
6524 b'prefix',
6500 b'prefix',
6525 b'',
6501 b'',
6526 _(b'prefix path to serve from (default: server root)'),
6502 _(b'prefix path to serve from (default: server root)'),
6527 _(b'PREFIX'),
6503 _(b'PREFIX'),
6528 ),
6504 ),
6529 (
6505 (
6530 b'n',
6506 b'n',
6531 b'name',
6507 b'name',
6532 b'',
6508 b'',
6533 _(b'name to show in web pages (default: working directory)'),
6509 _(b'name to show in web pages (default: working directory)'),
6534 _(b'NAME'),
6510 _(b'NAME'),
6535 ),
6511 ),
6536 (
6512 (
6537 b'',
6513 b'',
6538 b'web-conf',
6514 b'web-conf',
6539 b'',
6515 b'',
6540 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6516 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6541 _(b'FILE'),
6517 _(b'FILE'),
6542 ),
6518 ),
6543 (
6519 (
6544 b'',
6520 b'',
6545 b'webdir-conf',
6521 b'webdir-conf',
6546 b'',
6522 b'',
6547 _(b'name of the hgweb config file (DEPRECATED)'),
6523 _(b'name of the hgweb config file (DEPRECATED)'),
6548 _(b'FILE'),
6524 _(b'FILE'),
6549 ),
6525 ),
6550 (
6526 (
6551 b'',
6527 b'',
6552 b'pid-file',
6528 b'pid-file',
6553 b'',
6529 b'',
6554 _(b'name of file to write process ID to'),
6530 _(b'name of file to write process ID to'),
6555 _(b'FILE'),
6531 _(b'FILE'),
6556 ),
6532 ),
6557 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6533 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6558 (
6534 (
6559 b'',
6535 b'',
6560 b'cmdserver',
6536 b'cmdserver',
6561 b'',
6537 b'',
6562 _(b'for remote clients (ADVANCED)'),
6538 _(b'for remote clients (ADVANCED)'),
6563 _(b'MODE'),
6539 _(b'MODE'),
6564 ),
6540 ),
6565 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6541 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6566 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6542 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6567 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6543 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6568 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6544 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6569 (b'', b'print-url', None, _(b'start and print only the URL')),
6545 (b'', b'print-url', None, _(b'start and print only the URL')),
6570 ]
6546 ]
6571 + subrepoopts,
6547 + subrepoopts,
6572 _(b'[OPTION]...'),
6548 _(b'[OPTION]...'),
6573 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6549 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6574 helpbasic=True,
6550 helpbasic=True,
6575 optionalrepo=True,
6551 optionalrepo=True,
6576 )
6552 )
6577 def serve(ui, repo, **opts):
6553 def serve(ui, repo, **opts):
6578 """start stand-alone webserver
6554 """start stand-alone webserver
6579
6555
6580 Start a local HTTP repository browser and pull server. You can use
6556 Start a local HTTP repository browser and pull server. You can use
6581 this for ad-hoc sharing and browsing of repositories. It is
6557 this for ad-hoc sharing and browsing of repositories. It is
6582 recommended to use a real web server to serve a repository for
6558 recommended to use a real web server to serve a repository for
6583 longer periods of time.
6559 longer periods of time.
6584
6560
6585 Please note that the server does not implement access control.
6561 Please note that the server does not implement access control.
6586 This means that, by default, anybody can read from the server and
6562 This means that, by default, anybody can read from the server and
6587 nobody can write to it by default. Set the ``web.allow-push``
6563 nobody can write to it by default. Set the ``web.allow-push``
6588 option to ``*`` to allow everybody to push to the server. You
6564 option to ``*`` to allow everybody to push to the server. You
6589 should use a real web server if you need to authenticate users.
6565 should use a real web server if you need to authenticate users.
6590
6566
6591 By default, the server logs accesses to stdout and errors to
6567 By default, the server logs accesses to stdout and errors to
6592 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6568 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6593 files.
6569 files.
6594
6570
6595 To have the server choose a free port number to listen on, specify
6571 To have the server choose a free port number to listen on, specify
6596 a port number of 0; in this case, the server will print the port
6572 a port number of 0; in this case, the server will print the port
6597 number it uses.
6573 number it uses.
6598
6574
6599 Returns 0 on success.
6575 Returns 0 on success.
6600 """
6576 """
6601
6577
6602 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6578 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6603 opts = pycompat.byteskwargs(opts)
6579 opts = pycompat.byteskwargs(opts)
6604 if opts[b"print_url"] and ui.verbose:
6580 if opts[b"print_url"] and ui.verbose:
6605 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6581 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6606
6582
6607 if opts[b"stdio"]:
6583 if opts[b"stdio"]:
6608 if repo is None:
6584 if repo is None:
6609 raise error.RepoError(
6585 raise error.RepoError(
6610 _(b"there is no Mercurial repository here (.hg not found)")
6586 _(b"there is no Mercurial repository here (.hg not found)")
6611 )
6587 )
6612 s = wireprotoserver.sshserver(ui, repo)
6588 s = wireprotoserver.sshserver(ui, repo)
6613 s.serve_forever()
6589 s.serve_forever()
6614 return
6590 return
6615
6591
6616 service = server.createservice(ui, repo, opts)
6592 service = server.createservice(ui, repo, opts)
6617 return server.runservice(opts, initfn=service.init, runfn=service.run)
6593 return server.runservice(opts, initfn=service.init, runfn=service.run)
6618
6594
6619
6595
6620 @command(
6596 @command(
6621 b'shelve',
6597 b'shelve',
6622 [
6598 [
6623 (
6599 (
6624 b'A',
6600 b'A',
6625 b'addremove',
6601 b'addremove',
6626 None,
6602 None,
6627 _(b'mark new/missing files as added/removed before shelving'),
6603 _(b'mark new/missing files as added/removed before shelving'),
6628 ),
6604 ),
6629 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6605 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6630 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6606 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6631 (
6607 (
6632 b'',
6608 b'',
6633 b'date',
6609 b'date',
6634 b'',
6610 b'',
6635 _(b'shelve with the specified commit date'),
6611 _(b'shelve with the specified commit date'),
6636 _(b'DATE'),
6612 _(b'DATE'),
6637 ),
6613 ),
6638 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6614 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6639 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6615 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6640 (
6616 (
6641 b'k',
6617 b'k',
6642 b'keep',
6618 b'keep',
6643 False,
6619 False,
6644 _(b'shelve, but keep changes in the working directory'),
6620 _(b'shelve, but keep changes in the working directory'),
6645 ),
6621 ),
6646 (b'l', b'list', None, _(b'list current shelves')),
6622 (b'l', b'list', None, _(b'list current shelves')),
6647 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6623 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6648 (
6624 (
6649 b'n',
6625 b'n',
6650 b'name',
6626 b'name',
6651 b'',
6627 b'',
6652 _(b'use the given name for the shelved commit'),
6628 _(b'use the given name for the shelved commit'),
6653 _(b'NAME'),
6629 _(b'NAME'),
6654 ),
6630 ),
6655 (
6631 (
6656 b'p',
6632 b'p',
6657 b'patch',
6633 b'patch',
6658 None,
6634 None,
6659 _(
6635 _(
6660 b'output patches for changes (provide the names of the shelved '
6636 b'output patches for changes (provide the names of the shelved '
6661 b'changes as positional arguments)'
6637 b'changes as positional arguments)'
6662 ),
6638 ),
6663 ),
6639 ),
6664 (b'i', b'interactive', None, _(b'interactive mode')),
6640 (b'i', b'interactive', None, _(b'interactive mode')),
6665 (
6641 (
6666 b'',
6642 b'',
6667 b'stat',
6643 b'stat',
6668 None,
6644 None,
6669 _(
6645 _(
6670 b'output diffstat-style summary of changes (provide the names of '
6646 b'output diffstat-style summary of changes (provide the names of '
6671 b'the shelved changes as positional arguments)'
6647 b'the shelved changes as positional arguments)'
6672 ),
6648 ),
6673 ),
6649 ),
6674 ]
6650 ]
6675 + cmdutil.walkopts,
6651 + cmdutil.walkopts,
6676 _(b'hg shelve [OPTION]... [FILE]...'),
6652 _(b'hg shelve [OPTION]... [FILE]...'),
6677 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6653 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6678 )
6654 )
6679 def shelve(ui, repo, *pats, **opts):
6655 def shelve(ui, repo, *pats, **opts):
6680 """save and set aside changes from the working directory
6656 """save and set aside changes from the working directory
6681
6657
6682 Shelving takes files that "hg status" reports as not clean, saves
6658 Shelving takes files that "hg status" reports as not clean, saves
6683 the modifications to a bundle (a shelved change), and reverts the
6659 the modifications to a bundle (a shelved change), and reverts the
6684 files so that their state in the working directory becomes clean.
6660 files so that their state in the working directory becomes clean.
6685
6661
6686 To restore these changes to the working directory, using "hg
6662 To restore these changes to the working directory, using "hg
6687 unshelve"; this will work even if you switch to a different
6663 unshelve"; this will work even if you switch to a different
6688 commit.
6664 commit.
6689
6665
6690 When no files are specified, "hg shelve" saves all not-clean
6666 When no files are specified, "hg shelve" saves all not-clean
6691 files. If specific files or directories are named, only changes to
6667 files. If specific files or directories are named, only changes to
6692 those files are shelved.
6668 those files are shelved.
6693
6669
6694 In bare shelve (when no files are specified, without interactive,
6670 In bare shelve (when no files are specified, without interactive,
6695 include and exclude option), shelving remembers information if the
6671 include and exclude option), shelving remembers information if the
6696 working directory was on newly created branch, in other words working
6672 working directory was on newly created branch, in other words working
6697 directory was on different branch than its first parent. In this
6673 directory was on different branch than its first parent. In this
6698 situation unshelving restores branch information to the working directory.
6674 situation unshelving restores branch information to the working directory.
6699
6675
6700 Each shelved change has a name that makes it easier to find later.
6676 Each shelved change has a name that makes it easier to find later.
6701 The name of a shelved change defaults to being based on the active
6677 The name of a shelved change defaults to being based on the active
6702 bookmark, or if there is no active bookmark, the current named
6678 bookmark, or if there is no active bookmark, the current named
6703 branch. To specify a different name, use ``--name``.
6679 branch. To specify a different name, use ``--name``.
6704
6680
6705 To see a list of existing shelved changes, use the ``--list``
6681 To see a list of existing shelved changes, use the ``--list``
6706 option. For each shelved change, this will print its name, age,
6682 option. For each shelved change, this will print its name, age,
6707 and description; use ``--patch`` or ``--stat`` for more details.
6683 and description; use ``--patch`` or ``--stat`` for more details.
6708
6684
6709 To delete specific shelved changes, use ``--delete``. To delete
6685 To delete specific shelved changes, use ``--delete``. To delete
6710 all shelved changes, use ``--cleanup``.
6686 all shelved changes, use ``--cleanup``.
6711 """
6687 """
6712 opts = pycompat.byteskwargs(opts)
6688 opts = pycompat.byteskwargs(opts)
6713 allowables = [
6689 allowables = [
6714 (b'addremove', {b'create'}), # 'create' is pseudo action
6690 (b'addremove', {b'create'}), # 'create' is pseudo action
6715 (b'unknown', {b'create'}),
6691 (b'unknown', {b'create'}),
6716 (b'cleanup', {b'cleanup'}),
6692 (b'cleanup', {b'cleanup'}),
6717 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6693 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6718 (b'delete', {b'delete'}),
6694 (b'delete', {b'delete'}),
6719 (b'edit', {b'create'}),
6695 (b'edit', {b'create'}),
6720 (b'keep', {b'create'}),
6696 (b'keep', {b'create'}),
6721 (b'list', {b'list'}),
6697 (b'list', {b'list'}),
6722 (b'message', {b'create'}),
6698 (b'message', {b'create'}),
6723 (b'name', {b'create'}),
6699 (b'name', {b'create'}),
6724 (b'patch', {b'patch', b'list'}),
6700 (b'patch', {b'patch', b'list'}),
6725 (b'stat', {b'stat', b'list'}),
6701 (b'stat', {b'stat', b'list'}),
6726 ]
6702 ]
6727
6703
6728 def checkopt(opt):
6704 def checkopt(opt):
6729 if opts.get(opt):
6705 if opts.get(opt):
6730 for i, allowable in allowables:
6706 for i, allowable in allowables:
6731 if opts[i] and opt not in allowable:
6707 if opts[i] and opt not in allowable:
6732 raise error.InputError(
6708 raise error.InputError(
6733 _(
6709 _(
6734 b"options '--%s' and '--%s' may not be "
6710 b"options '--%s' and '--%s' may not be "
6735 b"used together"
6711 b"used together"
6736 )
6712 )
6737 % (opt, i)
6713 % (opt, i)
6738 )
6714 )
6739 return True
6715 return True
6740
6716
6741 if checkopt(b'cleanup'):
6717 if checkopt(b'cleanup'):
6742 if pats:
6718 if pats:
6743 raise error.InputError(
6719 raise error.InputError(
6744 _(b"cannot specify names when using '--cleanup'")
6720 _(b"cannot specify names when using '--cleanup'")
6745 )
6721 )
6746 return shelvemod.cleanupcmd(ui, repo)
6722 return shelvemod.cleanupcmd(ui, repo)
6747 elif checkopt(b'delete'):
6723 elif checkopt(b'delete'):
6748 return shelvemod.deletecmd(ui, repo, pats)
6724 return shelvemod.deletecmd(ui, repo, pats)
6749 elif checkopt(b'list'):
6725 elif checkopt(b'list'):
6750 return shelvemod.listcmd(ui, repo, pats, opts)
6726 return shelvemod.listcmd(ui, repo, pats, opts)
6751 elif checkopt(b'patch') or checkopt(b'stat'):
6727 elif checkopt(b'patch') or checkopt(b'stat'):
6752 return shelvemod.patchcmds(ui, repo, pats, opts)
6728 return shelvemod.patchcmds(ui, repo, pats, opts)
6753 else:
6729 else:
6754 return shelvemod.createcmd(ui, repo, pats, opts)
6730 return shelvemod.createcmd(ui, repo, pats, opts)
6755
6731
6756
6732
6757 _NOTTERSE = b'nothing'
6733 _NOTTERSE = b'nothing'
6758
6734
6759
6735
6760 @command(
6736 @command(
6761 b'status|st',
6737 b'status|st',
6762 [
6738 [
6763 (b'A', b'all', None, _(b'show status of all files')),
6739 (b'A', b'all', None, _(b'show status of all files')),
6764 (b'm', b'modified', None, _(b'show only modified files')),
6740 (b'm', b'modified', None, _(b'show only modified files')),
6765 (b'a', b'added', None, _(b'show only added files')),
6741 (b'a', b'added', None, _(b'show only added files')),
6766 (b'r', b'removed', None, _(b'show only removed files')),
6742 (b'r', b'removed', None, _(b'show only removed files')),
6767 (b'd', b'deleted', None, _(b'show only missing files')),
6743 (b'd', b'deleted', None, _(b'show only missing files')),
6768 (b'c', b'clean', None, _(b'show only files without changes')),
6744 (b'c', b'clean', None, _(b'show only files without changes')),
6769 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6745 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6770 (b'i', b'ignored', None, _(b'show only ignored files')),
6746 (b'i', b'ignored', None, _(b'show only ignored files')),
6771 (b'n', b'no-status', None, _(b'hide status prefix')),
6747 (b'n', b'no-status', None, _(b'hide status prefix')),
6772 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6748 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6773 (
6749 (
6774 b'C',
6750 b'C',
6775 b'copies',
6751 b'copies',
6776 None,
6752 None,
6777 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6753 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6778 ),
6754 ),
6779 (
6755 (
6780 b'0',
6756 b'0',
6781 b'print0',
6757 b'print0',
6782 None,
6758 None,
6783 _(b'end filenames with NUL, for use with xargs'),
6759 _(b'end filenames with NUL, for use with xargs'),
6784 ),
6760 ),
6785 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6761 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6786 (
6762 (
6787 b'',
6763 b'',
6788 b'change',
6764 b'change',
6789 b'',
6765 b'',
6790 _(b'list the changed files of a revision'),
6766 _(b'list the changed files of a revision'),
6791 _(b'REV'),
6767 _(b'REV'),
6792 ),
6768 ),
6793 ]
6769 ]
6794 + walkopts
6770 + walkopts
6795 + subrepoopts
6771 + subrepoopts
6796 + formatteropts,
6772 + formatteropts,
6797 _(b'[OPTION]... [FILE]...'),
6773 _(b'[OPTION]... [FILE]...'),
6798 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6774 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6799 helpbasic=True,
6775 helpbasic=True,
6800 inferrepo=True,
6776 inferrepo=True,
6801 intents={INTENT_READONLY},
6777 intents={INTENT_READONLY},
6802 )
6778 )
6803 def status(ui, repo, *pats, **opts):
6779 def status(ui, repo, *pats, **opts):
6804 """show changed files in the working directory
6780 """show changed files in the working directory
6805
6781
6806 Show status of files in the repository. If names are given, only
6782 Show status of files in the repository. If names are given, only
6807 files that match are shown. Files that are clean or ignored or
6783 files that match are shown. Files that are clean or ignored or
6808 the source of a copy/move operation, are not listed unless
6784 the source of a copy/move operation, are not listed unless
6809 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6785 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6810 Unless options described with "show only ..." are given, the
6786 Unless options described with "show only ..." are given, the
6811 options -mardu are used.
6787 options -mardu are used.
6812
6788
6813 Option -q/--quiet hides untracked (unknown and ignored) files
6789 Option -q/--quiet hides untracked (unknown and ignored) files
6814 unless explicitly requested with -u/--unknown or -i/--ignored.
6790 unless explicitly requested with -u/--unknown or -i/--ignored.
6815
6791
6816 .. note::
6792 .. note::
6817
6793
6818 :hg:`status` may appear to disagree with diff if permissions have
6794 :hg:`status` may appear to disagree with diff if permissions have
6819 changed or a merge has occurred. The standard diff format does
6795 changed or a merge has occurred. The standard diff format does
6820 not report permission changes and diff only reports changes
6796 not report permission changes and diff only reports changes
6821 relative to one merge parent.
6797 relative to one merge parent.
6822
6798
6823 If one revision is given, it is used as the base revision.
6799 If one revision is given, it is used as the base revision.
6824 If two revisions are given, the differences between them are
6800 If two revisions are given, the differences between them are
6825 shown. The --change option can also be used as a shortcut to list
6801 shown. The --change option can also be used as a shortcut to list
6826 the changed files of a revision from its first parent.
6802 the changed files of a revision from its first parent.
6827
6803
6828 The codes used to show the status of files are::
6804 The codes used to show the status of files are::
6829
6805
6830 M = modified
6806 M = modified
6831 A = added
6807 A = added
6832 R = removed
6808 R = removed
6833 C = clean
6809 C = clean
6834 ! = missing (deleted by non-hg command, but still tracked)
6810 ! = missing (deleted by non-hg command, but still tracked)
6835 ? = not tracked
6811 ? = not tracked
6836 I = ignored
6812 I = ignored
6837 = origin of the previous file (with --copies)
6813 = origin of the previous file (with --copies)
6838
6814
6839 .. container:: verbose
6815 .. container:: verbose
6840
6816
6841 The -t/--terse option abbreviates the output by showing only the directory
6817 The -t/--terse option abbreviates the output by showing only the directory
6842 name if all the files in it share the same status. The option takes an
6818 name if all the files in it share the same status. The option takes an
6843 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6819 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6844 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6820 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6845 for 'ignored' and 'c' for clean.
6821 for 'ignored' and 'c' for clean.
6846
6822
6847 It abbreviates only those statuses which are passed. Note that clean and
6823 It abbreviates only those statuses which are passed. Note that clean and
6848 ignored files are not displayed with '--terse ic' unless the -c/--clean
6824 ignored files are not displayed with '--terse ic' unless the -c/--clean
6849 and -i/--ignored options are also used.
6825 and -i/--ignored options are also used.
6850
6826
6851 The -v/--verbose option shows information when the repository is in an
6827 The -v/--verbose option shows information when the repository is in an
6852 unfinished merge, shelve, rebase state etc. You can have this behavior
6828 unfinished merge, shelve, rebase state etc. You can have this behavior
6853 turned on by default by enabling the ``commands.status.verbose`` option.
6829 turned on by default by enabling the ``commands.status.verbose`` option.
6854
6830
6855 You can skip displaying some of these states by setting
6831 You can skip displaying some of these states by setting
6856 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6832 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6857 'histedit', 'merge', 'rebase', or 'unshelve'.
6833 'histedit', 'merge', 'rebase', or 'unshelve'.
6858
6834
6859 Template:
6835 Template:
6860
6836
6861 The following keywords are supported in addition to the common template
6837 The following keywords are supported in addition to the common template
6862 keywords and functions. See also :hg:`help templates`.
6838 keywords and functions. See also :hg:`help templates`.
6863
6839
6864 :path: String. Repository-absolute path of the file.
6840 :path: String. Repository-absolute path of the file.
6865 :source: String. Repository-absolute path of the file originated from.
6841 :source: String. Repository-absolute path of the file originated from.
6866 Available if ``--copies`` is specified.
6842 Available if ``--copies`` is specified.
6867 :status: String. Character denoting file's status.
6843 :status: String. Character denoting file's status.
6868
6844
6869 Examples:
6845 Examples:
6870
6846
6871 - show changes in the working directory relative to a
6847 - show changes in the working directory relative to a
6872 changeset::
6848 changeset::
6873
6849
6874 hg status --rev 9353
6850 hg status --rev 9353
6875
6851
6876 - show changes in the working directory relative to the
6852 - show changes in the working directory relative to the
6877 current directory (see :hg:`help patterns` for more information)::
6853 current directory (see :hg:`help patterns` for more information)::
6878
6854
6879 hg status re:
6855 hg status re:
6880
6856
6881 - show all changes including copies in an existing changeset::
6857 - show all changes including copies in an existing changeset::
6882
6858
6883 hg status --copies --change 9353
6859 hg status --copies --change 9353
6884
6860
6885 - get a NUL separated list of added files, suitable for xargs::
6861 - get a NUL separated list of added files, suitable for xargs::
6886
6862
6887 hg status -an0
6863 hg status -an0
6888
6864
6889 - show more information about the repository status, abbreviating
6865 - show more information about the repository status, abbreviating
6890 added, removed, modified, deleted, and untracked paths::
6866 added, removed, modified, deleted, and untracked paths::
6891
6867
6892 hg status -v -t mardu
6868 hg status -v -t mardu
6893
6869
6894 Returns 0 on success.
6870 Returns 0 on success.
6895
6871
6896 """
6872 """
6897
6873
6898 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6874 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6899 opts = pycompat.byteskwargs(opts)
6875 opts = pycompat.byteskwargs(opts)
6900 revs = opts.get(b'rev')
6876 revs = opts.get(b'rev')
6901 change = opts.get(b'change')
6877 change = opts.get(b'change')
6902 terse = opts.get(b'terse')
6878 terse = opts.get(b'terse')
6903 if terse is _NOTTERSE:
6879 if terse is _NOTTERSE:
6904 if revs:
6880 if revs:
6905 terse = b''
6881 terse = b''
6906 else:
6882 else:
6907 terse = ui.config(b'commands', b'status.terse')
6883 terse = ui.config(b'commands', b'status.terse')
6908
6884
6909 if revs and terse:
6885 if revs and terse:
6910 msg = _(b'cannot use --terse with --rev')
6886 msg = _(b'cannot use --terse with --rev')
6911 raise error.InputError(msg)
6887 raise error.InputError(msg)
6912 elif change:
6888 elif change:
6913 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6889 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6914 ctx2 = logcmdutil.revsingle(repo, change, None)
6890 ctx2 = logcmdutil.revsingle(repo, change, None)
6915 ctx1 = ctx2.p1()
6891 ctx1 = ctx2.p1()
6916 else:
6892 else:
6917 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6893 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6918 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6894 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6919
6895
6920 forcerelativevalue = None
6896 forcerelativevalue = None
6921 if ui.hasconfig(b'commands', b'status.relative'):
6897 if ui.hasconfig(b'commands', b'status.relative'):
6922 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6898 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6923 uipathfn = scmutil.getuipathfn(
6899 uipathfn = scmutil.getuipathfn(
6924 repo,
6900 repo,
6925 legacyrelativevalue=bool(pats),
6901 legacyrelativevalue=bool(pats),
6926 forcerelativevalue=forcerelativevalue,
6902 forcerelativevalue=forcerelativevalue,
6927 )
6903 )
6928
6904
6929 if opts.get(b'print0'):
6905 if opts.get(b'print0'):
6930 end = b'\0'
6906 end = b'\0'
6931 else:
6907 else:
6932 end = b'\n'
6908 end = b'\n'
6933 states = b'modified added removed deleted unknown ignored clean'.split()
6909 states = b'modified added removed deleted unknown ignored clean'.split()
6934 show = [k for k in states if opts.get(k)]
6910 show = [k for k in states if opts.get(k)]
6935 if opts.get(b'all'):
6911 if opts.get(b'all'):
6936 show += ui.quiet and (states[:4] + [b'clean']) or states
6912 show += ui.quiet and (states[:4] + [b'clean']) or states
6937
6913
6938 if not show:
6914 if not show:
6939 if ui.quiet:
6915 if ui.quiet:
6940 show = states[:4]
6916 show = states[:4]
6941 else:
6917 else:
6942 show = states[:5]
6918 show = states[:5]
6943
6919
6944 m = scmutil.match(ctx2, pats, opts)
6920 m = scmutil.match(ctx2, pats, opts)
6945 if terse:
6921 if terse:
6946 # we need to compute clean and unknown to terse
6922 # we need to compute clean and unknown to terse
6947 stat = repo.status(
6923 stat = repo.status(
6948 ctx1.node(),
6924 ctx1.node(),
6949 ctx2.node(),
6925 ctx2.node(),
6950 m,
6926 m,
6951 b'ignored' in show or b'i' in terse,
6927 b'ignored' in show or b'i' in terse,
6952 clean=True,
6928 clean=True,
6953 unknown=True,
6929 unknown=True,
6954 listsubrepos=opts.get(b'subrepos'),
6930 listsubrepos=opts.get(b'subrepos'),
6955 )
6931 )
6956
6932
6957 stat = cmdutil.tersedir(stat, terse)
6933 stat = cmdutil.tersedir(stat, terse)
6958 else:
6934 else:
6959 stat = repo.status(
6935 stat = repo.status(
6960 ctx1.node(),
6936 ctx1.node(),
6961 ctx2.node(),
6937 ctx2.node(),
6962 m,
6938 m,
6963 b'ignored' in show,
6939 b'ignored' in show,
6964 b'clean' in show,
6940 b'clean' in show,
6965 b'unknown' in show,
6941 b'unknown' in show,
6966 opts.get(b'subrepos'),
6942 opts.get(b'subrepos'),
6967 )
6943 )
6968
6944
6969 changestates = zip(
6945 changestates = zip(
6970 states,
6946 states,
6971 pycompat.iterbytestr(b'MAR!?IC'),
6947 pycompat.iterbytestr(b'MAR!?IC'),
6972 [getattr(stat, s.decode('utf8')) for s in states],
6948 [getattr(stat, s.decode('utf8')) for s in states],
6973 )
6949 )
6974
6950
6975 copy = {}
6951 copy = {}
6976 if (
6952 if (
6977 opts.get(b'all')
6953 opts.get(b'all')
6978 or opts.get(b'copies')
6954 or opts.get(b'copies')
6979 or ui.configbool(b'ui', b'statuscopies')
6955 or ui.configbool(b'ui', b'statuscopies')
6980 ) and not opts.get(b'no_status'):
6956 ) and not opts.get(b'no_status'):
6981 copy = copies.pathcopies(ctx1, ctx2, m)
6957 copy = copies.pathcopies(ctx1, ctx2, m)
6982
6958
6983 morestatus = None
6959 morestatus = None
6984 if (
6960 if (
6985 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6961 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6986 and not ui.plain()
6962 and not ui.plain()
6987 and not opts.get(b'print0')
6963 and not opts.get(b'print0')
6988 ):
6964 ):
6989 morestatus = cmdutil.readmorestatus(repo)
6965 morestatus = cmdutil.readmorestatus(repo)
6990
6966
6991 ui.pager(b'status')
6967 ui.pager(b'status')
6992 fm = ui.formatter(b'status', opts)
6968 fm = ui.formatter(b'status', opts)
6993 fmt = b'%s' + end
6969 fmt = b'%s' + end
6994 showchar = not opts.get(b'no_status')
6970 showchar = not opts.get(b'no_status')
6995
6971
6996 for state, char, files in changestates:
6972 for state, char, files in changestates:
6997 if state in show:
6973 if state in show:
6998 label = b'status.' + state
6974 label = b'status.' + state
6999 for f in files:
6975 for f in files:
7000 fm.startitem()
6976 fm.startitem()
7001 fm.context(ctx=ctx2)
6977 fm.context(ctx=ctx2)
7002 fm.data(itemtype=b'file', path=f)
6978 fm.data(itemtype=b'file', path=f)
7003 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6979 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7004 fm.plain(fmt % uipathfn(f), label=label)
6980 fm.plain(fmt % uipathfn(f), label=label)
7005 if f in copy:
6981 if f in copy:
7006 fm.data(source=copy[f])
6982 fm.data(source=copy[f])
7007 fm.plain(
6983 fm.plain(
7008 (b' %s' + end) % uipathfn(copy[f]),
6984 (b' %s' + end) % uipathfn(copy[f]),
7009 label=b'status.copied',
6985 label=b'status.copied',
7010 )
6986 )
7011 if morestatus:
6987 if morestatus:
7012 morestatus.formatfile(f, fm)
6988 morestatus.formatfile(f, fm)
7013
6989
7014 if morestatus:
6990 if morestatus:
7015 morestatus.formatfooter(fm)
6991 morestatus.formatfooter(fm)
7016 fm.end()
6992 fm.end()
7017
6993
7018
6994
7019 @command(
6995 @command(
7020 b'summary|sum',
6996 b'summary|sum',
7021 [(b'', b'remote', None, _(b'check for push and pull'))],
6997 [(b'', b'remote', None, _(b'check for push and pull'))],
7022 b'[--remote]',
6998 b'[--remote]',
7023 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6999 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7024 helpbasic=True,
7000 helpbasic=True,
7025 intents={INTENT_READONLY},
7001 intents={INTENT_READONLY},
7026 )
7002 )
7027 def summary(ui, repo, **opts):
7003 def summary(ui, repo, **opts):
7028 """summarize working directory state
7004 """summarize working directory state
7029
7005
7030 This generates a brief summary of the working directory state,
7006 This generates a brief summary of the working directory state,
7031 including parents, branch, commit status, phase and available updates.
7007 including parents, branch, commit status, phase and available updates.
7032
7008
7033 With the --remote option, this will check the default paths for
7009 With the --remote option, this will check the default paths for
7034 incoming and outgoing changes. This can be time-consuming.
7010 incoming and outgoing changes. This can be time-consuming.
7035
7011
7036 Returns 0 on success.
7012 Returns 0 on success.
7037 """
7013 """
7038
7014
7039 opts = pycompat.byteskwargs(opts)
7015 opts = pycompat.byteskwargs(opts)
7040 ui.pager(b'summary')
7016 ui.pager(b'summary')
7041 ctx = repo[None]
7017 ctx = repo[None]
7042 parents = ctx.parents()
7018 parents = ctx.parents()
7043 pnode = parents[0].node()
7019 pnode = parents[0].node()
7044 marks = []
7020 marks = []
7045
7021
7046 try:
7022 try:
7047 ms = mergestatemod.mergestate.read(repo)
7023 ms = mergestatemod.mergestate.read(repo)
7048 except error.UnsupportedMergeRecords as e:
7024 except error.UnsupportedMergeRecords as e:
7049 s = b' '.join(e.recordtypes)
7025 s = b' '.join(e.recordtypes)
7050 ui.warn(
7026 ui.warn(
7051 _(b'warning: merge state has unsupported record types: %s\n') % s
7027 _(b'warning: merge state has unsupported record types: %s\n') % s
7052 )
7028 )
7053 unresolved = []
7029 unresolved = []
7054 else:
7030 else:
7055 unresolved = list(ms.unresolved())
7031 unresolved = list(ms.unresolved())
7056
7032
7057 for p in parents:
7033 for p in parents:
7058 # label with log.changeset (instead of log.parent) since this
7034 # label with log.changeset (instead of log.parent) since this
7059 # shows a working directory parent *changeset*:
7035 # shows a working directory parent *changeset*:
7060 # i18n: column positioning for "hg summary"
7036 # i18n: column positioning for "hg summary"
7061 ui.write(
7037 ui.write(
7062 _(b'parent: %d:%s ') % (p.rev(), p),
7038 _(b'parent: %d:%s ') % (p.rev(), p),
7063 label=logcmdutil.changesetlabels(p),
7039 label=logcmdutil.changesetlabels(p),
7064 )
7040 )
7065 ui.write(b' '.join(p.tags()), label=b'log.tag')
7041 ui.write(b' '.join(p.tags()), label=b'log.tag')
7066 if p.bookmarks():
7042 if p.bookmarks():
7067 marks.extend(p.bookmarks())
7043 marks.extend(p.bookmarks())
7068 if p.rev() == -1:
7044 if p.rev() == -1:
7069 if not len(repo):
7045 if not len(repo):
7070 ui.write(_(b' (empty repository)'))
7046 ui.write(_(b' (empty repository)'))
7071 else:
7047 else:
7072 ui.write(_(b' (no revision checked out)'))
7048 ui.write(_(b' (no revision checked out)'))
7073 if p.obsolete():
7049 if p.obsolete():
7074 ui.write(_(b' (obsolete)'))
7050 ui.write(_(b' (obsolete)'))
7075 if p.isunstable():
7051 if p.isunstable():
7076 instabilities = (
7052 instabilities = (
7077 ui.label(instability, b'trouble.%s' % instability)
7053 ui.label(instability, b'trouble.%s' % instability)
7078 for instability in p.instabilities()
7054 for instability in p.instabilities()
7079 )
7055 )
7080 ui.write(b' (' + b', '.join(instabilities) + b')')
7056 ui.write(b' (' + b', '.join(instabilities) + b')')
7081 ui.write(b'\n')
7057 ui.write(b'\n')
7082 if p.description():
7058 if p.description():
7083 ui.status(
7059 ui.status(
7084 b' ' + p.description().splitlines()[0].strip() + b'\n',
7060 b' ' + p.description().splitlines()[0].strip() + b'\n',
7085 label=b'log.summary',
7061 label=b'log.summary',
7086 )
7062 )
7087
7063
7088 branch = ctx.branch()
7064 branch = ctx.branch()
7089 bheads = repo.branchheads(branch)
7065 bheads = repo.branchheads(branch)
7090 # i18n: column positioning for "hg summary"
7066 # i18n: column positioning for "hg summary"
7091 m = _(b'branch: %s\n') % branch
7067 m = _(b'branch: %s\n') % branch
7092 if branch != b'default':
7068 if branch != b'default':
7093 ui.write(m, label=b'log.branch')
7069 ui.write(m, label=b'log.branch')
7094 else:
7070 else:
7095 ui.status(m, label=b'log.branch')
7071 ui.status(m, label=b'log.branch')
7096
7072
7097 if marks:
7073 if marks:
7098 active = repo._activebookmark
7074 active = repo._activebookmark
7099 # i18n: column positioning for "hg summary"
7075 # i18n: column positioning for "hg summary"
7100 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7076 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7101 if active is not None:
7077 if active is not None:
7102 if active in marks:
7078 if active in marks:
7103 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7079 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7104 marks.remove(active)
7080 marks.remove(active)
7105 else:
7081 else:
7106 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7082 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7107 for m in marks:
7083 for m in marks:
7108 ui.write(b' ' + m, label=b'log.bookmark')
7084 ui.write(b' ' + m, label=b'log.bookmark')
7109 ui.write(b'\n', label=b'log.bookmark')
7085 ui.write(b'\n', label=b'log.bookmark')
7110
7086
7111 status = repo.status(unknown=True)
7087 status = repo.status(unknown=True)
7112
7088
7113 c = repo.dirstate.copies()
7089 c = repo.dirstate.copies()
7114 copied, renamed = [], []
7090 copied, renamed = [], []
7115 for d, s in pycompat.iteritems(c):
7091 for d, s in pycompat.iteritems(c):
7116 if s in status.removed:
7092 if s in status.removed:
7117 status.removed.remove(s)
7093 status.removed.remove(s)
7118 renamed.append(d)
7094 renamed.append(d)
7119 else:
7095 else:
7120 copied.append(d)
7096 copied.append(d)
7121 if d in status.added:
7097 if d in status.added:
7122 status.added.remove(d)
7098 status.added.remove(d)
7123
7099
7124 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7100 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7125
7101
7126 labels = [
7102 labels = [
7127 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7103 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7128 (ui.label(_(b'%d added'), b'status.added'), status.added),
7104 (ui.label(_(b'%d added'), b'status.added'), status.added),
7129 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7105 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7130 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7106 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7131 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7107 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7132 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7108 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7133 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7109 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7134 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7110 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7135 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7111 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7136 ]
7112 ]
7137 t = []
7113 t = []
7138 for l, s in labels:
7114 for l, s in labels:
7139 if s:
7115 if s:
7140 t.append(l % len(s))
7116 t.append(l % len(s))
7141
7117
7142 t = b', '.join(t)
7118 t = b', '.join(t)
7143 cleanworkdir = False
7119 cleanworkdir = False
7144
7120
7145 if repo.vfs.exists(b'graftstate'):
7121 if repo.vfs.exists(b'graftstate'):
7146 t += _(b' (graft in progress)')
7122 t += _(b' (graft in progress)')
7147 if repo.vfs.exists(b'updatestate'):
7123 if repo.vfs.exists(b'updatestate'):
7148 t += _(b' (interrupted update)')
7124 t += _(b' (interrupted update)')
7149 elif len(parents) > 1:
7125 elif len(parents) > 1:
7150 t += _(b' (merge)')
7126 t += _(b' (merge)')
7151 elif branch != parents[0].branch():
7127 elif branch != parents[0].branch():
7152 t += _(b' (new branch)')
7128 t += _(b' (new branch)')
7153 elif parents[0].closesbranch() and pnode in repo.branchheads(
7129 elif parents[0].closesbranch() and pnode in repo.branchheads(
7154 branch, closed=True
7130 branch, closed=True
7155 ):
7131 ):
7156 t += _(b' (head closed)')
7132 t += _(b' (head closed)')
7157 elif not (
7133 elif not (
7158 status.modified
7134 status.modified
7159 or status.added
7135 or status.added
7160 or status.removed
7136 or status.removed
7161 or renamed
7137 or renamed
7162 or copied
7138 or copied
7163 or subs
7139 or subs
7164 ):
7140 ):
7165 t += _(b' (clean)')
7141 t += _(b' (clean)')
7166 cleanworkdir = True
7142 cleanworkdir = True
7167 elif pnode not in bheads:
7143 elif pnode not in bheads:
7168 t += _(b' (new branch head)')
7144 t += _(b' (new branch head)')
7169
7145
7170 if parents:
7146 if parents:
7171 pendingphase = max(p.phase() for p in parents)
7147 pendingphase = max(p.phase() for p in parents)
7172 else:
7148 else:
7173 pendingphase = phases.public
7149 pendingphase = phases.public
7174
7150
7175 if pendingphase > phases.newcommitphase(ui):
7151 if pendingphase > phases.newcommitphase(ui):
7176 t += b' (%s)' % phases.phasenames[pendingphase]
7152 t += b' (%s)' % phases.phasenames[pendingphase]
7177
7153
7178 if cleanworkdir:
7154 if cleanworkdir:
7179 # i18n: column positioning for "hg summary"
7155 # i18n: column positioning for "hg summary"
7180 ui.status(_(b'commit: %s\n') % t.strip())
7156 ui.status(_(b'commit: %s\n') % t.strip())
7181 else:
7157 else:
7182 # i18n: column positioning for "hg summary"
7158 # i18n: column positioning for "hg summary"
7183 ui.write(_(b'commit: %s\n') % t.strip())
7159 ui.write(_(b'commit: %s\n') % t.strip())
7184
7160
7185 # all ancestors of branch heads - all ancestors of parent = new csets
7161 # all ancestors of branch heads - all ancestors of parent = new csets
7186 new = len(
7162 new = len(
7187 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7163 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7188 )
7164 )
7189
7165
7190 if new == 0:
7166 if new == 0:
7191 # i18n: column positioning for "hg summary"
7167 # i18n: column positioning for "hg summary"
7192 ui.status(_(b'update: (current)\n'))
7168 ui.status(_(b'update: (current)\n'))
7193 elif pnode not in bheads:
7169 elif pnode not in bheads:
7194 # i18n: column positioning for "hg summary"
7170 # i18n: column positioning for "hg summary"
7195 ui.write(_(b'update: %d new changesets (update)\n') % new)
7171 ui.write(_(b'update: %d new changesets (update)\n') % new)
7196 else:
7172 else:
7197 # i18n: column positioning for "hg summary"
7173 # i18n: column positioning for "hg summary"
7198 ui.write(
7174 ui.write(
7199 _(b'update: %d new changesets, %d branch heads (merge)\n')
7175 _(b'update: %d new changesets, %d branch heads (merge)\n')
7200 % (new, len(bheads))
7176 % (new, len(bheads))
7201 )
7177 )
7202
7178
7203 t = []
7179 t = []
7204 draft = len(repo.revs(b'draft()'))
7180 draft = len(repo.revs(b'draft()'))
7205 if draft:
7181 if draft:
7206 t.append(_(b'%d draft') % draft)
7182 t.append(_(b'%d draft') % draft)
7207 secret = len(repo.revs(b'secret()'))
7183 secret = len(repo.revs(b'secret()'))
7208 if secret:
7184 if secret:
7209 t.append(_(b'%d secret') % secret)
7185 t.append(_(b'%d secret') % secret)
7210
7186
7211 if draft or secret:
7187 if draft or secret:
7212 ui.status(_(b'phases: %s\n') % b', '.join(t))
7188 ui.status(_(b'phases: %s\n') % b', '.join(t))
7213
7189
7214 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7190 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7215 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7191 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7216 numtrouble = len(repo.revs(trouble + b"()"))
7192 numtrouble = len(repo.revs(trouble + b"()"))
7217 # We write all the possibilities to ease translation
7193 # We write all the possibilities to ease translation
7218 troublemsg = {
7194 troublemsg = {
7219 b"orphan": _(b"orphan: %d changesets"),
7195 b"orphan": _(b"orphan: %d changesets"),
7220 b"contentdivergent": _(b"content-divergent: %d changesets"),
7196 b"contentdivergent": _(b"content-divergent: %d changesets"),
7221 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7197 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7222 }
7198 }
7223 if numtrouble > 0:
7199 if numtrouble > 0:
7224 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7200 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7225
7201
7226 cmdutil.summaryhooks(ui, repo)
7202 cmdutil.summaryhooks(ui, repo)
7227
7203
7228 if opts.get(b'remote'):
7204 if opts.get(b'remote'):
7229 needsincoming, needsoutgoing = True, True
7205 needsincoming, needsoutgoing = True, True
7230 else:
7206 else:
7231 needsincoming, needsoutgoing = False, False
7207 needsincoming, needsoutgoing = False, False
7232 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7208 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7233 if i:
7209 if i:
7234 needsincoming = True
7210 needsincoming = True
7235 if o:
7211 if o:
7236 needsoutgoing = True
7212 needsoutgoing = True
7237 if not needsincoming and not needsoutgoing:
7213 if not needsincoming and not needsoutgoing:
7238 return
7214 return
7239
7215
7240 def getincoming():
7216 def getincoming():
7241 # XXX We should actually skip this if no default is specified, instead
7217 # XXX We should actually skip this if no default is specified, instead
7242 # of passing "default" which will resolve as "./default/" if no default
7218 # of passing "default" which will resolve as "./default/" if no default
7243 # path is defined.
7219 # path is defined.
7244 source, branches = urlutil.get_unique_pull_path(
7220 source, branches = urlutil.get_unique_pull_path(
7245 b'summary', repo, ui, b'default'
7221 b'summary', repo, ui, b'default'
7246 )
7222 )
7247 sbranch = branches[0]
7223 sbranch = branches[0]
7248 try:
7224 try:
7249 other = hg.peer(repo, {}, source)
7225 other = hg.peer(repo, {}, source)
7250 except error.RepoError:
7226 except error.RepoError:
7251 if opts.get(b'remote'):
7227 if opts.get(b'remote'):
7252 raise
7228 raise
7253 return source, sbranch, None, None, None
7229 return source, sbranch, None, None, None
7254 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7230 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7255 if revs:
7231 if revs:
7256 revs = [other.lookup(rev) for rev in revs]
7232 revs = [other.lookup(rev) for rev in revs]
7257 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7233 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7258 with repo.ui.silent():
7234 with repo.ui.silent():
7259 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7235 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7260 return source, sbranch, other, commoninc, commoninc[1]
7236 return source, sbranch, other, commoninc, commoninc[1]
7261
7237
7262 if needsincoming:
7238 if needsincoming:
7263 source, sbranch, sother, commoninc, incoming = getincoming()
7239 source, sbranch, sother, commoninc, incoming = getincoming()
7264 else:
7240 else:
7265 source = sbranch = sother = commoninc = incoming = None
7241 source = sbranch = sother = commoninc = incoming = None
7266
7242
7267 def getoutgoing():
7243 def getoutgoing():
7268 # XXX We should actually skip this if no default is specified, instead
7244 # XXX We should actually skip this if no default is specified, instead
7269 # of passing "default" which will resolve as "./default/" if no default
7245 # of passing "default" which will resolve as "./default/" if no default
7270 # path is defined.
7246 # path is defined.
7271 d = None
7247 d = None
7272 if b'default-push' in ui.paths:
7248 if b'default-push' in ui.paths:
7273 d = b'default-push'
7249 d = b'default-push'
7274 elif b'default' in ui.paths:
7250 elif b'default' in ui.paths:
7275 d = b'default'
7251 d = b'default'
7276 if d is not None:
7252 if d is not None:
7277 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7253 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7278 dest = path.pushloc or path.loc
7254 dest = path.pushloc or path.loc
7279 dbranch = path.branch
7255 dbranch = path.branch
7280 else:
7256 else:
7281 dest = b'default'
7257 dest = b'default'
7282 dbranch = None
7258 dbranch = None
7283 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7259 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7284 if source != dest:
7260 if source != dest:
7285 try:
7261 try:
7286 dother = hg.peer(repo, {}, dest)
7262 dother = hg.peer(repo, {}, dest)
7287 except error.RepoError:
7263 except error.RepoError:
7288 if opts.get(b'remote'):
7264 if opts.get(b'remote'):
7289 raise
7265 raise
7290 return dest, dbranch, None, None
7266 return dest, dbranch, None, None
7291 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7267 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7292 elif sother is None:
7268 elif sother is None:
7293 # there is no explicit destination peer, but source one is invalid
7269 # there is no explicit destination peer, but source one is invalid
7294 return dest, dbranch, None, None
7270 return dest, dbranch, None, None
7295 else:
7271 else:
7296 dother = sother
7272 dother = sother
7297 if source != dest or (sbranch is not None and sbranch != dbranch):
7273 if source != dest or (sbranch is not None and sbranch != dbranch):
7298 common = None
7274 common = None
7299 else:
7275 else:
7300 common = commoninc
7276 common = commoninc
7301 if revs:
7277 if revs:
7302 revs = [repo.lookup(rev) for rev in revs]
7278 revs = [repo.lookup(rev) for rev in revs]
7303 with repo.ui.silent():
7279 with repo.ui.silent():
7304 outgoing = discovery.findcommonoutgoing(
7280 outgoing = discovery.findcommonoutgoing(
7305 repo, dother, onlyheads=revs, commoninc=common
7281 repo, dother, onlyheads=revs, commoninc=common
7306 )
7282 )
7307 return dest, dbranch, dother, outgoing
7283 return dest, dbranch, dother, outgoing
7308
7284
7309 if needsoutgoing:
7285 if needsoutgoing:
7310 dest, dbranch, dother, outgoing = getoutgoing()
7286 dest, dbranch, dother, outgoing = getoutgoing()
7311 else:
7287 else:
7312 dest = dbranch = dother = outgoing = None
7288 dest = dbranch = dother = outgoing = None
7313
7289
7314 if opts.get(b'remote'):
7290 if opts.get(b'remote'):
7315 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7291 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7316 # The former always sets `sother` (or raises an exception if it can't);
7292 # The former always sets `sother` (or raises an exception if it can't);
7317 # the latter always sets `outgoing`.
7293 # the latter always sets `outgoing`.
7318 assert sother is not None
7294 assert sother is not None
7319 assert outgoing is not None
7295 assert outgoing is not None
7320
7296
7321 t = []
7297 t = []
7322 if incoming:
7298 if incoming:
7323 t.append(_(b'1 or more incoming'))
7299 t.append(_(b'1 or more incoming'))
7324 o = outgoing.missing
7300 o = outgoing.missing
7325 if o:
7301 if o:
7326 t.append(_(b'%d outgoing') % len(o))
7302 t.append(_(b'%d outgoing') % len(o))
7327 other = dother or sother
7303 other = dother or sother
7328 if b'bookmarks' in other.listkeys(b'namespaces'):
7304 if b'bookmarks' in other.listkeys(b'namespaces'):
7329 counts = bookmarks.summary(repo, other)
7305 counts = bookmarks.summary(repo, other)
7330 if counts[0] > 0:
7306 if counts[0] > 0:
7331 t.append(_(b'%d incoming bookmarks') % counts[0])
7307 t.append(_(b'%d incoming bookmarks') % counts[0])
7332 if counts[1] > 0:
7308 if counts[1] > 0:
7333 t.append(_(b'%d outgoing bookmarks') % counts[1])
7309 t.append(_(b'%d outgoing bookmarks') % counts[1])
7334
7310
7335 if t:
7311 if t:
7336 # i18n: column positioning for "hg summary"
7312 # i18n: column positioning for "hg summary"
7337 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7313 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7338 else:
7314 else:
7339 # i18n: column positioning for "hg summary"
7315 # i18n: column positioning for "hg summary"
7340 ui.status(_(b'remote: (synced)\n'))
7316 ui.status(_(b'remote: (synced)\n'))
7341
7317
7342 cmdutil.summaryremotehooks(
7318 cmdutil.summaryremotehooks(
7343 ui,
7319 ui,
7344 repo,
7320 repo,
7345 opts,
7321 opts,
7346 (
7322 (
7347 (source, sbranch, sother, commoninc),
7323 (source, sbranch, sother, commoninc),
7348 (dest, dbranch, dother, outgoing),
7324 (dest, dbranch, dother, outgoing),
7349 ),
7325 ),
7350 )
7326 )
7351
7327
7352
7328
7353 @command(
7329 @command(
7354 b'tag',
7330 b'tag',
7355 [
7331 [
7356 (b'f', b'force', None, _(b'force tag')),
7332 (b'f', b'force', None, _(b'force tag')),
7357 (b'l', b'local', None, _(b'make the tag local')),
7333 (b'l', b'local', None, _(b'make the tag local')),
7358 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7334 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7359 (b'', b'remove', None, _(b'remove a tag')),
7335 (b'', b'remove', None, _(b'remove a tag')),
7360 # -l/--local is already there, commitopts cannot be used
7336 # -l/--local is already there, commitopts cannot be used
7361 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7337 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7362 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7338 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7363 ]
7339 ]
7364 + commitopts2,
7340 + commitopts2,
7365 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7341 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7366 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7342 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7367 )
7343 )
7368 def tag(ui, repo, name1, *names, **opts):
7344 def tag(ui, repo, name1, *names, **opts):
7369 """add one or more tags for the current or given revision
7345 """add one or more tags for the current or given revision
7370
7346
7371 Name a particular revision using <name>.
7347 Name a particular revision using <name>.
7372
7348
7373 Tags are used to name particular revisions of the repository and are
7349 Tags are used to name particular revisions of the repository and are
7374 very useful to compare different revisions, to go back to significant
7350 very useful to compare different revisions, to go back to significant
7375 earlier versions or to mark branch points as releases, etc. Changing
7351 earlier versions or to mark branch points as releases, etc. Changing
7376 an existing tag is normally disallowed; use -f/--force to override.
7352 an existing tag is normally disallowed; use -f/--force to override.
7377
7353
7378 If no revision is given, the parent of the working directory is
7354 If no revision is given, the parent of the working directory is
7379 used.
7355 used.
7380
7356
7381 To facilitate version control, distribution, and merging of tags,
7357 To facilitate version control, distribution, and merging of tags,
7382 they are stored as a file named ".hgtags" which is managed similarly
7358 they are stored as a file named ".hgtags" which is managed similarly
7383 to other project files and can be hand-edited if necessary. This
7359 to other project files and can be hand-edited if necessary. This
7384 also means that tagging creates a new commit. The file
7360 also means that tagging creates a new commit. The file
7385 ".hg/localtags" is used for local tags (not shared among
7361 ".hg/localtags" is used for local tags (not shared among
7386 repositories).
7362 repositories).
7387
7363
7388 Tag commits are usually made at the head of a branch. If the parent
7364 Tag commits are usually made at the head of a branch. If the parent
7389 of the working directory is not a branch head, :hg:`tag` aborts; use
7365 of the working directory is not a branch head, :hg:`tag` aborts; use
7390 -f/--force to force the tag commit to be based on a non-head
7366 -f/--force to force the tag commit to be based on a non-head
7391 changeset.
7367 changeset.
7392
7368
7393 See :hg:`help dates` for a list of formats valid for -d/--date.
7369 See :hg:`help dates` for a list of formats valid for -d/--date.
7394
7370
7395 Since tag names have priority over branch names during revision
7371 Since tag names have priority over branch names during revision
7396 lookup, using an existing branch name as a tag name is discouraged.
7372 lookup, using an existing branch name as a tag name is discouraged.
7397
7373
7398 Returns 0 on success.
7374 Returns 0 on success.
7399 """
7375 """
7400 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7376 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7401 opts = pycompat.byteskwargs(opts)
7377 opts = pycompat.byteskwargs(opts)
7402 with repo.wlock(), repo.lock():
7378 with repo.wlock(), repo.lock():
7403 rev_ = b"."
7379 rev_ = b"."
7404 names = [t.strip() for t in (name1,) + names]
7380 names = [t.strip() for t in (name1,) + names]
7405 if len(names) != len(set(names)):
7381 if len(names) != len(set(names)):
7406 raise error.InputError(_(b'tag names must be unique'))
7382 raise error.InputError(_(b'tag names must be unique'))
7407 for n in names:
7383 for n in names:
7408 scmutil.checknewlabel(repo, n, b'tag')
7384 scmutil.checknewlabel(repo, n, b'tag')
7409 if not n:
7385 if not n:
7410 raise error.InputError(
7386 raise error.InputError(
7411 _(b'tag names cannot consist entirely of whitespace')
7387 _(b'tag names cannot consist entirely of whitespace')
7412 )
7388 )
7413 if opts.get(b'rev'):
7389 if opts.get(b'rev'):
7414 rev_ = opts[b'rev']
7390 rev_ = opts[b'rev']
7415 message = opts.get(b'message')
7391 message = opts.get(b'message')
7416 if opts.get(b'remove'):
7392 if opts.get(b'remove'):
7417 if opts.get(b'local'):
7393 if opts.get(b'local'):
7418 expectedtype = b'local'
7394 expectedtype = b'local'
7419 else:
7395 else:
7420 expectedtype = b'global'
7396 expectedtype = b'global'
7421
7397
7422 for n in names:
7398 for n in names:
7423 if repo.tagtype(n) == b'global':
7399 if repo.tagtype(n) == b'global':
7424 alltags = tagsmod.findglobaltags(ui, repo)
7400 alltags = tagsmod.findglobaltags(ui, repo)
7425 if alltags[n][0] == repo.nullid:
7401 if alltags[n][0] == repo.nullid:
7426 raise error.InputError(
7402 raise error.InputError(
7427 _(b"tag '%s' is already removed") % n
7403 _(b"tag '%s' is already removed") % n
7428 )
7404 )
7429 if not repo.tagtype(n):
7405 if not repo.tagtype(n):
7430 raise error.InputError(_(b"tag '%s' does not exist") % n)
7406 raise error.InputError(_(b"tag '%s' does not exist") % n)
7431 if repo.tagtype(n) != expectedtype:
7407 if repo.tagtype(n) != expectedtype:
7432 if expectedtype == b'global':
7408 if expectedtype == b'global':
7433 raise error.InputError(
7409 raise error.InputError(
7434 _(b"tag '%s' is not a global tag") % n
7410 _(b"tag '%s' is not a global tag") % n
7435 )
7411 )
7436 else:
7412 else:
7437 raise error.InputError(
7413 raise error.InputError(
7438 _(b"tag '%s' is not a local tag") % n
7414 _(b"tag '%s' is not a local tag") % n
7439 )
7415 )
7440 rev_ = b'null'
7416 rev_ = b'null'
7441 if not message:
7417 if not message:
7442 # we don't translate commit messages
7418 # we don't translate commit messages
7443 message = b'Removed tag %s' % b', '.join(names)
7419 message = b'Removed tag %s' % b', '.join(names)
7444 elif not opts.get(b'force'):
7420 elif not opts.get(b'force'):
7445 for n in names:
7421 for n in names:
7446 if n in repo.tags():
7422 if n in repo.tags():
7447 raise error.InputError(
7423 raise error.InputError(
7448 _(b"tag '%s' already exists (use -f to force)") % n
7424 _(b"tag '%s' already exists (use -f to force)") % n
7449 )
7425 )
7450 if not opts.get(b'local'):
7426 if not opts.get(b'local'):
7451 p1, p2 = repo.dirstate.parents()
7427 p1, p2 = repo.dirstate.parents()
7452 if p2 != repo.nullid:
7428 if p2 != repo.nullid:
7453 raise error.StateError(_(b'uncommitted merge'))
7429 raise error.StateError(_(b'uncommitted merge'))
7454 bheads = repo.branchheads()
7430 bheads = repo.branchheads()
7455 if not opts.get(b'force') and bheads and p1 not in bheads:
7431 if not opts.get(b'force') and bheads and p1 not in bheads:
7456 raise error.InputError(
7432 raise error.InputError(
7457 _(
7433 _(
7458 b'working directory is not at a branch head '
7434 b'working directory is not at a branch head '
7459 b'(use -f to force)'
7435 b'(use -f to force)'
7460 )
7436 )
7461 )
7437 )
7462 node = logcmdutil.revsingle(repo, rev_).node()
7438 node = logcmdutil.revsingle(repo, rev_).node()
7463
7439
7464 if not message:
7440 if not message:
7465 # we don't translate commit messages
7441 # we don't translate commit messages
7466 message = b'Added tag %s for changeset %s' % (
7442 message = b'Added tag %s for changeset %s' % (
7467 b', '.join(names),
7443 b', '.join(names),
7468 short(node),
7444 short(node),
7469 )
7445 )
7470
7446
7471 date = opts.get(b'date')
7447 date = opts.get(b'date')
7472 if date:
7448 if date:
7473 date = dateutil.parsedate(date)
7449 date = dateutil.parsedate(date)
7474
7450
7475 if opts.get(b'remove'):
7451 if opts.get(b'remove'):
7476 editform = b'tag.remove'
7452 editform = b'tag.remove'
7477 else:
7453 else:
7478 editform = b'tag.add'
7454 editform = b'tag.add'
7479 editor = cmdutil.getcommiteditor(
7455 editor = cmdutil.getcommiteditor(
7480 editform=editform, **pycompat.strkwargs(opts)
7456 editform=editform, **pycompat.strkwargs(opts)
7481 )
7457 )
7482
7458
7483 # don't allow tagging the null rev
7459 # don't allow tagging the null rev
7484 if (
7460 if (
7485 not opts.get(b'remove')
7461 not opts.get(b'remove')
7486 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7462 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7487 ):
7463 ):
7488 raise error.InputError(_(b"cannot tag null revision"))
7464 raise error.InputError(_(b"cannot tag null revision"))
7489
7465
7490 tagsmod.tag(
7466 tagsmod.tag(
7491 repo,
7467 repo,
7492 names,
7468 names,
7493 node,
7469 node,
7494 message,
7470 message,
7495 opts.get(b'local'),
7471 opts.get(b'local'),
7496 opts.get(b'user'),
7472 opts.get(b'user'),
7497 date,
7473 date,
7498 editor=editor,
7474 editor=editor,
7499 )
7475 )
7500
7476
7501
7477
7502 @command(
7478 @command(
7503 b'tags',
7479 b'tags',
7504 formatteropts,
7480 formatteropts,
7505 b'',
7481 b'',
7506 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7482 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7507 intents={INTENT_READONLY},
7483 intents={INTENT_READONLY},
7508 )
7484 )
7509 def tags(ui, repo, **opts):
7485 def tags(ui, repo, **opts):
7510 """list repository tags
7486 """list repository tags
7511
7487
7512 This lists both regular and local tags. When the -v/--verbose
7488 This lists both regular and local tags. When the -v/--verbose
7513 switch is used, a third column "local" is printed for local tags.
7489 switch is used, a third column "local" is printed for local tags.
7514 When the -q/--quiet switch is used, only the tag name is printed.
7490 When the -q/--quiet switch is used, only the tag name is printed.
7515
7491
7516 .. container:: verbose
7492 .. container:: verbose
7517
7493
7518 Template:
7494 Template:
7519
7495
7520 The following keywords are supported in addition to the common template
7496 The following keywords are supported in addition to the common template
7521 keywords and functions such as ``{tag}``. See also
7497 keywords and functions such as ``{tag}``. See also
7522 :hg:`help templates`.
7498 :hg:`help templates`.
7523
7499
7524 :type: String. ``local`` for local tags.
7500 :type: String. ``local`` for local tags.
7525
7501
7526 Returns 0 on success.
7502 Returns 0 on success.
7527 """
7503 """
7528
7504
7529 opts = pycompat.byteskwargs(opts)
7505 opts = pycompat.byteskwargs(opts)
7530 ui.pager(b'tags')
7506 ui.pager(b'tags')
7531 fm = ui.formatter(b'tags', opts)
7507 fm = ui.formatter(b'tags', opts)
7532 hexfunc = fm.hexfunc
7508 hexfunc = fm.hexfunc
7533
7509
7534 for t, n in reversed(repo.tagslist()):
7510 for t, n in reversed(repo.tagslist()):
7535 hn = hexfunc(n)
7511 hn = hexfunc(n)
7536 label = b'tags.normal'
7512 label = b'tags.normal'
7537 tagtype = repo.tagtype(t)
7513 tagtype = repo.tagtype(t)
7538 if not tagtype or tagtype == b'global':
7514 if not tagtype or tagtype == b'global':
7539 tagtype = b''
7515 tagtype = b''
7540 else:
7516 else:
7541 label = b'tags.' + tagtype
7517 label = b'tags.' + tagtype
7542
7518
7543 fm.startitem()
7519 fm.startitem()
7544 fm.context(repo=repo)
7520 fm.context(repo=repo)
7545 fm.write(b'tag', b'%s', t, label=label)
7521 fm.write(b'tag', b'%s', t, label=label)
7546 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7522 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7547 fm.condwrite(
7523 fm.condwrite(
7548 not ui.quiet,
7524 not ui.quiet,
7549 b'rev node',
7525 b'rev node',
7550 fmt,
7526 fmt,
7551 repo.changelog.rev(n),
7527 repo.changelog.rev(n),
7552 hn,
7528 hn,
7553 label=label,
7529 label=label,
7554 )
7530 )
7555 fm.condwrite(
7531 fm.condwrite(
7556 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7532 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7557 )
7533 )
7558 fm.plain(b'\n')
7534 fm.plain(b'\n')
7559 fm.end()
7535 fm.end()
7560
7536
7561
7537
7562 @command(
7538 @command(
7563 b'tip',
7539 b'tip',
7564 [
7540 [
7565 (b'p', b'patch', None, _(b'show patch')),
7541 (b'p', b'patch', None, _(b'show patch')),
7566 (b'g', b'git', None, _(b'use git extended diff format')),
7542 (b'g', b'git', None, _(b'use git extended diff format')),
7567 ]
7543 ]
7568 + templateopts,
7544 + templateopts,
7569 _(b'[-p] [-g]'),
7545 _(b'[-p] [-g]'),
7570 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7546 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7571 )
7547 )
7572 def tip(ui, repo, **opts):
7548 def tip(ui, repo, **opts):
7573 """show the tip revision (DEPRECATED)
7549 """show the tip revision (DEPRECATED)
7574
7550
7575 The tip revision (usually just called the tip) is the changeset
7551 The tip revision (usually just called the tip) is the changeset
7576 most recently added to the repository (and therefore the most
7552 most recently added to the repository (and therefore the most
7577 recently changed head).
7553 recently changed head).
7578
7554
7579 If you have just made a commit, that commit will be the tip. If
7555 If you have just made a commit, that commit will be the tip. If
7580 you have just pulled changes from another repository, the tip of
7556 you have just pulled changes from another repository, the tip of
7581 that repository becomes the current tip. The "tip" tag is special
7557 that repository becomes the current tip. The "tip" tag is special
7582 and cannot be renamed or assigned to a different changeset.
7558 and cannot be renamed or assigned to a different changeset.
7583
7559
7584 This command is deprecated, please use :hg:`heads` instead.
7560 This command is deprecated, please use :hg:`heads` instead.
7585
7561
7586 Returns 0 on success.
7562 Returns 0 on success.
7587 """
7563 """
7588 opts = pycompat.byteskwargs(opts)
7564 opts = pycompat.byteskwargs(opts)
7589 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7565 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7590 displayer.show(repo[b'tip'])
7566 displayer.show(repo[b'tip'])
7591 displayer.close()
7567 displayer.close()
7592
7568
7593
7569
7594 @command(
7570 @command(
7595 b'unbundle',
7571 b'unbundle',
7596 [
7572 [
7597 (
7573 (
7598 b'u',
7574 b'u',
7599 b'update',
7575 b'update',
7600 None,
7576 None,
7601 _(b'update to new branch head if changesets were unbundled'),
7577 _(b'update to new branch head if changesets were unbundled'),
7602 )
7578 )
7603 ],
7579 ],
7604 _(b'[-u] FILE...'),
7580 _(b'[-u] FILE...'),
7605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7581 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7606 )
7582 )
7607 def unbundle(ui, repo, fname1, *fnames, **opts):
7583 def unbundle(ui, repo, fname1, *fnames, **opts):
7608 """apply one or more bundle files
7584 """apply one or more bundle files
7609
7585
7610 Apply one or more bundle files generated by :hg:`bundle`.
7586 Apply one or more bundle files generated by :hg:`bundle`.
7611
7587
7612 Returns 0 on success, 1 if an update has unresolved files.
7588 Returns 0 on success, 1 if an update has unresolved files.
7613 """
7589 """
7614 fnames = (fname1,) + fnames
7590 fnames = (fname1,) + fnames
7615
7591
7616 with repo.lock():
7592 with repo.lock():
7617 for fname in fnames:
7593 for fname in fnames:
7618 f = hg.openpath(ui, fname)
7594 f = hg.openpath(ui, fname)
7619 gen = exchange.readbundle(ui, f, fname)
7595 gen = exchange.readbundle(ui, f, fname)
7620 if isinstance(gen, streamclone.streamcloneapplier):
7596 if isinstance(gen, streamclone.streamcloneapplier):
7621 raise error.InputError(
7597 raise error.InputError(
7622 _(
7598 _(
7623 b'packed bundles cannot be applied with '
7599 b'packed bundles cannot be applied with '
7624 b'"hg unbundle"'
7600 b'"hg unbundle"'
7625 ),
7601 ),
7626 hint=_(b'use "hg debugapplystreamclonebundle"'),
7602 hint=_(b'use "hg debugapplystreamclonebundle"'),
7627 )
7603 )
7628 url = b'bundle:' + fname
7604 url = b'bundle:' + fname
7629 try:
7605 try:
7630 txnname = b'unbundle'
7606 txnname = b'unbundle'
7631 if not isinstance(gen, bundle2.unbundle20):
7607 if not isinstance(gen, bundle2.unbundle20):
7632 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7608 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7633 with repo.transaction(txnname) as tr:
7609 with repo.transaction(txnname) as tr:
7634 op = bundle2.applybundle(
7610 op = bundle2.applybundle(
7635 repo, gen, tr, source=b'unbundle', url=url
7611 repo, gen, tr, source=b'unbundle', url=url
7636 )
7612 )
7637 except error.BundleUnknownFeatureError as exc:
7613 except error.BundleUnknownFeatureError as exc:
7638 raise error.Abort(
7614 raise error.Abort(
7639 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7615 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7640 hint=_(
7616 hint=_(
7641 b"see https://mercurial-scm.org/"
7617 b"see https://mercurial-scm.org/"
7642 b"wiki/BundleFeature for more "
7618 b"wiki/BundleFeature for more "
7643 b"information"
7619 b"information"
7644 ),
7620 ),
7645 )
7621 )
7646 modheads = bundle2.combinechangegroupresults(op)
7622 modheads = bundle2.combinechangegroupresults(op)
7647
7623
7648 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7624 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7649 return 1
7625 return 1
7650 else:
7626 else:
7651 return 0
7627 return 0
7652
7628
7653
7629
7654 @command(
7630 @command(
7655 b'unshelve',
7631 b'unshelve',
7656 [
7632 [
7657 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7633 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7658 (
7634 (
7659 b'c',
7635 b'c',
7660 b'continue',
7636 b'continue',
7661 None,
7637 None,
7662 _(b'continue an incomplete unshelve operation'),
7638 _(b'continue an incomplete unshelve operation'),
7663 ),
7639 ),
7664 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7640 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7665 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7641 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7666 (
7642 (
7667 b'n',
7643 b'n',
7668 b'name',
7644 b'name',
7669 b'',
7645 b'',
7670 _(b'restore shelved change with given name'),
7646 _(b'restore shelved change with given name'),
7671 _(b'NAME'),
7647 _(b'NAME'),
7672 ),
7648 ),
7673 (b't', b'tool', b'', _(b'specify merge tool')),
7649 (b't', b'tool', b'', _(b'specify merge tool')),
7674 (
7650 (
7675 b'',
7651 b'',
7676 b'date',
7652 b'date',
7677 b'',
7653 b'',
7678 _(b'set date for temporary commits (DEPRECATED)'),
7654 _(b'set date for temporary commits (DEPRECATED)'),
7679 _(b'DATE'),
7655 _(b'DATE'),
7680 ),
7656 ),
7681 ],
7657 ],
7682 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7658 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7659 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7684 )
7660 )
7685 def unshelve(ui, repo, *shelved, **opts):
7661 def unshelve(ui, repo, *shelved, **opts):
7686 """restore a shelved change to the working directory
7662 """restore a shelved change to the working directory
7687
7663
7688 This command accepts an optional name of a shelved change to
7664 This command accepts an optional name of a shelved change to
7689 restore. If none is given, the most recent shelved change is used.
7665 restore. If none is given, the most recent shelved change is used.
7690
7666
7691 If a shelved change is applied successfully, the bundle that
7667 If a shelved change is applied successfully, the bundle that
7692 contains the shelved changes is moved to a backup location
7668 contains the shelved changes is moved to a backup location
7693 (.hg/shelve-backup).
7669 (.hg/shelve-backup).
7694
7670
7695 Since you can restore a shelved change on top of an arbitrary
7671 Since you can restore a shelved change on top of an arbitrary
7696 commit, it is possible that unshelving will result in a conflict
7672 commit, it is possible that unshelving will result in a conflict
7697 between your changes and the commits you are unshelving onto. If
7673 between your changes and the commits you are unshelving onto. If
7698 this occurs, you must resolve the conflict, then use
7674 this occurs, you must resolve the conflict, then use
7699 ``--continue`` to complete the unshelve operation. (The bundle
7675 ``--continue`` to complete the unshelve operation. (The bundle
7700 will not be moved until you successfully complete the unshelve.)
7676 will not be moved until you successfully complete the unshelve.)
7701
7677
7702 (Alternatively, you can use ``--abort`` to abandon an unshelve
7678 (Alternatively, you can use ``--abort`` to abandon an unshelve
7703 that causes a conflict. This reverts the unshelved changes, and
7679 that causes a conflict. This reverts the unshelved changes, and
7704 leaves the bundle in place.)
7680 leaves the bundle in place.)
7705
7681
7706 If bare shelved change (without interactive, include and exclude
7682 If bare shelved change (without interactive, include and exclude
7707 option) was done on newly created branch it would restore branch
7683 option) was done on newly created branch it would restore branch
7708 information to the working directory.
7684 information to the working directory.
7709
7685
7710 After a successful unshelve, the shelved changes are stored in a
7686 After a successful unshelve, the shelved changes are stored in a
7711 backup directory. Only the N most recent backups are kept. N
7687 backup directory. Only the N most recent backups are kept. N
7712 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7688 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7713 configuration option.
7689 configuration option.
7714
7690
7715 .. container:: verbose
7691 .. container:: verbose
7716
7692
7717 Timestamp in seconds is used to decide order of backups. More
7693 Timestamp in seconds is used to decide order of backups. More
7718 than ``maxbackups`` backups are kept, if same timestamp
7694 than ``maxbackups`` backups are kept, if same timestamp
7719 prevents from deciding exact order of them, for safety.
7695 prevents from deciding exact order of them, for safety.
7720
7696
7721 Selected changes can be unshelved with ``--interactive`` flag.
7697 Selected changes can be unshelved with ``--interactive`` flag.
7722 The working directory is updated with the selected changes, and
7698 The working directory is updated with the selected changes, and
7723 only the unselected changes remain shelved.
7699 only the unselected changes remain shelved.
7724 Note: The whole shelve is applied to working directory first before
7700 Note: The whole shelve is applied to working directory first before
7725 running interactively. So, this will bring up all the conflicts between
7701 running interactively. So, this will bring up all the conflicts between
7726 working directory and the shelve, irrespective of which changes will be
7702 working directory and the shelve, irrespective of which changes will be
7727 unshelved.
7703 unshelved.
7728 """
7704 """
7729 with repo.wlock():
7705 with repo.wlock():
7730 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7706 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7731
7707
7732
7708
7733 statemod.addunfinished(
7709 statemod.addunfinished(
7734 b'unshelve',
7710 b'unshelve',
7735 fname=b'shelvedstate',
7711 fname=b'shelvedstate',
7736 continueflag=True,
7712 continueflag=True,
7737 abortfunc=shelvemod.hgabortunshelve,
7713 abortfunc=shelvemod.hgabortunshelve,
7738 continuefunc=shelvemod.hgcontinueunshelve,
7714 continuefunc=shelvemod.hgcontinueunshelve,
7739 cmdmsg=_(b'unshelve already in progress'),
7715 cmdmsg=_(b'unshelve already in progress'),
7740 )
7716 )
7741
7717
7742
7718
7743 @command(
7719 @command(
7744 b'update|up|checkout|co',
7720 b'update|up|checkout|co',
7745 [
7721 [
7746 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7722 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7747 (b'c', b'check', None, _(b'require clean working directory')),
7723 (b'c', b'check', None, _(b'require clean working directory')),
7748 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7724 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7749 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7725 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7750 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7726 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7751 ]
7727 ]
7752 + mergetoolopts,
7728 + mergetoolopts,
7753 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7729 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7754 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7730 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7755 helpbasic=True,
7731 helpbasic=True,
7756 )
7732 )
7757 def update(ui, repo, node=None, **opts):
7733 def update(ui, repo, node=None, **opts):
7758 """update working directory (or switch revisions)
7734 """update working directory (or switch revisions)
7759
7735
7760 Update the repository's working directory to the specified
7736 Update the repository's working directory to the specified
7761 changeset. If no changeset is specified, update to the tip of the
7737 changeset. If no changeset is specified, update to the tip of the
7762 current named branch and move the active bookmark (see :hg:`help
7738 current named branch and move the active bookmark (see :hg:`help
7763 bookmarks`).
7739 bookmarks`).
7764
7740
7765 Update sets the working directory's parent revision to the specified
7741 Update sets the working directory's parent revision to the specified
7766 changeset (see :hg:`help parents`).
7742 changeset (see :hg:`help parents`).
7767
7743
7768 If the changeset is not a descendant or ancestor of the working
7744 If the changeset is not a descendant or ancestor of the working
7769 directory's parent and there are uncommitted changes, the update is
7745 directory's parent and there are uncommitted changes, the update is
7770 aborted. With the -c/--check option, the working directory is checked
7746 aborted. With the -c/--check option, the working directory is checked
7771 for uncommitted changes; if none are found, the working directory is
7747 for uncommitted changes; if none are found, the working directory is
7772 updated to the specified changeset.
7748 updated to the specified changeset.
7773
7749
7774 .. container:: verbose
7750 .. container:: verbose
7775
7751
7776 The -C/--clean, -c/--check, and -m/--merge options control what
7752 The -C/--clean, -c/--check, and -m/--merge options control what
7777 happens if the working directory contains uncommitted changes.
7753 happens if the working directory contains uncommitted changes.
7778 At most of one of them can be specified.
7754 At most of one of them can be specified.
7779
7755
7780 1. If no option is specified, and if
7756 1. If no option is specified, and if
7781 the requested changeset is an ancestor or descendant of
7757 the requested changeset is an ancestor or descendant of
7782 the working directory's parent, the uncommitted changes
7758 the working directory's parent, the uncommitted changes
7783 are merged into the requested changeset and the merged
7759 are merged into the requested changeset and the merged
7784 result is left uncommitted. If the requested changeset is
7760 result is left uncommitted. If the requested changeset is
7785 not an ancestor or descendant (that is, it is on another
7761 not an ancestor or descendant (that is, it is on another
7786 branch), the update is aborted and the uncommitted changes
7762 branch), the update is aborted and the uncommitted changes
7787 are preserved.
7763 are preserved.
7788
7764
7789 2. With the -m/--merge option, the update is allowed even if the
7765 2. With the -m/--merge option, the update is allowed even if the
7790 requested changeset is not an ancestor or descendant of
7766 requested changeset is not an ancestor or descendant of
7791 the working directory's parent.
7767 the working directory's parent.
7792
7768
7793 3. With the -c/--check option, the update is aborted and the
7769 3. With the -c/--check option, the update is aborted and the
7794 uncommitted changes are preserved.
7770 uncommitted changes are preserved.
7795
7771
7796 4. With the -C/--clean option, uncommitted changes are discarded and
7772 4. With the -C/--clean option, uncommitted changes are discarded and
7797 the working directory is updated to the requested changeset.
7773 the working directory is updated to the requested changeset.
7798
7774
7799 To cancel an uncommitted merge (and lose your changes), use
7775 To cancel an uncommitted merge (and lose your changes), use
7800 :hg:`merge --abort`.
7776 :hg:`merge --abort`.
7801
7777
7802 Use null as the changeset to remove the working directory (like
7778 Use null as the changeset to remove the working directory (like
7803 :hg:`clone -U`).
7779 :hg:`clone -U`).
7804
7780
7805 If you want to revert just one file to an older revision, use
7781 If you want to revert just one file to an older revision, use
7806 :hg:`revert [-r REV] NAME`.
7782 :hg:`revert [-r REV] NAME`.
7807
7783
7808 See :hg:`help dates` for a list of formats valid for -d/--date.
7784 See :hg:`help dates` for a list of formats valid for -d/--date.
7809
7785
7810 Returns 0 on success, 1 if there are unresolved files.
7786 Returns 0 on success, 1 if there are unresolved files.
7811 """
7787 """
7812 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7788 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7813 rev = opts.get('rev')
7789 rev = opts.get('rev')
7814 date = opts.get('date')
7790 date = opts.get('date')
7815 clean = opts.get('clean')
7791 clean = opts.get('clean')
7816 check = opts.get('check')
7792 check = opts.get('check')
7817 merge = opts.get('merge')
7793 merge = opts.get('merge')
7818 if rev and node:
7794 if rev and node:
7819 raise error.InputError(_(b"please specify just one revision"))
7795 raise error.InputError(_(b"please specify just one revision"))
7820
7796
7821 if ui.configbool(b'commands', b'update.requiredest'):
7797 if ui.configbool(b'commands', b'update.requiredest'):
7822 if not node and not rev and not date:
7798 if not node and not rev and not date:
7823 raise error.InputError(
7799 raise error.InputError(
7824 _(b'you must specify a destination'),
7800 _(b'you must specify a destination'),
7825 hint=_(b'for example: hg update ".::"'),
7801 hint=_(b'for example: hg update ".::"'),
7826 )
7802 )
7827
7803
7828 if rev is None or rev == b'':
7804 if rev is None or rev == b'':
7829 rev = node
7805 rev = node
7830
7806
7831 if date and rev is not None:
7807 if date and rev is not None:
7832 raise error.InputError(_(b"you can't specify a revision and a date"))
7808 raise error.InputError(_(b"you can't specify a revision and a date"))
7833
7809
7834 updatecheck = None
7810 updatecheck = None
7835 if check or merge is not None and not merge:
7811 if check or merge is not None and not merge:
7836 updatecheck = b'abort'
7812 updatecheck = b'abort'
7837 elif merge or check is not None and not check:
7813 elif merge or check is not None and not check:
7838 updatecheck = b'none'
7814 updatecheck = b'none'
7839
7815
7840 with repo.wlock():
7816 with repo.wlock():
7841 cmdutil.clearunfinished(repo)
7817 cmdutil.clearunfinished(repo)
7842 if date:
7818 if date:
7843 rev = cmdutil.finddate(ui, repo, date)
7819 rev = cmdutil.finddate(ui, repo, date)
7844
7820
7845 # if we defined a bookmark, we have to remember the original name
7821 # if we defined a bookmark, we have to remember the original name
7846 brev = rev
7822 brev = rev
7847 if rev:
7823 if rev:
7848 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7824 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7849 ctx = logcmdutil.revsingle(repo, rev, default=None)
7825 ctx = logcmdutil.revsingle(repo, rev, default=None)
7850 rev = ctx.rev()
7826 rev = ctx.rev()
7851 hidden = ctx.hidden()
7827 hidden = ctx.hidden()
7852 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7828 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7853 with ui.configoverride(overrides, b'update'):
7829 with ui.configoverride(overrides, b'update'):
7854 ret = hg.updatetotally(
7830 ret = hg.updatetotally(
7855 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7831 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7856 )
7832 )
7857 if hidden:
7833 if hidden:
7858 ctxstr = ctx.hex()[:12]
7834 ctxstr = ctx.hex()[:12]
7859 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7835 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7860
7836
7861 if ctx.obsolete():
7837 if ctx.obsolete():
7862 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7838 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7863 ui.warn(b"(%s)\n" % obsfatemsg)
7839 ui.warn(b"(%s)\n" % obsfatemsg)
7864 return ret
7840 return ret
7865
7841
7866
7842
7867 @command(
7843 @command(
7868 b'verify',
7844 b'verify',
7869 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7845 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7870 helpcategory=command.CATEGORY_MAINTENANCE,
7846 helpcategory=command.CATEGORY_MAINTENANCE,
7871 )
7847 )
7872 def verify(ui, repo, **opts):
7848 def verify(ui, repo, **opts):
7873 """verify the integrity of the repository
7849 """verify the integrity of the repository
7874
7850
7875 Verify the integrity of the current repository.
7851 Verify the integrity of the current repository.
7876
7852
7877 This will perform an extensive check of the repository's
7853 This will perform an extensive check of the repository's
7878 integrity, validating the hashes and checksums of each entry in
7854 integrity, validating the hashes and checksums of each entry in
7879 the changelog, manifest, and tracked files, as well as the
7855 the changelog, manifest, and tracked files, as well as the
7880 integrity of their crosslinks and indices.
7856 integrity of their crosslinks and indices.
7881
7857
7882 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7858 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7883 for more information about recovery from corruption of the
7859 for more information about recovery from corruption of the
7884 repository.
7860 repository.
7885
7861
7886 Returns 0 on success, 1 if errors are encountered.
7862 Returns 0 on success, 1 if errors are encountered.
7887 """
7863 """
7888 opts = pycompat.byteskwargs(opts)
7864 opts = pycompat.byteskwargs(opts)
7889
7865
7890 level = None
7866 level = None
7891 if opts[b'full']:
7867 if opts[b'full']:
7892 level = verifymod.VERIFY_FULL
7868 level = verifymod.VERIFY_FULL
7893 return hg.verify(repo, level)
7869 return hg.verify(repo, level)
7894
7870
7895
7871
7896 @command(
7872 @command(
7897 b'version',
7873 b'version',
7898 [] + formatteropts,
7874 [] + formatteropts,
7899 helpcategory=command.CATEGORY_HELP,
7875 helpcategory=command.CATEGORY_HELP,
7900 norepo=True,
7876 norepo=True,
7901 intents={INTENT_READONLY},
7877 intents={INTENT_READONLY},
7902 )
7878 )
7903 def version_(ui, **opts):
7879 def version_(ui, **opts):
7904 """output version and copyright information
7880 """output version and copyright information
7905
7881
7906 .. container:: verbose
7882 .. container:: verbose
7907
7883
7908 Template:
7884 Template:
7909
7885
7910 The following keywords are supported. See also :hg:`help templates`.
7886 The following keywords are supported. See also :hg:`help templates`.
7911
7887
7912 :extensions: List of extensions.
7888 :extensions: List of extensions.
7913 :ver: String. Version number.
7889 :ver: String. Version number.
7914
7890
7915 And each entry of ``{extensions}`` provides the following sub-keywords
7891 And each entry of ``{extensions}`` provides the following sub-keywords
7916 in addition to ``{ver}``.
7892 in addition to ``{ver}``.
7917
7893
7918 :bundled: Boolean. True if included in the release.
7894 :bundled: Boolean. True if included in the release.
7919 :name: String. Extension name.
7895 :name: String. Extension name.
7920 """
7896 """
7921 opts = pycompat.byteskwargs(opts)
7897 opts = pycompat.byteskwargs(opts)
7922 if ui.verbose:
7898 if ui.verbose:
7923 ui.pager(b'version')
7899 ui.pager(b'version')
7924 fm = ui.formatter(b"version", opts)
7900 fm = ui.formatter(b"version", opts)
7925 fm.startitem()
7901 fm.startitem()
7926 fm.write(
7902 fm.write(
7927 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7903 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7928 )
7904 )
7929 license = _(
7905 license = _(
7930 b"(see https://mercurial-scm.org for more information)\n"
7906 b"(see https://mercurial-scm.org for more information)\n"
7931 b"\nCopyright (C) 2005-2021 Olivia Mackall and others\n"
7907 b"\nCopyright (C) 2005-2021 Olivia Mackall and others\n"
7932 b"This is free software; see the source for copying conditions. "
7908 b"This is free software; see the source for copying conditions. "
7933 b"There is NO\nwarranty; "
7909 b"There is NO\nwarranty; "
7934 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7910 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7935 )
7911 )
7936 if not ui.quiet:
7912 if not ui.quiet:
7937 fm.plain(license)
7913 fm.plain(license)
7938
7914
7939 if ui.verbose:
7915 if ui.verbose:
7940 fm.plain(_(b"\nEnabled extensions:\n\n"))
7916 fm.plain(_(b"\nEnabled extensions:\n\n"))
7941 # format names and versions into columns
7917 # format names and versions into columns
7942 names = []
7918 names = []
7943 vers = []
7919 vers = []
7944 isinternals = []
7920 isinternals = []
7945 for name, module in sorted(extensions.extensions()):
7921 for name, module in sorted(extensions.extensions()):
7946 names.append(name)
7922 names.append(name)
7947 vers.append(extensions.moduleversion(module) or None)
7923 vers.append(extensions.moduleversion(module) or None)
7948 isinternals.append(extensions.ismoduleinternal(module))
7924 isinternals.append(extensions.ismoduleinternal(module))
7949 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7925 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7950 if names:
7926 if names:
7951 namefmt = b" %%-%ds " % max(len(n) for n in names)
7927 namefmt = b" %%-%ds " % max(len(n) for n in names)
7952 places = [_(b"external"), _(b"internal")]
7928 places = [_(b"external"), _(b"internal")]
7953 for n, v, p in zip(names, vers, isinternals):
7929 for n, v, p in zip(names, vers, isinternals):
7954 fn.startitem()
7930 fn.startitem()
7955 fn.condwrite(ui.verbose, b"name", namefmt, n)
7931 fn.condwrite(ui.verbose, b"name", namefmt, n)
7956 if ui.verbose:
7932 if ui.verbose:
7957 fn.plain(b"%s " % places[p])
7933 fn.plain(b"%s " % places[p])
7958 fn.data(bundled=p)
7934 fn.data(bundled=p)
7959 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7935 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7960 if ui.verbose:
7936 if ui.verbose:
7961 fn.plain(b"\n")
7937 fn.plain(b"\n")
7962 fn.end()
7938 fn.end()
7963 fm.end()
7939 fm.end()
7964
7940
7965
7941
7966 def loadcmdtable(ui, name, cmdtable):
7942 def loadcmdtable(ui, name, cmdtable):
7967 """Load command functions from specified cmdtable"""
7943 """Load command functions from specified cmdtable"""
7968 overrides = [cmd for cmd in cmdtable if cmd in table]
7944 overrides = [cmd for cmd in cmdtable if cmd in table]
7969 if overrides:
7945 if overrides:
7970 ui.warn(
7946 ui.warn(
7971 _(b"extension '%s' overrides commands: %s\n")
7947 _(b"extension '%s' overrides commands: %s\n")
7972 % (name, b" ".join(overrides))
7948 % (name, b" ".join(overrides))
7973 )
7949 )
7974 table.update(cmdtable)
7950 table.update(cmdtable)
@@ -1,2527 +1,2515 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 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import errno
11 import errno
12 import struct
12 import struct
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import nullrev
15 from .node import nullrev
16 from .thirdparty import attr
16 from .thirdparty import attr
17 from .utils import stringutil
17 from .utils import stringutil
18 from .dirstateutils import timestamp
18 from .dirstateutils import timestamp
19 from . import (
19 from . import (
20 copies,
20 copies,
21 encoding,
21 encoding,
22 error,
22 error,
23 filemerge,
23 filemerge,
24 match as matchmod,
24 match as matchmod,
25 mergestate as mergestatemod,
25 mergestate as mergestatemod,
26 obsutil,
26 obsutil,
27 pathutil,
27 pathutil,
28 pycompat,
28 pycompat,
29 scmutil,
29 scmutil,
30 subrepoutil,
30 subrepoutil,
31 util,
31 util,
32 worker,
32 worker,
33 )
33 )
34
34
35 _pack = struct.pack
35 _pack = struct.pack
36 _unpack = struct.unpack
36 _unpack = struct.unpack
37
37
38
38
39 def _getcheckunknownconfig(repo, section, name):
39 def _getcheckunknownconfig(repo, section, name):
40 config = repo.ui.config(section, name)
40 config = repo.ui.config(section, name)
41 valid = [b'abort', b'ignore', b'warn']
41 valid = [b'abort', b'ignore', b'warn']
42 if config not in valid:
42 if config not in valid:
43 validstr = b', '.join([b"'" + v + b"'" for v in valid])
43 validstr = b', '.join([b"'" + v + b"'" for v in valid])
44 raise error.ConfigError(
44 raise error.ConfigError(
45 _(b"%s.%s not valid ('%s' is none of %s)")
45 _(b"%s.%s not valid ('%s' is none of %s)")
46 % (section, name, config, validstr)
46 % (section, name, config, validstr)
47 )
47 )
48 return config
48 return config
49
49
50
50
51 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
51 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
52 if wctx.isinmemory():
52 if wctx.isinmemory():
53 # Nothing to do in IMM because nothing in the "working copy" can be an
53 # Nothing to do in IMM because nothing in the "working copy" can be an
54 # unknown file.
54 # unknown file.
55 #
55 #
56 # Note that we should bail out here, not in ``_checkunknownfiles()``,
56 # Note that we should bail out here, not in ``_checkunknownfiles()``,
57 # because that function does other useful work.
57 # because that function does other useful work.
58 return False
58 return False
59
59
60 if f2 is None:
60 if f2 is None:
61 f2 = f
61 f2 = f
62 return (
62 return (
63 repo.wvfs.audit.check(f)
63 repo.wvfs.audit.check(f)
64 and repo.wvfs.isfileorlink(f)
64 and repo.wvfs.isfileorlink(f)
65 and repo.dirstate.normalize(f) not in repo.dirstate
65 and repo.dirstate.normalize(f) not in repo.dirstate
66 and mctx[f2].cmp(wctx[f])
66 and mctx[f2].cmp(wctx[f])
67 )
67 )
68
68
69
69
70 class _unknowndirschecker(object):
70 class _unknowndirschecker(object):
71 """
71 """
72 Look for any unknown files or directories that may have a path conflict
72 Look for any unknown files or directories that may have a path conflict
73 with a file. If any path prefix of the file exists as a file or link,
73 with a file. If any path prefix of the file exists as a file or link,
74 then it conflicts. If the file itself is a directory that contains any
74 then it conflicts. If the file itself is a directory that contains any
75 file that is not tracked, then it conflicts.
75 file that is not tracked, then it conflicts.
76
76
77 Returns the shortest path at which a conflict occurs, or None if there is
77 Returns the shortest path at which a conflict occurs, or None if there is
78 no conflict.
78 no conflict.
79 """
79 """
80
80
81 def __init__(self):
81 def __init__(self):
82 # A set of paths known to be good. This prevents repeated checking of
82 # A set of paths known to be good. This prevents repeated checking of
83 # dirs. It will be updated with any new dirs that are checked and found
83 # dirs. It will be updated with any new dirs that are checked and found
84 # to be safe.
84 # to be safe.
85 self._unknowndircache = set()
85 self._unknowndircache = set()
86
86
87 # A set of paths that are known to be absent. This prevents repeated
87 # A set of paths that are known to be absent. This prevents repeated
88 # checking of subdirectories that are known not to exist. It will be
88 # checking of subdirectories that are known not to exist. It will be
89 # updated with any new dirs that are checked and found to be absent.
89 # updated with any new dirs that are checked and found to be absent.
90 self._missingdircache = set()
90 self._missingdircache = set()
91
91
92 def __call__(self, repo, wctx, f):
92 def __call__(self, repo, wctx, f):
93 if wctx.isinmemory():
93 if wctx.isinmemory():
94 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
94 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
95 return False
95 return False
96
96
97 # Check for path prefixes that exist as unknown files.
97 # Check for path prefixes that exist as unknown files.
98 for p in reversed(list(pathutil.finddirs(f))):
98 for p in reversed(list(pathutil.finddirs(f))):
99 if p in self._missingdircache:
99 if p in self._missingdircache:
100 return
100 return
101 if p in self._unknowndircache:
101 if p in self._unknowndircache:
102 continue
102 continue
103 if repo.wvfs.audit.check(p):
103 if repo.wvfs.audit.check(p):
104 if (
104 if (
105 repo.wvfs.isfileorlink(p)
105 repo.wvfs.isfileorlink(p)
106 and repo.dirstate.normalize(p) not in repo.dirstate
106 and repo.dirstate.normalize(p) not in repo.dirstate
107 ):
107 ):
108 return p
108 return p
109 if not repo.wvfs.lexists(p):
109 if not repo.wvfs.lexists(p):
110 self._missingdircache.add(p)
110 self._missingdircache.add(p)
111 return
111 return
112 self._unknowndircache.add(p)
112 self._unknowndircache.add(p)
113
113
114 # Check if the file conflicts with a directory containing unknown files.
114 # Check if the file conflicts with a directory containing unknown files.
115 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
115 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
116 # Does the directory contain any files that are not in the dirstate?
116 # Does the directory contain any files that are not in the dirstate?
117 for p, dirs, files in repo.wvfs.walk(f):
117 for p, dirs, files in repo.wvfs.walk(f):
118 for fn in files:
118 for fn in files:
119 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
119 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
120 relf = repo.dirstate.normalize(relf, isknown=True)
120 relf = repo.dirstate.normalize(relf, isknown=True)
121 if relf not in repo.dirstate:
121 if relf not in repo.dirstate:
122 return f
122 return f
123 return None
123 return None
124
124
125
125
126 def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce):
126 def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce):
127 """
127 """
128 Considers any actions that care about the presence of conflicting unknown
128 Considers any actions that care about the presence of conflicting unknown
129 files. For some actions, the result is to abort; for others, it is to
129 files. For some actions, the result is to abort; for others, it is to
130 choose a different action.
130 choose a different action.
131 """
131 """
132 fileconflicts = set()
132 fileconflicts = set()
133 pathconflicts = set()
133 pathconflicts = set()
134 warnconflicts = set()
134 warnconflicts = set()
135 abortconflicts = set()
135 abortconflicts = set()
136 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
136 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
137 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
137 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
138 pathconfig = repo.ui.configbool(
138 pathconfig = repo.ui.configbool(
139 b'experimental', b'merge.checkpathconflicts'
139 b'experimental', b'merge.checkpathconflicts'
140 )
140 )
141 if not force:
141 if not force:
142
142
143 def collectconflicts(conflicts, config):
143 def collectconflicts(conflicts, config):
144 if config == b'abort':
144 if config == b'abort':
145 abortconflicts.update(conflicts)
145 abortconflicts.update(conflicts)
146 elif config == b'warn':
146 elif config == b'warn':
147 warnconflicts.update(conflicts)
147 warnconflicts.update(conflicts)
148
148
149 checkunknowndirs = _unknowndirschecker()
149 checkunknowndirs = _unknowndirschecker()
150 for f in mresult.files(
150 for f in mresult.files(
151 (
151 (
152 mergestatemod.ACTION_CREATED,
152 mergestatemod.ACTION_CREATED,
153 mergestatemod.ACTION_DELETED_CHANGED,
153 mergestatemod.ACTION_DELETED_CHANGED,
154 )
154 )
155 ):
155 ):
156 if _checkunknownfile(repo, wctx, mctx, f):
156 if _checkunknownfile(repo, wctx, mctx, f):
157 fileconflicts.add(f)
157 fileconflicts.add(f)
158 elif pathconfig and f not in wctx:
158 elif pathconfig and f not in wctx:
159 path = checkunknowndirs(repo, wctx, f)
159 path = checkunknowndirs(repo, wctx, f)
160 if path is not None:
160 if path is not None:
161 pathconflicts.add(path)
161 pathconflicts.add(path)
162 for f, args, msg in mresult.getactions(
162 for f, args, msg in mresult.getactions(
163 [mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]
163 [mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]
164 ):
164 ):
165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
166 fileconflicts.add(f)
166 fileconflicts.add(f)
167
167
168 allconflicts = fileconflicts | pathconflicts
168 allconflicts = fileconflicts | pathconflicts
169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
170 unknownconflicts = allconflicts - ignoredconflicts
170 unknownconflicts = allconflicts - ignoredconflicts
171 collectconflicts(ignoredconflicts, ignoredconfig)
171 collectconflicts(ignoredconflicts, ignoredconfig)
172 collectconflicts(unknownconflicts, unknownconfig)
172 collectconflicts(unknownconflicts, unknownconfig)
173 else:
173 else:
174 for f, args, msg in list(
174 for f, args, msg in list(
175 mresult.getactions([mergestatemod.ACTION_CREATED_MERGE])
175 mresult.getactions([mergestatemod.ACTION_CREATED_MERGE])
176 ):
176 ):
177 fl2, anc = args
177 fl2, anc = args
178 different = _checkunknownfile(repo, wctx, mctx, f)
178 different = _checkunknownfile(repo, wctx, mctx, f)
179 if repo.dirstate._ignore(f):
179 if repo.dirstate._ignore(f):
180 config = ignoredconfig
180 config = ignoredconfig
181 else:
181 else:
182 config = unknownconfig
182 config = unknownconfig
183
183
184 # The behavior when force is True is described by this table:
184 # The behavior when force is True is described by this table:
185 # config different mergeforce | action backup
185 # config different mergeforce | action backup
186 # * n * | get n
186 # * n * | get n
187 # * y y | merge -
187 # * y y | merge -
188 # abort y n | merge - (1)
188 # abort y n | merge - (1)
189 # warn y n | warn + get y
189 # warn y n | warn + get y
190 # ignore y n | get y
190 # ignore y n | get y
191 #
191 #
192 # (1) this is probably the wrong behavior here -- we should
192 # (1) this is probably the wrong behavior here -- we should
193 # probably abort, but some actions like rebases currently
193 # probably abort, but some actions like rebases currently
194 # don't like an abort happening in the middle of
194 # don't like an abort happening in the middle of
195 # merge.update.
195 # merge.update.
196 if not different:
196 if not different:
197 mresult.addfile(
197 mresult.addfile(
198 f,
198 f,
199 mergestatemod.ACTION_GET,
199 mergestatemod.ACTION_GET,
200 (fl2, False),
200 (fl2, False),
201 b'remote created',
201 b'remote created',
202 )
202 )
203 elif mergeforce or config == b'abort':
203 elif mergeforce or config == b'abort':
204 mresult.addfile(
204 mresult.addfile(
205 f,
205 f,
206 mergestatemod.ACTION_MERGE,
206 mergestatemod.ACTION_MERGE,
207 (f, f, None, False, anc),
207 (f, f, None, False, anc),
208 b'remote differs from untracked local',
208 b'remote differs from untracked local',
209 )
209 )
210 elif config == b'abort':
210 elif config == b'abort':
211 abortconflicts.add(f)
211 abortconflicts.add(f)
212 else:
212 else:
213 if config == b'warn':
213 if config == b'warn':
214 warnconflicts.add(f)
214 warnconflicts.add(f)
215 mresult.addfile(
215 mresult.addfile(
216 f,
216 f,
217 mergestatemod.ACTION_GET,
217 mergestatemod.ACTION_GET,
218 (fl2, True),
218 (fl2, True),
219 b'remote created',
219 b'remote created',
220 )
220 )
221
221
222 for f in sorted(abortconflicts):
222 for f in sorted(abortconflicts):
223 warn = repo.ui.warn
223 warn = repo.ui.warn
224 if f in pathconflicts:
224 if f in pathconflicts:
225 if repo.wvfs.isfileorlink(f):
225 if repo.wvfs.isfileorlink(f):
226 warn(_(b"%s: untracked file conflicts with directory\n") % f)
226 warn(_(b"%s: untracked file conflicts with directory\n") % f)
227 else:
227 else:
228 warn(_(b"%s: untracked directory conflicts with file\n") % f)
228 warn(_(b"%s: untracked directory conflicts with file\n") % f)
229 else:
229 else:
230 warn(_(b"%s: untracked file differs\n") % f)
230 warn(_(b"%s: untracked file differs\n") % f)
231 if abortconflicts:
231 if abortconflicts:
232 raise error.StateError(
232 raise error.StateError(
233 _(
233 _(
234 b"untracked files in working directory "
234 b"untracked files in working directory "
235 b"differ from files in requested revision"
235 b"differ from files in requested revision"
236 )
236 )
237 )
237 )
238
238
239 for f in sorted(warnconflicts):
239 for f in sorted(warnconflicts):
240 if repo.wvfs.isfileorlink(f):
240 if repo.wvfs.isfileorlink(f):
241 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
241 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
242 else:
242 else:
243 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
243 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
244
244
245 for f, args, msg in list(
245 for f, args, msg in list(
246 mresult.getactions([mergestatemod.ACTION_CREATED])
246 mresult.getactions([mergestatemod.ACTION_CREATED])
247 ):
247 ):
248 backup = (
248 backup = (
249 f in fileconflicts
249 f in fileconflicts
250 or f in pathconflicts
250 or f in pathconflicts
251 or any(p in pathconflicts for p in pathutil.finddirs(f))
251 or any(p in pathconflicts for p in pathutil.finddirs(f))
252 )
252 )
253 (flags,) = args
253 (flags,) = args
254 mresult.addfile(f, mergestatemod.ACTION_GET, (flags, backup), msg)
254 mresult.addfile(f, mergestatemod.ACTION_GET, (flags, backup), msg)
255
255
256
256
257 def _forgetremoved(wctx, mctx, branchmerge, mresult):
257 def _forgetremoved(wctx, mctx, branchmerge, mresult):
258 """
258 """
259 Forget removed files
259 Forget removed files
260
260
261 If we're jumping between revisions (as opposed to merging), and if
261 If we're jumping between revisions (as opposed to merging), and if
262 neither the working directory nor the target rev has the file,
262 neither the working directory nor the target rev has the file,
263 then we need to remove it from the dirstate, to prevent the
263 then we need to remove it from the dirstate, to prevent the
264 dirstate from listing the file when it is no longer in the
264 dirstate from listing the file when it is no longer in the
265 manifest.
265 manifest.
266
266
267 If we're merging, and the other revision has removed a file
267 If we're merging, and the other revision has removed a file
268 that is not present in the working directory, we need to mark it
268 that is not present in the working directory, we need to mark it
269 as removed.
269 as removed.
270 """
270 """
271
271
272 m = mergestatemod.ACTION_FORGET
272 m = mergestatemod.ACTION_FORGET
273 if branchmerge:
273 if branchmerge:
274 m = mergestatemod.ACTION_REMOVE
274 m = mergestatemod.ACTION_REMOVE
275 for f in wctx.deleted():
275 for f in wctx.deleted():
276 if f not in mctx:
276 if f not in mctx:
277 mresult.addfile(f, m, None, b"forget deleted")
277 mresult.addfile(f, m, None, b"forget deleted")
278
278
279 if not branchmerge:
279 if not branchmerge:
280 for f in wctx.removed():
280 for f in wctx.removed():
281 if f not in mctx:
281 if f not in mctx:
282 mresult.addfile(
282 mresult.addfile(
283 f,
283 f,
284 mergestatemod.ACTION_FORGET,
284 mergestatemod.ACTION_FORGET,
285 None,
285 None,
286 b"forget removed",
286 b"forget removed",
287 )
287 )
288
288
289
289
290 def _checkcollision(repo, wmf, mresult):
290 def _checkcollision(repo, wmf, mresult):
291 """
291 """
292 Check for case-folding collisions.
292 Check for case-folding collisions.
293 """
293 """
294 # If the repo is narrowed, filter out files outside the narrowspec.
294 # If the repo is narrowed, filter out files outside the narrowspec.
295 narrowmatch = repo.narrowmatch()
295 narrowmatch = repo.narrowmatch()
296 if not narrowmatch.always():
296 if not narrowmatch.always():
297 pmmf = set(wmf.walk(narrowmatch))
297 pmmf = set(wmf.walk(narrowmatch))
298 if mresult:
298 if mresult:
299 for f in list(mresult.files()):
299 for f in list(mresult.files()):
300 if not narrowmatch(f):
300 if not narrowmatch(f):
301 mresult.removefile(f)
301 mresult.removefile(f)
302 else:
302 else:
303 # build provisional merged manifest up
303 # build provisional merged manifest up
304 pmmf = set(wmf)
304 pmmf = set(wmf)
305
305
306 if mresult:
306 if mresult:
307 # KEEP and EXEC are no-op
307 # KEEP and EXEC are no-op
308 for f in mresult.files(
308 for f in mresult.files(
309 (
309 (
310 mergestatemod.ACTION_ADD,
310 mergestatemod.ACTION_ADD,
311 mergestatemod.ACTION_ADD_MODIFIED,
311 mergestatemod.ACTION_ADD_MODIFIED,
312 mergestatemod.ACTION_FORGET,
312 mergestatemod.ACTION_FORGET,
313 mergestatemod.ACTION_GET,
313 mergestatemod.ACTION_GET,
314 mergestatemod.ACTION_CHANGED_DELETED,
314 mergestatemod.ACTION_CHANGED_DELETED,
315 mergestatemod.ACTION_DELETED_CHANGED,
315 mergestatemod.ACTION_DELETED_CHANGED,
316 )
316 )
317 ):
317 ):
318 pmmf.add(f)
318 pmmf.add(f)
319 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
319 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
320 pmmf.discard(f)
320 pmmf.discard(f)
321 for f, args, msg in mresult.getactions(
321 for f, args, msg in mresult.getactions(
322 [mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]
322 [mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]
323 ):
323 ):
324 f2, flags = args
324 f2, flags = args
325 pmmf.discard(f2)
325 pmmf.discard(f2)
326 pmmf.add(f)
326 pmmf.add(f)
327 for f in mresult.files((mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,)):
327 for f in mresult.files((mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,)):
328 pmmf.add(f)
328 pmmf.add(f)
329 for f, args, msg in mresult.getactions([mergestatemod.ACTION_MERGE]):
329 for f, args, msg in mresult.getactions([mergestatemod.ACTION_MERGE]):
330 f1, f2, fa, move, anc = args
330 f1, f2, fa, move, anc = args
331 if move:
331 if move:
332 pmmf.discard(f1)
332 pmmf.discard(f1)
333 pmmf.add(f)
333 pmmf.add(f)
334
334
335 # check case-folding collision in provisional merged manifest
335 # check case-folding collision in provisional merged manifest
336 foldmap = {}
336 foldmap = {}
337 for f in pmmf:
337 for f in pmmf:
338 fold = util.normcase(f)
338 fold = util.normcase(f)
339 if fold in foldmap:
339 if fold in foldmap:
340 raise error.StateError(
340 raise error.StateError(
341 _(b"case-folding collision between %s and %s")
341 _(b"case-folding collision between %s and %s")
342 % (f, foldmap[fold])
342 % (f, foldmap[fold])
343 )
343 )
344 foldmap[fold] = f
344 foldmap[fold] = f
345
345
346 # check case-folding of directories
346 # check case-folding of directories
347 foldprefix = unfoldprefix = lastfull = b''
347 foldprefix = unfoldprefix = lastfull = b''
348 for fold, f in sorted(foldmap.items()):
348 for fold, f in sorted(foldmap.items()):
349 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
349 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
350 # the folded prefix matches but actual casing is different
350 # the folded prefix matches but actual casing is different
351 raise error.StateError(
351 raise error.StateError(
352 _(b"case-folding collision between %s and directory of %s")
352 _(b"case-folding collision between %s and directory of %s")
353 % (lastfull, f)
353 % (lastfull, f)
354 )
354 )
355 foldprefix = fold + b'/'
355 foldprefix = fold + b'/'
356 unfoldprefix = f + b'/'
356 unfoldprefix = f + b'/'
357 lastfull = f
357 lastfull = f
358
358
359
359
360 def _filesindirs(repo, manifest, dirs):
360 def _filesindirs(repo, manifest, dirs):
361 """
361 """
362 Generator that yields pairs of all the files in the manifest that are found
362 Generator that yields pairs of all the files in the manifest that are found
363 inside the directories listed in dirs, and which directory they are found
363 inside the directories listed in dirs, and which directory they are found
364 in.
364 in.
365 """
365 """
366 for f in manifest:
366 for f in manifest:
367 for p in pathutil.finddirs(f):
367 for p in pathutil.finddirs(f):
368 if p in dirs:
368 if p in dirs:
369 yield f, p
369 yield f, p
370 break
370 break
371
371
372
372
373 def checkpathconflicts(repo, wctx, mctx, mresult):
373 def checkpathconflicts(repo, wctx, mctx, mresult):
374 """
374 """
375 Check if any actions introduce path conflicts in the repository, updating
375 Check if any actions introduce path conflicts in the repository, updating
376 actions to record or handle the path conflict accordingly.
376 actions to record or handle the path conflict accordingly.
377 """
377 """
378 mf = wctx.manifest()
378 mf = wctx.manifest()
379
379
380 # The set of local files that conflict with a remote directory.
380 # The set of local files that conflict with a remote directory.
381 localconflicts = set()
381 localconflicts = set()
382
382
383 # The set of directories that conflict with a remote file, and so may cause
383 # The set of directories that conflict with a remote file, and so may cause
384 # conflicts if they still contain any files after the merge.
384 # conflicts if they still contain any files after the merge.
385 remoteconflicts = set()
385 remoteconflicts = set()
386
386
387 # The set of directories that appear as both a file and a directory in the
387 # The set of directories that appear as both a file and a directory in the
388 # remote manifest. These indicate an invalid remote manifest, which
388 # remote manifest. These indicate an invalid remote manifest, which
389 # can't be updated to cleanly.
389 # can't be updated to cleanly.
390 invalidconflicts = set()
390 invalidconflicts = set()
391
391
392 # The set of directories that contain files that are being created.
392 # The set of directories that contain files that are being created.
393 createdfiledirs = set()
393 createdfiledirs = set()
394
394
395 # The set of files deleted by all the actions.
395 # The set of files deleted by all the actions.
396 deletedfiles = set()
396 deletedfiles = set()
397
397
398 for f in mresult.files(
398 for f in mresult.files(
399 (
399 (
400 mergestatemod.ACTION_CREATED,
400 mergestatemod.ACTION_CREATED,
401 mergestatemod.ACTION_DELETED_CHANGED,
401 mergestatemod.ACTION_DELETED_CHANGED,
402 mergestatemod.ACTION_MERGE,
402 mergestatemod.ACTION_MERGE,
403 mergestatemod.ACTION_CREATED_MERGE,
403 mergestatemod.ACTION_CREATED_MERGE,
404 )
404 )
405 ):
405 ):
406 # This action may create a new local file.
406 # This action may create a new local file.
407 createdfiledirs.update(pathutil.finddirs(f))
407 createdfiledirs.update(pathutil.finddirs(f))
408 if mf.hasdir(f):
408 if mf.hasdir(f):
409 # The file aliases a local directory. This might be ok if all
409 # The file aliases a local directory. This might be ok if all
410 # the files in the local directory are being deleted. This
410 # the files in the local directory are being deleted. This
411 # will be checked once we know what all the deleted files are.
411 # will be checked once we know what all the deleted files are.
412 remoteconflicts.add(f)
412 remoteconflicts.add(f)
413 # Track the names of all deleted files.
413 # Track the names of all deleted files.
414 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
414 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
415 deletedfiles.add(f)
415 deletedfiles.add(f)
416 for (f, args, msg) in mresult.getactions((mergestatemod.ACTION_MERGE,)):
416 for (f, args, msg) in mresult.getactions((mergestatemod.ACTION_MERGE,)):
417 f1, f2, fa, move, anc = args
417 f1, f2, fa, move, anc = args
418 if move:
418 if move:
419 deletedfiles.add(f1)
419 deletedfiles.add(f1)
420 for (f, args, msg) in mresult.getactions(
420 for (f, args, msg) in mresult.getactions(
421 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,)
421 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,)
422 ):
422 ):
423 f2, flags = args
423 f2, flags = args
424 deletedfiles.add(f2)
424 deletedfiles.add(f2)
425
425
426 # Check all directories that contain created files for path conflicts.
426 # Check all directories that contain created files for path conflicts.
427 for p in createdfiledirs:
427 for p in createdfiledirs:
428 if p in mf:
428 if p in mf:
429 if p in mctx:
429 if p in mctx:
430 # A file is in a directory which aliases both a local
430 # A file is in a directory which aliases both a local
431 # and a remote file. This is an internal inconsistency
431 # and a remote file. This is an internal inconsistency
432 # within the remote manifest.
432 # within the remote manifest.
433 invalidconflicts.add(p)
433 invalidconflicts.add(p)
434 else:
434 else:
435 # A file is in a directory which aliases a local file.
435 # A file is in a directory which aliases a local file.
436 # We will need to rename the local file.
436 # We will need to rename the local file.
437 localconflicts.add(p)
437 localconflicts.add(p)
438 pd = mresult.getfile(p)
438 pd = mresult.getfile(p)
439 if pd and pd[0] in (
439 if pd and pd[0] in (
440 mergestatemod.ACTION_CREATED,
440 mergestatemod.ACTION_CREATED,
441 mergestatemod.ACTION_DELETED_CHANGED,
441 mergestatemod.ACTION_DELETED_CHANGED,
442 mergestatemod.ACTION_MERGE,
442 mergestatemod.ACTION_MERGE,
443 mergestatemod.ACTION_CREATED_MERGE,
443 mergestatemod.ACTION_CREATED_MERGE,
444 ):
444 ):
445 # The file is in a directory which aliases a remote file.
445 # The file is in a directory which aliases a remote file.
446 # This is an internal inconsistency within the remote
446 # This is an internal inconsistency within the remote
447 # manifest.
447 # manifest.
448 invalidconflicts.add(p)
448 invalidconflicts.add(p)
449
449
450 # Rename all local conflicting files that have not been deleted.
450 # Rename all local conflicting files that have not been deleted.
451 for p in localconflicts:
451 for p in localconflicts:
452 if p not in deletedfiles:
452 if p not in deletedfiles:
453 ctxname = bytes(wctx).rstrip(b'+')
453 ctxname = bytes(wctx).rstrip(b'+')
454 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
454 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
455 porig = wctx[p].copysource() or p
455 porig = wctx[p].copysource() or p
456 mresult.addfile(
456 mresult.addfile(
457 pnew,
457 pnew,
458 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
458 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
459 (p, porig),
459 (p, porig),
460 b'local path conflict',
460 b'local path conflict',
461 )
461 )
462 mresult.addfile(
462 mresult.addfile(
463 p,
463 p,
464 mergestatemod.ACTION_PATH_CONFLICT,
464 mergestatemod.ACTION_PATH_CONFLICT,
465 (pnew, b'l'),
465 (pnew, b'l'),
466 b'path conflict',
466 b'path conflict',
467 )
467 )
468
468
469 if remoteconflicts:
469 if remoteconflicts:
470 # Check if all files in the conflicting directories have been removed.
470 # Check if all files in the conflicting directories have been removed.
471 ctxname = bytes(mctx).rstrip(b'+')
471 ctxname = bytes(mctx).rstrip(b'+')
472 for f, p in _filesindirs(repo, mf, remoteconflicts):
472 for f, p in _filesindirs(repo, mf, remoteconflicts):
473 if f not in deletedfiles:
473 if f not in deletedfiles:
474 m, args, msg = mresult.getfile(p)
474 m, args, msg = mresult.getfile(p)
475 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
475 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
476 if m in (
476 if m in (
477 mergestatemod.ACTION_DELETED_CHANGED,
477 mergestatemod.ACTION_DELETED_CHANGED,
478 mergestatemod.ACTION_MERGE,
478 mergestatemod.ACTION_MERGE,
479 ):
479 ):
480 # Action was merge, just update target.
480 # Action was merge, just update target.
481 mresult.addfile(pnew, m, args, msg)
481 mresult.addfile(pnew, m, args, msg)
482 else:
482 else:
483 # Action was create, change to renamed get action.
483 # Action was create, change to renamed get action.
484 fl = args[0]
484 fl = args[0]
485 mresult.addfile(
485 mresult.addfile(
486 pnew,
486 pnew,
487 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
487 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
488 (p, fl),
488 (p, fl),
489 b'remote path conflict',
489 b'remote path conflict',
490 )
490 )
491 mresult.addfile(
491 mresult.addfile(
492 p,
492 p,
493 mergestatemod.ACTION_PATH_CONFLICT,
493 mergestatemod.ACTION_PATH_CONFLICT,
494 (pnew, mergestatemod.ACTION_REMOVE),
494 (pnew, mergestatemod.ACTION_REMOVE),
495 b'path conflict',
495 b'path conflict',
496 )
496 )
497 remoteconflicts.remove(p)
497 remoteconflicts.remove(p)
498 break
498 break
499
499
500 if invalidconflicts:
500 if invalidconflicts:
501 for p in invalidconflicts:
501 for p in invalidconflicts:
502 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
502 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
503 raise error.StateError(
503 raise error.StateError(
504 _(b"destination manifest contains path conflicts")
504 _(b"destination manifest contains path conflicts")
505 )
505 )
506
506
507
507
508 def _filternarrowactions(narrowmatch, branchmerge, mresult):
508 def _filternarrowactions(narrowmatch, branchmerge, mresult):
509 """
509 """
510 Filters out actions that can ignored because the repo is narrowed.
510 Filters out actions that can ignored because the repo is narrowed.
511
511
512 Raise an exception if the merge cannot be completed because the repo is
512 Raise an exception if the merge cannot be completed because the repo is
513 narrowed.
513 narrowed.
514 """
514 """
515 # TODO: handle with nonconflicttypes
515 # TODO: handle with nonconflicttypes
516 nonconflicttypes = {
516 nonconflicttypes = {
517 mergestatemod.ACTION_ADD,
517 mergestatemod.ACTION_ADD,
518 mergestatemod.ACTION_ADD_MODIFIED,
518 mergestatemod.ACTION_ADD_MODIFIED,
519 mergestatemod.ACTION_CREATED,
519 mergestatemod.ACTION_CREATED,
520 mergestatemod.ACTION_CREATED_MERGE,
520 mergestatemod.ACTION_CREATED_MERGE,
521 mergestatemod.ACTION_FORGET,
521 mergestatemod.ACTION_FORGET,
522 mergestatemod.ACTION_GET,
522 mergestatemod.ACTION_GET,
523 mergestatemod.ACTION_REMOVE,
523 mergestatemod.ACTION_REMOVE,
524 mergestatemod.ACTION_EXEC,
524 mergestatemod.ACTION_EXEC,
525 }
525 }
526 # We mutate the items in the dict during iteration, so iterate
526 # We mutate the items in the dict during iteration, so iterate
527 # over a copy.
527 # over a copy.
528 for f, action in mresult.filemap():
528 for f, action in mresult.filemap():
529 if narrowmatch(f):
529 if narrowmatch(f):
530 pass
530 pass
531 elif not branchmerge:
531 elif not branchmerge:
532 mresult.removefile(f) # just updating, ignore changes outside clone
532 mresult.removefile(f) # just updating, ignore changes outside clone
533 elif action[0] in mergestatemod.NO_OP_ACTIONS:
533 elif action[0] in mergestatemod.NO_OP_ACTIONS:
534 mresult.removefile(f) # merge does not affect file
534 mresult.removefile(f) # merge does not affect file
535 elif action[0] in nonconflicttypes:
535 elif action[0] in nonconflicttypes:
536 raise error.Abort(
536 raise error.Abort(
537 _(
537 _(
538 b'merge affects file \'%s\' outside narrow, '
538 b'merge affects file \'%s\' outside narrow, '
539 b'which is not yet supported'
539 b'which is not yet supported'
540 )
540 )
541 % f,
541 % f,
542 hint=_(b'merging in the other direction may work'),
542 hint=_(b'merging in the other direction may work'),
543 )
543 )
544 else:
544 else:
545 raise error.StateError(
545 raise error.StateError(
546 _(b'conflict in file \'%s\' is outside narrow clone') % f
546 _(b'conflict in file \'%s\' is outside narrow clone') % f
547 )
547 )
548
548
549
549
550 class mergeresult(object):
550 class mergeresult(object):
551 """An object representing result of merging manifests.
551 """An object representing result of merging manifests.
552
552
553 It has information about what actions need to be performed on dirstate
553 It has information about what actions need to be performed on dirstate
554 mapping of divergent renames and other such cases."""
554 mapping of divergent renames and other such cases."""
555
555
556 def __init__(self):
556 def __init__(self):
557 """
557 """
558 filemapping: dict of filename as keys and action related info as values
558 filemapping: dict of filename as keys and action related info as values
559 diverge: mapping of source name -> list of dest name for
559 diverge: mapping of source name -> list of dest name for
560 divergent renames
560 divergent renames
561 renamedelete: mapping of source name -> list of destinations for files
561 renamedelete: mapping of source name -> list of destinations for files
562 deleted on one side and renamed on other.
562 deleted on one side and renamed on other.
563 commitinfo: dict containing data which should be used on commit
563 commitinfo: dict containing data which should be used on commit
564 contains a filename -> info mapping
564 contains a filename -> info mapping
565 actionmapping: dict of action names as keys and values are dict of
565 actionmapping: dict of action names as keys and values are dict of
566 filename as key and related data as values
566 filename as key and related data as values
567 """
567 """
568 self._filemapping = {}
568 self._filemapping = {}
569 self._diverge = {}
569 self._diverge = {}
570 self._renamedelete = {}
570 self._renamedelete = {}
571 self._commitinfo = collections.defaultdict(dict)
571 self._commitinfo = collections.defaultdict(dict)
572 self._actionmapping = collections.defaultdict(dict)
572 self._actionmapping = collections.defaultdict(dict)
573
573
574 def updatevalues(self, diverge, renamedelete):
574 def updatevalues(self, diverge, renamedelete):
575 self._diverge = diverge
575 self._diverge = diverge
576 self._renamedelete = renamedelete
576 self._renamedelete = renamedelete
577
577
578 def addfile(self, filename, action, data, message):
578 def addfile(self, filename, action, data, message):
579 """adds a new file to the mergeresult object
579 """adds a new file to the mergeresult object
580
580
581 filename: file which we are adding
581 filename: file which we are adding
582 action: one of mergestatemod.ACTION_*
582 action: one of mergestatemod.ACTION_*
583 data: a tuple of information like fctx and ctx related to this merge
583 data: a tuple of information like fctx and ctx related to this merge
584 message: a message about the merge
584 message: a message about the merge
585 """
585 """
586 # if the file already existed, we need to delete it's old
586 # if the file already existed, we need to delete it's old
587 # entry form _actionmapping too
587 # entry form _actionmapping too
588 if filename in self._filemapping:
588 if filename in self._filemapping:
589 a, d, m = self._filemapping[filename]
589 a, d, m = self._filemapping[filename]
590 del self._actionmapping[a][filename]
590 del self._actionmapping[a][filename]
591
591
592 self._filemapping[filename] = (action, data, message)
592 self._filemapping[filename] = (action, data, message)
593 self._actionmapping[action][filename] = (data, message)
593 self._actionmapping[action][filename] = (data, message)
594
594
595 def getfile(self, filename, default_return=None):
595 def getfile(self, filename, default_return=None):
596 """returns (action, args, msg) about this file
596 """returns (action, args, msg) about this file
597
597
598 returns default_return if the file is not present"""
598 returns default_return if the file is not present"""
599 if filename in self._filemapping:
599 if filename in self._filemapping:
600 return self._filemapping[filename]
600 return self._filemapping[filename]
601 return default_return
601 return default_return
602
602
603 def files(self, actions=None):
603 def files(self, actions=None):
604 """returns files on which provided action needs to perfromed
604 """returns files on which provided action needs to perfromed
605
605
606 If actions is None, all files are returned
606 If actions is None, all files are returned
607 """
607 """
608 # TODO: think whether we should return renamedelete and
608 # TODO: think whether we should return renamedelete and
609 # diverge filenames also
609 # diverge filenames also
610 if actions is None:
610 if actions is None:
611 for f in self._filemapping:
611 for f in self._filemapping:
612 yield f
612 yield f
613
613
614 else:
614 else:
615 for a in actions:
615 for a in actions:
616 for f in self._actionmapping[a]:
616 for f in self._actionmapping[a]:
617 yield f
617 yield f
618
618
619 def removefile(self, filename):
619 def removefile(self, filename):
620 """removes a file from the mergeresult object as the file might
620 """removes a file from the mergeresult object as the file might
621 not merging anymore"""
621 not merging anymore"""
622 action, data, message = self._filemapping[filename]
622 action, data, message = self._filemapping[filename]
623 del self._filemapping[filename]
623 del self._filemapping[filename]
624 del self._actionmapping[action][filename]
624 del self._actionmapping[action][filename]
625
625
626 def getactions(self, actions, sort=False):
626 def getactions(self, actions, sort=False):
627 """get list of files which are marked with these actions
627 """get list of files which are marked with these actions
628 if sort is true, files for each action is sorted and then added
628 if sort is true, files for each action is sorted and then added
629
629
630 Returns a list of tuple of form (filename, data, message)
630 Returns a list of tuple of form (filename, data, message)
631 """
631 """
632 for a in actions:
632 for a in actions:
633 if sort:
633 if sort:
634 for f in sorted(self._actionmapping[a]):
634 for f in sorted(self._actionmapping[a]):
635 args, msg = self._actionmapping[a][f]
635 args, msg = self._actionmapping[a][f]
636 yield f, args, msg
636 yield f, args, msg
637 else:
637 else:
638 for f, (args, msg) in pycompat.iteritems(
638 for f, (args, msg) in pycompat.iteritems(
639 self._actionmapping[a]
639 self._actionmapping[a]
640 ):
640 ):
641 yield f, args, msg
641 yield f, args, msg
642
642
643 def len(self, actions=None):
643 def len(self, actions=None):
644 """returns number of files which needs actions
644 """returns number of files which needs actions
645
645
646 if actions is passed, total of number of files in that action
646 if actions is passed, total of number of files in that action
647 only is returned"""
647 only is returned"""
648
648
649 if actions is None:
649 if actions is None:
650 return len(self._filemapping)
650 return len(self._filemapping)
651
651
652 return sum(len(self._actionmapping[a]) for a in actions)
652 return sum(len(self._actionmapping[a]) for a in actions)
653
653
654 def filemap(self, sort=False):
654 def filemap(self, sort=False):
655 if sorted:
655 if sorted:
656 for key, val in sorted(pycompat.iteritems(self._filemapping)):
656 for key, val in sorted(pycompat.iteritems(self._filemapping)):
657 yield key, val
657 yield key, val
658 else:
658 else:
659 for key, val in pycompat.iteritems(self._filemapping):
659 for key, val in pycompat.iteritems(self._filemapping):
660 yield key, val
660 yield key, val
661
661
662 def addcommitinfo(self, filename, key, value):
662 def addcommitinfo(self, filename, key, value):
663 """adds key-value information about filename which will be required
663 """adds key-value information about filename which will be required
664 while committing this merge"""
664 while committing this merge"""
665 self._commitinfo[filename][key] = value
665 self._commitinfo[filename][key] = value
666
666
667 @property
667 @property
668 def diverge(self):
668 def diverge(self):
669 return self._diverge
669 return self._diverge
670
670
671 @property
671 @property
672 def renamedelete(self):
672 def renamedelete(self):
673 return self._renamedelete
673 return self._renamedelete
674
674
675 @property
675 @property
676 def commitinfo(self):
676 def commitinfo(self):
677 return self._commitinfo
677 return self._commitinfo
678
678
679 @property
679 @property
680 def actionsdict(self):
680 def actionsdict(self):
681 """returns a dictionary of actions to be perfomed with action as key
681 """returns a dictionary of actions to be perfomed with action as key
682 and a list of files and related arguments as values"""
682 and a list of files and related arguments as values"""
683 res = collections.defaultdict(list)
683 res = collections.defaultdict(list)
684 for a, d in pycompat.iteritems(self._actionmapping):
684 for a, d in pycompat.iteritems(self._actionmapping):
685 for f, (args, msg) in pycompat.iteritems(d):
685 for f, (args, msg) in pycompat.iteritems(d):
686 res[a].append((f, args, msg))
686 res[a].append((f, args, msg))
687 return res
687 return res
688
688
689 def setactions(self, actions):
689 def setactions(self, actions):
690 self._filemapping = actions
690 self._filemapping = actions
691 self._actionmapping = collections.defaultdict(dict)
691 self._actionmapping = collections.defaultdict(dict)
692 for f, (act, data, msg) in pycompat.iteritems(self._filemapping):
692 for f, (act, data, msg) in pycompat.iteritems(self._filemapping):
693 self._actionmapping[act][f] = data, msg
693 self._actionmapping[act][f] = data, msg
694
694
695 def hasconflicts(self):
695 def hasconflicts(self):
696 """tells whether this merge resulted in some actions which can
696 """tells whether this merge resulted in some actions which can
697 result in conflicts or not"""
697 result in conflicts or not"""
698 for a in self._actionmapping.keys():
698 for a in self._actionmapping.keys():
699 if (
699 if (
700 a
700 a
701 not in (
701 not in (
702 mergestatemod.ACTION_GET,
702 mergestatemod.ACTION_GET,
703 mergestatemod.ACTION_EXEC,
703 mergestatemod.ACTION_EXEC,
704 mergestatemod.ACTION_REMOVE,
704 mergestatemod.ACTION_REMOVE,
705 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
705 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
706 )
706 )
707 and self._actionmapping[a]
707 and self._actionmapping[a]
708 and a not in mergestatemod.NO_OP_ACTIONS
708 and a not in mergestatemod.NO_OP_ACTIONS
709 ):
709 ):
710 return True
710 return True
711
711
712 return False
712 return False
713
713
714
714
715 def manifestmerge(
715 def manifestmerge(
716 repo,
716 repo,
717 wctx,
717 wctx,
718 p2,
718 p2,
719 pa,
719 pa,
720 branchmerge,
720 branchmerge,
721 force,
721 force,
722 matcher,
722 matcher,
723 acceptremote,
723 acceptremote,
724 followcopies,
724 followcopies,
725 forcefulldiff=False,
725 forcefulldiff=False,
726 ):
726 ):
727 """
727 """
728 Merge wctx and p2 with ancestor pa and generate merge action list
728 Merge wctx and p2 with ancestor pa and generate merge action list
729
729
730 branchmerge and force are as passed in to update
730 branchmerge and force are as passed in to update
731 matcher = matcher to filter file lists
731 matcher = matcher to filter file lists
732 acceptremote = accept the incoming changes without prompting
732 acceptremote = accept the incoming changes without prompting
733
733
734 Returns an object of mergeresult class
734 Returns an object of mergeresult class
735 """
735 """
736 mresult = mergeresult()
736 mresult = mergeresult()
737 if matcher is not None and matcher.always():
737 if matcher is not None and matcher.always():
738 matcher = None
738 matcher = None
739
739
740 # manifests fetched in order are going to be faster, so prime the caches
740 # manifests fetched in order are going to be faster, so prime the caches
741 [
741 [
742 x.manifest()
742 x.manifest()
743 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
743 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
744 ]
744 ]
745
745
746 branch_copies1 = copies.branch_copies()
746 branch_copies1 = copies.branch_copies()
747 branch_copies2 = copies.branch_copies()
747 branch_copies2 = copies.branch_copies()
748 diverge = {}
748 diverge = {}
749 # information from merge which is needed at commit time
749 # information from merge which is needed at commit time
750 # for example choosing filelog of which parent to commit
750 # for example choosing filelog of which parent to commit
751 # TODO: use specific constants in future for this mapping
751 # TODO: use specific constants in future for this mapping
752 if followcopies:
752 if followcopies:
753 branch_copies1, branch_copies2, diverge = copies.mergecopies(
753 branch_copies1, branch_copies2, diverge = copies.mergecopies(
754 repo, wctx, p2, pa
754 repo, wctx, p2, pa
755 )
755 )
756
756
757 boolbm = pycompat.bytestr(bool(branchmerge))
757 boolbm = pycompat.bytestr(bool(branchmerge))
758 boolf = pycompat.bytestr(bool(force))
758 boolf = pycompat.bytestr(bool(force))
759 boolm = pycompat.bytestr(bool(matcher))
759 boolm = pycompat.bytestr(bool(matcher))
760 repo.ui.note(_(b"resolving manifests\n"))
760 repo.ui.note(_(b"resolving manifests\n"))
761 repo.ui.debug(
761 repo.ui.debug(
762 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
762 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
763 )
763 )
764 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
764 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
765
765
766 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
766 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
767 copied1 = set(branch_copies1.copy.values())
767 copied1 = set(branch_copies1.copy.values())
768 copied1.update(branch_copies1.movewithdir.values())
768 copied1.update(branch_copies1.movewithdir.values())
769 copied2 = set(branch_copies2.copy.values())
769 copied2 = set(branch_copies2.copy.values())
770 copied2.update(branch_copies2.movewithdir.values())
770 copied2.update(branch_copies2.movewithdir.values())
771
771
772 if b'.hgsubstate' in m1 and wctx.rev() is None:
772 if b'.hgsubstate' in m1 and wctx.rev() is None:
773 # Check whether sub state is modified, and overwrite the manifest
773 # Check whether sub state is modified, and overwrite the manifest
774 # to flag the change. If wctx is a committed revision, we shouldn't
774 # to flag the change. If wctx is a committed revision, we shouldn't
775 # care for the dirty state of the working directory.
775 # care for the dirty state of the working directory.
776 if any(wctx.sub(s).dirty() for s in wctx.substate):
776 if any(wctx.sub(s).dirty() for s in wctx.substate):
777 m1[b'.hgsubstate'] = repo.nodeconstants.modifiednodeid
777 m1[b'.hgsubstate'] = repo.nodeconstants.modifiednodeid
778
778
779 # Don't use m2-vs-ma optimization if:
779 # Don't use m2-vs-ma optimization if:
780 # - ma is the same as m1 or m2, which we're just going to diff again later
780 # - ma is the same as m1 or m2, which we're just going to diff again later
781 # - The caller specifically asks for a full diff, which is useful during bid
781 # - The caller specifically asks for a full diff, which is useful during bid
782 # merge.
782 # merge.
783 # - we are tracking salvaged files specifically hence should process all
783 # - we are tracking salvaged files specifically hence should process all
784 # files
784 # files
785 if (
785 if (
786 pa not in ([wctx, p2] + wctx.parents())
786 pa not in ([wctx, p2] + wctx.parents())
787 and not forcefulldiff
787 and not forcefulldiff
788 and not (
788 and not (
789 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
789 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
790 or repo.filecopiesmode == b'changeset-sidedata'
790 or repo.filecopiesmode == b'changeset-sidedata'
791 )
791 )
792 ):
792 ):
793 # Identify which files are relevant to the merge, so we can limit the
793 # Identify which files are relevant to the merge, so we can limit the
794 # total m1-vs-m2 diff to just those files. This has significant
794 # total m1-vs-m2 diff to just those files. This has significant
795 # performance benefits in large repositories.
795 # performance benefits in large repositories.
796 relevantfiles = set(ma.diff(m2).keys())
796 relevantfiles = set(ma.diff(m2).keys())
797
797
798 # For copied and moved files, we need to add the source file too.
798 # For copied and moved files, we need to add the source file too.
799 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
799 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
800 if copyvalue in relevantfiles:
800 if copyvalue in relevantfiles:
801 relevantfiles.add(copykey)
801 relevantfiles.add(copykey)
802 for movedirkey in branch_copies1.movewithdir:
802 for movedirkey in branch_copies1.movewithdir:
803 relevantfiles.add(movedirkey)
803 relevantfiles.add(movedirkey)
804 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
804 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
805 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
805 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
806
806
807 diff = m1.diff(m2, match=matcher)
807 diff = m1.diff(m2, match=matcher)
808
808
809 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
809 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
810 if n1 and n2: # file exists on both local and remote side
810 if n1 and n2: # file exists on both local and remote side
811 if f not in ma:
811 if f not in ma:
812 # TODO: what if they're renamed from different sources?
812 # TODO: what if they're renamed from different sources?
813 fa = branch_copies1.copy.get(
813 fa = branch_copies1.copy.get(
814 f, None
814 f, None
815 ) or branch_copies2.copy.get(f, None)
815 ) or branch_copies2.copy.get(f, None)
816 args, msg = None, None
816 args, msg = None, None
817 if fa is not None:
817 if fa is not None:
818 args = (f, f, fa, False, pa.node())
818 args = (f, f, fa, False, pa.node())
819 msg = b'both renamed from %s' % fa
819 msg = b'both renamed from %s' % fa
820 else:
820 else:
821 args = (f, f, None, False, pa.node())
821 args = (f, f, None, False, pa.node())
822 msg = b'both created'
822 msg = b'both created'
823 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
823 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
824 elif f in branch_copies1.copy:
824 elif f in branch_copies1.copy:
825 fa = branch_copies1.copy[f]
825 fa = branch_copies1.copy[f]
826 mresult.addfile(
826 mresult.addfile(
827 f,
827 f,
828 mergestatemod.ACTION_MERGE,
828 mergestatemod.ACTION_MERGE,
829 (f, fa, fa, False, pa.node()),
829 (f, fa, fa, False, pa.node()),
830 b'local replaced from %s' % fa,
830 b'local replaced from %s' % fa,
831 )
831 )
832 elif f in branch_copies2.copy:
832 elif f in branch_copies2.copy:
833 fa = branch_copies2.copy[f]
833 fa = branch_copies2.copy[f]
834 mresult.addfile(
834 mresult.addfile(
835 f,
835 f,
836 mergestatemod.ACTION_MERGE,
836 mergestatemod.ACTION_MERGE,
837 (fa, f, fa, False, pa.node()),
837 (fa, f, fa, False, pa.node()),
838 b'other replaced from %s' % fa,
838 b'other replaced from %s' % fa,
839 )
839 )
840 else:
840 else:
841 a = ma[f]
841 a = ma[f]
842 fla = ma.flags(f)
842 fla = ma.flags(f)
843 nol = b'l' not in fl1 + fl2 + fla
843 nol = b'l' not in fl1 + fl2 + fla
844 if n2 == a and fl2 == fla:
844 if n2 == a and fl2 == fla:
845 mresult.addfile(
845 mresult.addfile(
846 f,
846 f,
847 mergestatemod.ACTION_KEEP,
847 mergestatemod.ACTION_KEEP,
848 (),
848 (),
849 b'remote unchanged',
849 b'remote unchanged',
850 )
850 )
851 elif n1 == a and fl1 == fla: # local unchanged - use remote
851 elif n1 == a and fl1 == fla: # local unchanged - use remote
852 if n1 == n2: # optimization: keep local content
852 if n1 == n2: # optimization: keep local content
853 mresult.addfile(
853 mresult.addfile(
854 f,
854 f,
855 mergestatemod.ACTION_EXEC,
855 mergestatemod.ACTION_EXEC,
856 (fl2,),
856 (fl2,),
857 b'update permissions',
857 b'update permissions',
858 )
858 )
859 else:
859 else:
860 mresult.addfile(
860 mresult.addfile(
861 f,
861 f,
862 mergestatemod.ACTION_GET,
862 mergestatemod.ACTION_GET,
863 (fl2, False),
863 (fl2, False),
864 b'remote is newer',
864 b'remote is newer',
865 )
865 )
866 if branchmerge:
866 if branchmerge:
867 mresult.addcommitinfo(
867 mresult.addcommitinfo(
868 f, b'filenode-source', b'other'
868 f, b'filenode-source', b'other'
869 )
869 )
870 elif nol and n2 == a: # remote only changed 'x'
870 elif nol and n2 == a: # remote only changed 'x'
871 mresult.addfile(
871 mresult.addfile(
872 f,
872 f,
873 mergestatemod.ACTION_EXEC,
873 mergestatemod.ACTION_EXEC,
874 (fl2,),
874 (fl2,),
875 b'update permissions',
875 b'update permissions',
876 )
876 )
877 elif nol and n1 == a: # local only changed 'x'
877 elif nol and n1 == a: # local only changed 'x'
878 mresult.addfile(
878 mresult.addfile(
879 f,
879 f,
880 mergestatemod.ACTION_GET,
880 mergestatemod.ACTION_GET,
881 (fl1, False),
881 (fl1, False),
882 b'remote is newer',
882 b'remote is newer',
883 )
883 )
884 if branchmerge:
884 if branchmerge:
885 mresult.addcommitinfo(f, b'filenode-source', b'other')
885 mresult.addcommitinfo(f, b'filenode-source', b'other')
886 else: # both changed something
886 else: # both changed something
887 mresult.addfile(
887 mresult.addfile(
888 f,
888 f,
889 mergestatemod.ACTION_MERGE,
889 mergestatemod.ACTION_MERGE,
890 (f, f, f, False, pa.node()),
890 (f, f, f, False, pa.node()),
891 b'versions differ',
891 b'versions differ',
892 )
892 )
893 elif n1: # file exists only on local side
893 elif n1: # file exists only on local side
894 if f in copied2:
894 if f in copied2:
895 pass # we'll deal with it on m2 side
895 pass # we'll deal with it on m2 side
896 elif (
896 elif (
897 f in branch_copies1.movewithdir
897 f in branch_copies1.movewithdir
898 ): # directory rename, move local
898 ): # directory rename, move local
899 f2 = branch_copies1.movewithdir[f]
899 f2 = branch_copies1.movewithdir[f]
900 if f2 in m2:
900 if f2 in m2:
901 mresult.addfile(
901 mresult.addfile(
902 f2,
902 f2,
903 mergestatemod.ACTION_MERGE,
903 mergestatemod.ACTION_MERGE,
904 (f, f2, None, True, pa.node()),
904 (f, f2, None, True, pa.node()),
905 b'remote directory rename, both created',
905 b'remote directory rename, both created',
906 )
906 )
907 else:
907 else:
908 mresult.addfile(
908 mresult.addfile(
909 f2,
909 f2,
910 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
910 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
911 (f, fl1),
911 (f, fl1),
912 b'remote directory rename - move from %s' % f,
912 b'remote directory rename - move from %s' % f,
913 )
913 )
914 elif f in branch_copies1.copy:
914 elif f in branch_copies1.copy:
915 f2 = branch_copies1.copy[f]
915 f2 = branch_copies1.copy[f]
916 mresult.addfile(
916 mresult.addfile(
917 f,
917 f,
918 mergestatemod.ACTION_MERGE,
918 mergestatemod.ACTION_MERGE,
919 (f, f2, f2, False, pa.node()),
919 (f, f2, f2, False, pa.node()),
920 b'local copied/moved from %s' % f2,
920 b'local copied/moved from %s' % f2,
921 )
921 )
922 elif f in ma: # clean, a different, no remote
922 elif f in ma: # clean, a different, no remote
923 if n1 != ma[f]:
923 if n1 != ma[f]:
924 if acceptremote:
924 if acceptremote:
925 mresult.addfile(
925 mresult.addfile(
926 f,
926 f,
927 mergestatemod.ACTION_REMOVE,
927 mergestatemod.ACTION_REMOVE,
928 None,
928 None,
929 b'remote delete',
929 b'remote delete',
930 )
930 )
931 else:
931 else:
932 mresult.addfile(
932 mresult.addfile(
933 f,
933 f,
934 mergestatemod.ACTION_CHANGED_DELETED,
934 mergestatemod.ACTION_CHANGED_DELETED,
935 (f, None, f, False, pa.node()),
935 (f, None, f, False, pa.node()),
936 b'prompt changed/deleted',
936 b'prompt changed/deleted',
937 )
937 )
938 if branchmerge:
938 if branchmerge:
939 mresult.addcommitinfo(
939 mresult.addcommitinfo(
940 f, b'merge-removal-candidate', b'yes'
940 f, b'merge-removal-candidate', b'yes'
941 )
941 )
942 elif n1 == repo.nodeconstants.addednodeid:
942 elif n1 == repo.nodeconstants.addednodeid:
943 # This file was locally added. We should forget it instead of
943 # This file was locally added. We should forget it instead of
944 # deleting it.
944 # deleting it.
945 mresult.addfile(
945 mresult.addfile(
946 f,
946 f,
947 mergestatemod.ACTION_FORGET,
947 mergestatemod.ACTION_FORGET,
948 None,
948 None,
949 b'remote deleted',
949 b'remote deleted',
950 )
950 )
951 else:
951 else:
952 mresult.addfile(
952 mresult.addfile(
953 f,
953 f,
954 mergestatemod.ACTION_REMOVE,
954 mergestatemod.ACTION_REMOVE,
955 None,
955 None,
956 b'other deleted',
956 b'other deleted',
957 )
957 )
958 if branchmerge:
958 if branchmerge:
959 # the file must be absent after merging,
959 # the file must be absent after merging,
960 # howeber the user might make
960 # howeber the user might make
961 # the file reappear using revert and if they does,
961 # the file reappear using revert and if they does,
962 # we force create a new node
962 # we force create a new node
963 mresult.addcommitinfo(
963 mresult.addcommitinfo(
964 f, b'merge-removal-candidate', b'yes'
964 f, b'merge-removal-candidate', b'yes'
965 )
965 )
966
966
967 else: # file not in ancestor, not in remote
967 else: # file not in ancestor, not in remote
968 mresult.addfile(
968 mresult.addfile(
969 f,
969 f,
970 mergestatemod.ACTION_KEEP_NEW,
970 mergestatemod.ACTION_KEEP_NEW,
971 None,
971 None,
972 b'ancestor missing, remote missing',
972 b'ancestor missing, remote missing',
973 )
973 )
974
974
975 elif n2: # file exists only on remote side
975 elif n2: # file exists only on remote side
976 if f in copied1:
976 if f in copied1:
977 pass # we'll deal with it on m1 side
977 pass # we'll deal with it on m1 side
978 elif f in branch_copies2.movewithdir:
978 elif f in branch_copies2.movewithdir:
979 f2 = branch_copies2.movewithdir[f]
979 f2 = branch_copies2.movewithdir[f]
980 if f2 in m1:
980 if f2 in m1:
981 mresult.addfile(
981 mresult.addfile(
982 f2,
982 f2,
983 mergestatemod.ACTION_MERGE,
983 mergestatemod.ACTION_MERGE,
984 (f2, f, None, False, pa.node()),
984 (f2, f, None, False, pa.node()),
985 b'local directory rename, both created',
985 b'local directory rename, both created',
986 )
986 )
987 else:
987 else:
988 mresult.addfile(
988 mresult.addfile(
989 f2,
989 f2,
990 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
990 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
991 (f, fl2),
991 (f, fl2),
992 b'local directory rename - get from %s' % f,
992 b'local directory rename - get from %s' % f,
993 )
993 )
994 elif f in branch_copies2.copy:
994 elif f in branch_copies2.copy:
995 f2 = branch_copies2.copy[f]
995 f2 = branch_copies2.copy[f]
996 msg, args = None, None
996 msg, args = None, None
997 if f2 in m2:
997 if f2 in m2:
998 args = (f2, f, f2, False, pa.node())
998 args = (f2, f, f2, False, pa.node())
999 msg = b'remote copied from %s' % f2
999 msg = b'remote copied from %s' % f2
1000 else:
1000 else:
1001 args = (f2, f, f2, True, pa.node())
1001 args = (f2, f, f2, True, pa.node())
1002 msg = b'remote moved from %s' % f2
1002 msg = b'remote moved from %s' % f2
1003 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
1003 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
1004 elif f not in ma:
1004 elif f not in ma:
1005 # local unknown, remote created: the logic is described by the
1005 # local unknown, remote created: the logic is described by the
1006 # following table:
1006 # following table:
1007 #
1007 #
1008 # force branchmerge different | action
1008 # force branchmerge different | action
1009 # n * * | create
1009 # n * * | create
1010 # y n * | create
1010 # y n * | create
1011 # y y n | create
1011 # y y n | create
1012 # y y y | merge
1012 # y y y | merge
1013 #
1013 #
1014 # Checking whether the files are different is expensive, so we
1014 # Checking whether the files are different is expensive, so we
1015 # don't do that when we can avoid it.
1015 # don't do that when we can avoid it.
1016 if not force:
1016 if not force:
1017 mresult.addfile(
1017 mresult.addfile(
1018 f,
1018 f,
1019 mergestatemod.ACTION_CREATED,
1019 mergestatemod.ACTION_CREATED,
1020 (fl2,),
1020 (fl2,),
1021 b'remote created',
1021 b'remote created',
1022 )
1022 )
1023 elif not branchmerge:
1023 elif not branchmerge:
1024 mresult.addfile(
1024 mresult.addfile(
1025 f,
1025 f,
1026 mergestatemod.ACTION_CREATED,
1026 mergestatemod.ACTION_CREATED,
1027 (fl2,),
1027 (fl2,),
1028 b'remote created',
1028 b'remote created',
1029 )
1029 )
1030 else:
1030 else:
1031 mresult.addfile(
1031 mresult.addfile(
1032 f,
1032 f,
1033 mergestatemod.ACTION_CREATED_MERGE,
1033 mergestatemod.ACTION_CREATED_MERGE,
1034 (fl2, pa.node()),
1034 (fl2, pa.node()),
1035 b'remote created, get or merge',
1035 b'remote created, get or merge',
1036 )
1036 )
1037 elif n2 != ma[f]:
1037 elif n2 != ma[f]:
1038 df = None
1038 df = None
1039 for d in branch_copies1.dirmove:
1039 for d in branch_copies1.dirmove:
1040 if f.startswith(d):
1040 if f.startswith(d):
1041 # new file added in a directory that was moved
1041 # new file added in a directory that was moved
1042 df = branch_copies1.dirmove[d] + f[len(d) :]
1042 df = branch_copies1.dirmove[d] + f[len(d) :]
1043 break
1043 break
1044 if df is not None and df in m1:
1044 if df is not None and df in m1:
1045 mresult.addfile(
1045 mresult.addfile(
1046 df,
1046 df,
1047 mergestatemod.ACTION_MERGE,
1047 mergestatemod.ACTION_MERGE,
1048 (df, f, f, False, pa.node()),
1048 (df, f, f, False, pa.node()),
1049 b'local directory rename - respect move '
1049 b'local directory rename - respect move '
1050 b'from %s' % f,
1050 b'from %s' % f,
1051 )
1051 )
1052 elif acceptremote:
1052 elif acceptremote:
1053 mresult.addfile(
1053 mresult.addfile(
1054 f,
1054 f,
1055 mergestatemod.ACTION_CREATED,
1055 mergestatemod.ACTION_CREATED,
1056 (fl2,),
1056 (fl2,),
1057 b'remote recreating',
1057 b'remote recreating',
1058 )
1058 )
1059 else:
1059 else:
1060 mresult.addfile(
1060 mresult.addfile(
1061 f,
1061 f,
1062 mergestatemod.ACTION_DELETED_CHANGED,
1062 mergestatemod.ACTION_DELETED_CHANGED,
1063 (None, f, f, False, pa.node()),
1063 (None, f, f, False, pa.node()),
1064 b'prompt deleted/changed',
1064 b'prompt deleted/changed',
1065 )
1065 )
1066 if branchmerge:
1066 if branchmerge:
1067 mresult.addcommitinfo(
1067 mresult.addcommitinfo(
1068 f, b'merge-removal-candidate', b'yes'
1068 f, b'merge-removal-candidate', b'yes'
1069 )
1069 )
1070 else:
1070 else:
1071 mresult.addfile(
1071 mresult.addfile(
1072 f,
1072 f,
1073 mergestatemod.ACTION_KEEP_ABSENT,
1073 mergestatemod.ACTION_KEEP_ABSENT,
1074 None,
1074 None,
1075 b'local not present, remote unchanged',
1075 b'local not present, remote unchanged',
1076 )
1076 )
1077 if branchmerge:
1077 if branchmerge:
1078 # the file must be absent after merging
1078 # the file must be absent after merging
1079 # however the user might make
1079 # however the user might make
1080 # the file reappear using revert and if they does,
1080 # the file reappear using revert and if they does,
1081 # we force create a new node
1081 # we force create a new node
1082 mresult.addcommitinfo(f, b'merge-removal-candidate', b'yes')
1082 mresult.addcommitinfo(f, b'merge-removal-candidate', b'yes')
1083
1083
1084 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1084 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1085 # If we are merging, look for path conflicts.
1085 # If we are merging, look for path conflicts.
1086 checkpathconflicts(repo, wctx, p2, mresult)
1086 checkpathconflicts(repo, wctx, p2, mresult)
1087
1087
1088 narrowmatch = repo.narrowmatch()
1088 narrowmatch = repo.narrowmatch()
1089 if not narrowmatch.always():
1089 if not narrowmatch.always():
1090 # Updates "actions" in place
1090 # Updates "actions" in place
1091 _filternarrowactions(narrowmatch, branchmerge, mresult)
1091 _filternarrowactions(narrowmatch, branchmerge, mresult)
1092
1092
1093 renamedelete = branch_copies1.renamedelete
1093 renamedelete = branch_copies1.renamedelete
1094 renamedelete.update(branch_copies2.renamedelete)
1094 renamedelete.update(branch_copies2.renamedelete)
1095
1095
1096 mresult.updatevalues(diverge, renamedelete)
1096 mresult.updatevalues(diverge, renamedelete)
1097 return mresult
1097 return mresult
1098
1098
1099
1099
1100 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult):
1100 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult):
1101 """Resolves false conflicts where the nodeid changed but the content
1101 """Resolves false conflicts where the nodeid changed but the content
1102 remained the same."""
1102 remained the same."""
1103 # We force a copy of actions.items() because we're going to mutate
1103 # We force a copy of actions.items() because we're going to mutate
1104 # actions as we resolve trivial conflicts.
1104 # actions as we resolve trivial conflicts.
1105 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))):
1105 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))):
1106 if f in ancestor and not wctx[f].cmp(ancestor[f]):
1106 if f in ancestor and not wctx[f].cmp(ancestor[f]):
1107 # local did change but ended up with same content
1107 # local did change but ended up with same content
1108 mresult.addfile(
1108 mresult.addfile(
1109 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
1109 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
1110 )
1110 )
1111
1111
1112 for f in list(mresult.files((mergestatemod.ACTION_DELETED_CHANGED,))):
1112 for f in list(mresult.files((mergestatemod.ACTION_DELETED_CHANGED,))):
1113 if f in ancestor and not mctx[f].cmp(ancestor[f]):
1113 if f in ancestor and not mctx[f].cmp(ancestor[f]):
1114 # remote did change but ended up with same content
1114 # remote did change but ended up with same content
1115 mresult.removefile(f) # don't get = keep local deleted
1115 mresult.removefile(f) # don't get = keep local deleted
1116
1116
1117
1117
1118 def calculateupdates(
1118 def calculateupdates(
1119 repo,
1119 repo,
1120 wctx,
1120 wctx,
1121 mctx,
1121 mctx,
1122 ancestors,
1122 ancestors,
1123 branchmerge,
1123 branchmerge,
1124 force,
1124 force,
1125 acceptremote,
1125 acceptremote,
1126 followcopies,
1126 followcopies,
1127 matcher=None,
1127 matcher=None,
1128 mergeforce=False,
1128 mergeforce=False,
1129 ):
1129 ):
1130 """
1130 """
1131 Calculate the actions needed to merge mctx into wctx using ancestors
1131 Calculate the actions needed to merge mctx into wctx using ancestors
1132
1132
1133 Uses manifestmerge() to merge manifest and get list of actions required to
1133 Uses manifestmerge() to merge manifest and get list of actions required to
1134 perform for merging two manifests. If there are multiple ancestors, uses bid
1134 perform for merging two manifests. If there are multiple ancestors, uses bid
1135 merge if enabled.
1135 merge if enabled.
1136
1136
1137 Also filters out actions which are unrequired if repository is sparse.
1137 Also filters out actions which are unrequired if repository is sparse.
1138
1138
1139 Returns mergeresult object same as manifestmerge().
1139 Returns mergeresult object same as manifestmerge().
1140 """
1140 """
1141 # Avoid cycle.
1141 # Avoid cycle.
1142 from . import sparse
1142 from . import sparse
1143
1143
1144 mresult = None
1144 mresult = None
1145 if len(ancestors) == 1: # default
1145 if len(ancestors) == 1: # default
1146 mresult = manifestmerge(
1146 mresult = manifestmerge(
1147 repo,
1147 repo,
1148 wctx,
1148 wctx,
1149 mctx,
1149 mctx,
1150 ancestors[0],
1150 ancestors[0],
1151 branchmerge,
1151 branchmerge,
1152 force,
1152 force,
1153 matcher,
1153 matcher,
1154 acceptremote,
1154 acceptremote,
1155 followcopies,
1155 followcopies,
1156 )
1156 )
1157 _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce)
1157 _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce)
1158
1158
1159 else: # only when merge.preferancestor=* - the default
1159 else: # only when merge.preferancestor=* - the default
1160 repo.ui.note(
1160 repo.ui.note(
1161 _(b"note: merging %s and %s using bids from ancestors %s\n")
1161 _(b"note: merging %s and %s using bids from ancestors %s\n")
1162 % (
1162 % (
1163 wctx,
1163 wctx,
1164 mctx,
1164 mctx,
1165 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1165 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1166 )
1166 )
1167 )
1167 )
1168
1168
1169 # mapping filename to bids (action method to list af actions)
1169 # mapping filename to bids (action method to list af actions)
1170 # {FILENAME1 : BID1, FILENAME2 : BID2}
1170 # {FILENAME1 : BID1, FILENAME2 : BID2}
1171 # BID is another dictionary which contains
1171 # BID is another dictionary which contains
1172 # mapping of following form:
1172 # mapping of following form:
1173 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1173 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1174 fbids = {}
1174 fbids = {}
1175 mresult = mergeresult()
1175 mresult = mergeresult()
1176 diverge, renamedelete = None, None
1176 diverge, renamedelete = None, None
1177 for ancestor in ancestors:
1177 for ancestor in ancestors:
1178 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1178 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1179 mresult1 = manifestmerge(
1179 mresult1 = manifestmerge(
1180 repo,
1180 repo,
1181 wctx,
1181 wctx,
1182 mctx,
1182 mctx,
1183 ancestor,
1183 ancestor,
1184 branchmerge,
1184 branchmerge,
1185 force,
1185 force,
1186 matcher,
1186 matcher,
1187 acceptremote,
1187 acceptremote,
1188 followcopies,
1188 followcopies,
1189 forcefulldiff=True,
1189 forcefulldiff=True,
1190 )
1190 )
1191 _checkunknownfiles(repo, wctx, mctx, force, mresult1, mergeforce)
1191 _checkunknownfiles(repo, wctx, mctx, force, mresult1, mergeforce)
1192
1192
1193 # Track the shortest set of warning on the theory that bid
1193 # Track the shortest set of warning on the theory that bid
1194 # merge will correctly incorporate more information
1194 # merge will correctly incorporate more information
1195 if diverge is None or len(mresult1.diverge) < len(diverge):
1195 if diverge is None or len(mresult1.diverge) < len(diverge):
1196 diverge = mresult1.diverge
1196 diverge = mresult1.diverge
1197 if renamedelete is None or len(renamedelete) < len(
1197 if renamedelete is None or len(renamedelete) < len(
1198 mresult1.renamedelete
1198 mresult1.renamedelete
1199 ):
1199 ):
1200 renamedelete = mresult1.renamedelete
1200 renamedelete = mresult1.renamedelete
1201
1201
1202 # blindly update final mergeresult commitinfo with what we get
1202 # blindly update final mergeresult commitinfo with what we get
1203 # from mergeresult object for each ancestor
1203 # from mergeresult object for each ancestor
1204 # TODO: some commitinfo depends on what bid merge choose and hence
1204 # TODO: some commitinfo depends on what bid merge choose and hence
1205 # we will need to make commitinfo also depend on bid merge logic
1205 # we will need to make commitinfo also depend on bid merge logic
1206 mresult._commitinfo.update(mresult1._commitinfo)
1206 mresult._commitinfo.update(mresult1._commitinfo)
1207
1207
1208 for f, a in mresult1.filemap(sort=True):
1208 for f, a in mresult1.filemap(sort=True):
1209 m, args, msg = a
1209 m, args, msg = a
1210 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1210 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1211 if f in fbids:
1211 if f in fbids:
1212 d = fbids[f]
1212 d = fbids[f]
1213 if m in d:
1213 if m in d:
1214 d[m].append(a)
1214 d[m].append(a)
1215 else:
1215 else:
1216 d[m] = [a]
1216 d[m] = [a]
1217 else:
1217 else:
1218 fbids[f] = {m: [a]}
1218 fbids[f] = {m: [a]}
1219
1219
1220 # Call for bids
1220 # Call for bids
1221 # Pick the best bid for each file
1221 # Pick the best bid for each file
1222 repo.ui.note(
1222 repo.ui.note(
1223 _(b'\nauction for merging merge bids (%d ancestors)\n')
1223 _(b'\nauction for merging merge bids (%d ancestors)\n')
1224 % len(ancestors)
1224 % len(ancestors)
1225 )
1225 )
1226 for f, bids in sorted(fbids.items()):
1226 for f, bids in sorted(fbids.items()):
1227 if repo.ui.debugflag:
1227 if repo.ui.debugflag:
1228 repo.ui.debug(b" list of bids for %s:\n" % f)
1228 repo.ui.debug(b" list of bids for %s:\n" % f)
1229 for m, l in sorted(bids.items()):
1229 for m, l in sorted(bids.items()):
1230 for _f, args, msg in l:
1230 for _f, args, msg in l:
1231 repo.ui.debug(b' %s -> %s\n' % (msg, m))
1231 repo.ui.debug(b' %s -> %s\n' % (msg, m))
1232 # bids is a mapping from action method to list af actions
1232 # bids is a mapping from action method to list af actions
1233 # Consensus?
1233 # Consensus?
1234 if len(bids) == 1: # all bids are the same kind of method
1234 if len(bids) == 1: # all bids are the same kind of method
1235 m, l = list(bids.items())[0]
1235 m, l = list(bids.items())[0]
1236 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1236 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1237 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1237 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1238 mresult.addfile(f, *l[0])
1238 mresult.addfile(f, *l[0])
1239 continue
1239 continue
1240 # If keep is an option, just do it.
1240 # If keep is an option, just do it.
1241 if mergestatemod.ACTION_KEEP in bids:
1241 if mergestatemod.ACTION_KEEP in bids:
1242 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1242 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1243 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1243 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1244 continue
1244 continue
1245 # If keep absent is an option, just do that
1245 # If keep absent is an option, just do that
1246 if mergestatemod.ACTION_KEEP_ABSENT in bids:
1246 if mergestatemod.ACTION_KEEP_ABSENT in bids:
1247 repo.ui.note(_(b" %s: picking 'keep absent' action\n") % f)
1247 repo.ui.note(_(b" %s: picking 'keep absent' action\n") % f)
1248 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_ABSENT][0])
1248 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_ABSENT][0])
1249 continue
1249 continue
1250 # ACTION_KEEP_NEW and ACTION_CHANGED_DELETED are conflicting actions
1250 # ACTION_KEEP_NEW and ACTION_CHANGED_DELETED are conflicting actions
1251 # as one say that file is new while other says that file was present
1251 # as one say that file is new while other says that file was present
1252 # earlier too and has a change delete conflict
1252 # earlier too and has a change delete conflict
1253 # Let's fall back to conflicting ACTION_CHANGED_DELETED and let user
1253 # Let's fall back to conflicting ACTION_CHANGED_DELETED and let user
1254 # do the right thing
1254 # do the right thing
1255 if (
1255 if (
1256 mergestatemod.ACTION_CHANGED_DELETED in bids
1256 mergestatemod.ACTION_CHANGED_DELETED in bids
1257 and mergestatemod.ACTION_KEEP_NEW in bids
1257 and mergestatemod.ACTION_KEEP_NEW in bids
1258 ):
1258 ):
1259 repo.ui.note(_(b" %s: picking 'changed/deleted' action\n") % f)
1259 repo.ui.note(_(b" %s: picking 'changed/deleted' action\n") % f)
1260 mresult.addfile(
1260 mresult.addfile(
1261 f, *bids[mergestatemod.ACTION_CHANGED_DELETED][0]
1261 f, *bids[mergestatemod.ACTION_CHANGED_DELETED][0]
1262 )
1262 )
1263 continue
1263 continue
1264 # If keep new is an option, let's just do that
1264 # If keep new is an option, let's just do that
1265 if mergestatemod.ACTION_KEEP_NEW in bids:
1265 if mergestatemod.ACTION_KEEP_NEW in bids:
1266 repo.ui.note(_(b" %s: picking 'keep new' action\n") % f)
1266 repo.ui.note(_(b" %s: picking 'keep new' action\n") % f)
1267 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_NEW][0])
1267 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_NEW][0])
1268 continue
1268 continue
1269 # ACTION_GET and ACTION_DELETE_CHANGED are conflicting actions as
1269 # ACTION_GET and ACTION_DELETE_CHANGED are conflicting actions as
1270 # one action states the file is newer/created on remote side and
1270 # one action states the file is newer/created on remote side and
1271 # other states that file is deleted locally and changed on remote
1271 # other states that file is deleted locally and changed on remote
1272 # side. Let's fallback and rely on a conflicting action to let user
1272 # side. Let's fallback and rely on a conflicting action to let user
1273 # do the right thing
1273 # do the right thing
1274 if (
1274 if (
1275 mergestatemod.ACTION_DELETED_CHANGED in bids
1275 mergestatemod.ACTION_DELETED_CHANGED in bids
1276 and mergestatemod.ACTION_GET in bids
1276 and mergestatemod.ACTION_GET in bids
1277 ):
1277 ):
1278 repo.ui.note(_(b" %s: picking 'delete/changed' action\n") % f)
1278 repo.ui.note(_(b" %s: picking 'delete/changed' action\n") % f)
1279 mresult.addfile(
1279 mresult.addfile(
1280 f, *bids[mergestatemod.ACTION_DELETED_CHANGED][0]
1280 f, *bids[mergestatemod.ACTION_DELETED_CHANGED][0]
1281 )
1281 )
1282 continue
1282 continue
1283 # If there are gets and they all agree [how could they not?], do it.
1283 # If there are gets and they all agree [how could they not?], do it.
1284 if mergestatemod.ACTION_GET in bids:
1284 if mergestatemod.ACTION_GET in bids:
1285 ga0 = bids[mergestatemod.ACTION_GET][0]
1285 ga0 = bids[mergestatemod.ACTION_GET][0]
1286 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1286 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1287 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1287 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1288 mresult.addfile(f, *ga0)
1288 mresult.addfile(f, *ga0)
1289 continue
1289 continue
1290 # TODO: Consider other simple actions such as mode changes
1290 # TODO: Consider other simple actions such as mode changes
1291 # Handle inefficient democrazy.
1291 # Handle inefficient democrazy.
1292 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1292 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1293 for m, l in sorted(bids.items()):
1293 for m, l in sorted(bids.items()):
1294 for _f, args, msg in l:
1294 for _f, args, msg in l:
1295 repo.ui.note(b' %s -> %s\n' % (msg, m))
1295 repo.ui.note(b' %s -> %s\n' % (msg, m))
1296 # Pick random action. TODO: Instead, prompt user when resolving
1296 # Pick random action. TODO: Instead, prompt user when resolving
1297 m, l = list(bids.items())[0]
1297 m, l = list(bids.items())[0]
1298 repo.ui.warn(
1298 repo.ui.warn(
1299 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1299 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1300 )
1300 )
1301 mresult.addfile(f, *l[0])
1301 mresult.addfile(f, *l[0])
1302 continue
1302 continue
1303 repo.ui.note(_(b'end of auction\n\n'))
1303 repo.ui.note(_(b'end of auction\n\n'))
1304 mresult.updatevalues(diverge, renamedelete)
1304 mresult.updatevalues(diverge, renamedelete)
1305
1305
1306 if wctx.rev() is None:
1306 if wctx.rev() is None:
1307 _forgetremoved(wctx, mctx, branchmerge, mresult)
1307 _forgetremoved(wctx, mctx, branchmerge, mresult)
1308
1308
1309 sparse.filterupdatesactions(repo, wctx, mctx, branchmerge, mresult)
1309 sparse.filterupdatesactions(repo, wctx, mctx, branchmerge, mresult)
1310 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1310 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1311
1311
1312 return mresult
1312 return mresult
1313
1313
1314
1314
1315 def _getcwd():
1315 def _getcwd():
1316 try:
1316 try:
1317 return encoding.getcwd()
1317 return encoding.getcwd()
1318 except OSError as err:
1318 except OSError as err:
1319 if err.errno == errno.ENOENT:
1319 if err.errno == errno.ENOENT:
1320 return None
1320 return None
1321 raise
1321 raise
1322
1322
1323
1323
1324 def batchremove(repo, wctx, actions):
1324 def batchremove(repo, wctx, actions):
1325 """apply removes to the working directory
1325 """apply removes to the working directory
1326
1326
1327 yields tuples for progress updates
1327 yields tuples for progress updates
1328 """
1328 """
1329 verbose = repo.ui.verbose
1329 verbose = repo.ui.verbose
1330 cwd = _getcwd()
1330 cwd = _getcwd()
1331 i = 0
1331 i = 0
1332 for f, args, msg in actions:
1332 for f, args, msg in actions:
1333 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1333 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1334 if verbose:
1334 if verbose:
1335 repo.ui.note(_(b"removing %s\n") % f)
1335 repo.ui.note(_(b"removing %s\n") % f)
1336 wctx[f].audit()
1336 wctx[f].audit()
1337 try:
1337 try:
1338 wctx[f].remove(ignoremissing=True)
1338 wctx[f].remove(ignoremissing=True)
1339 except OSError as inst:
1339 except OSError as inst:
1340 repo.ui.warn(
1340 repo.ui.warn(
1341 _(b"update failed to remove %s: %s!\n")
1341 _(b"update failed to remove %s: %s!\n")
1342 % (f, stringutil.forcebytestr(inst.strerror))
1342 % (f, stringutil.forcebytestr(inst.strerror))
1343 )
1343 )
1344 if i == 100:
1344 if i == 100:
1345 yield i, f
1345 yield i, f
1346 i = 0
1346 i = 0
1347 i += 1
1347 i += 1
1348 if i > 0:
1348 if i > 0:
1349 yield i, f
1349 yield i, f
1350
1350
1351 if cwd and not _getcwd():
1351 if cwd and not _getcwd():
1352 # cwd was removed in the course of removing files; print a helpful
1352 # cwd was removed in the course of removing files; print a helpful
1353 # warning.
1353 # warning.
1354 repo.ui.warn(
1354 repo.ui.warn(
1355 _(
1355 _(
1356 b"current directory was removed\n"
1356 b"current directory was removed\n"
1357 b"(consider changing to repo root: %s)\n"
1357 b"(consider changing to repo root: %s)\n"
1358 )
1358 )
1359 % repo.root
1359 % repo.root
1360 )
1360 )
1361
1361
1362
1362
1363 def batchget(repo, mctx, wctx, wantfiledata, actions):
1363 def batchget(repo, mctx, wctx, wantfiledata, actions):
1364 """apply gets to the working directory
1364 """apply gets to the working directory
1365
1365
1366 mctx is the context to get from
1366 mctx is the context to get from
1367
1367
1368 Yields arbitrarily many (False, tuple) for progress updates, followed by
1368 Yields arbitrarily many (False, tuple) for progress updates, followed by
1369 exactly one (True, filedata). When wantfiledata is false, filedata is an
1369 exactly one (True, filedata). When wantfiledata is false, filedata is an
1370 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1370 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1371 mtime) of the file f written for each action.
1371 mtime) of the file f written for each action.
1372 """
1372 """
1373 filedata = {}
1373 filedata = {}
1374 verbose = repo.ui.verbose
1374 verbose = repo.ui.verbose
1375 fctx = mctx.filectx
1375 fctx = mctx.filectx
1376 ui = repo.ui
1376 ui = repo.ui
1377 i = 0
1377 i = 0
1378 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1378 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1379 for f, (flags, backup), msg in actions:
1379 for f, (flags, backup), msg in actions:
1380 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1380 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1381 if verbose:
1381 if verbose:
1382 repo.ui.note(_(b"getting %s\n") % f)
1382 repo.ui.note(_(b"getting %s\n") % f)
1383
1383
1384 if backup:
1384 if backup:
1385 # If a file or directory exists with the same name, back that
1385 # If a file or directory exists with the same name, back that
1386 # up. Otherwise, look to see if there is a file that conflicts
1386 # up. Otherwise, look to see if there is a file that conflicts
1387 # with a directory this file is in, and if so, back that up.
1387 # with a directory this file is in, and if so, back that up.
1388 conflicting = f
1388 conflicting = f
1389 if not repo.wvfs.lexists(f):
1389 if not repo.wvfs.lexists(f):
1390 for p in pathutil.finddirs(f):
1390 for p in pathutil.finddirs(f):
1391 if repo.wvfs.isfileorlink(p):
1391 if repo.wvfs.isfileorlink(p):
1392 conflicting = p
1392 conflicting = p
1393 break
1393 break
1394 if repo.wvfs.lexists(conflicting):
1394 if repo.wvfs.lexists(conflicting):
1395 orig = scmutil.backuppath(ui, repo, conflicting)
1395 orig = scmutil.backuppath(ui, repo, conflicting)
1396 util.rename(repo.wjoin(conflicting), orig)
1396 util.rename(repo.wjoin(conflicting), orig)
1397 wfctx = wctx[f]
1397 wfctx = wctx[f]
1398 wfctx.clearunknown()
1398 wfctx.clearunknown()
1399 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1399 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1400 size = wfctx.write(
1400 size = wfctx.write(
1401 fctx(f).data(),
1401 fctx(f).data(),
1402 flags,
1402 flags,
1403 backgroundclose=True,
1403 backgroundclose=True,
1404 atomictemp=atomictemp,
1404 atomictemp=atomictemp,
1405 )
1405 )
1406 if wantfiledata:
1406 if wantfiledata:
1407 # XXX note that there is a race window between the time we
1407 # XXX note that there is a race window between the time we
1408 # write the clean data into the file and we stats it. So another
1408 # write the clean data into the file and we stats it. So another
1409 # writing process meddling with the file content right after we
1409 # writing process meddling with the file content right after we
1410 # wrote it could cause bad stat data to be gathered.
1410 # wrote it could cause bad stat data to be gathered.
1411 #
1411 #
1412 # They are 2 data we gather here
1412 # They are 2 data we gather here
1413 # - the mode:
1413 # - the mode:
1414 # That we actually just wrote, we should not need to read
1414 # That we actually just wrote, we should not need to read
1415 # it from disk, (except not all mode might have survived
1415 # it from disk, (except not all mode might have survived
1416 # the disk round-trip, which is another issue: we should
1416 # the disk round-trip, which is another issue: we should
1417 # not depends on this)
1417 # not depends on this)
1418 # - the mtime,
1418 # - the mtime,
1419 # On system that support nanosecond precision, the mtime
1419 # On system that support nanosecond precision, the mtime
1420 # could be accurate enough to tell the two writes appart.
1420 # could be accurate enough to tell the two writes appart.
1421 # However gathering it in a racy way make the mtime we
1421 # However gathering it in a racy way make the mtime we
1422 # gather "unreliable".
1422 # gather "unreliable".
1423 #
1423 #
1424 # (note: we get the size from the data we write, which is sane)
1424 # (note: we get the size from the data we write, which is sane)
1425 #
1425 #
1426 # So in theory the data returned here are fully racy, but in
1426 # So in theory the data returned here are fully racy, but in
1427 # practice "it works mostly fine".
1427 # practice "it works mostly fine".
1428 #
1428 #
1429 # Do not be surprised if you end up reading this while looking
1429 # Do not be surprised if you end up reading this while looking
1430 # for the causes of some buggy status. Feel free to improve
1430 # for the causes of some buggy status. Feel free to improve
1431 # this in the future, but we cannot simply stop gathering
1431 # this in the future, but we cannot simply stop gathering
1432 # information. Otherwise `hg status` call made after a large `hg
1432 # information. Otherwise `hg status` call made after a large `hg
1433 # update` runs would have to redo a similar amount of work to
1433 # update` runs would have to redo a similar amount of work to
1434 # restore and compare all files content.
1434 # restore and compare all files content.
1435 s = wfctx.lstat()
1435 s = wfctx.lstat()
1436 mode = s.st_mode
1436 mode = s.st_mode
1437 mtime = timestamp.mtime_of(s)
1437 mtime = timestamp.mtime_of(s)
1438 # for dirstate.update_file's parentfiledata argument:
1438 # for dirstate.update_file's parentfiledata argument:
1439 filedata[f] = (mode, size, mtime)
1439 filedata[f] = (mode, size, mtime)
1440 if i == 100:
1440 if i == 100:
1441 yield False, (i, f)
1441 yield False, (i, f)
1442 i = 0
1442 i = 0
1443 i += 1
1443 i += 1
1444 if i > 0:
1444 if i > 0:
1445 yield False, (i, f)
1445 yield False, (i, f)
1446 yield True, filedata
1446 yield True, filedata
1447
1447
1448
1448
1449 def _prefetchfiles(repo, ctx, mresult):
1449 def _prefetchfiles(repo, ctx, mresult):
1450 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1450 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1451 of merge actions. ``ctx`` is the context being merged in."""
1451 of merge actions. ``ctx`` is the context being merged in."""
1452
1452
1453 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1453 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1454 # don't touch the context to be merged in. 'cd' is skipped, because
1454 # don't touch the context to be merged in. 'cd' is skipped, because
1455 # changed/deleted never resolves to something from the remote side.
1455 # changed/deleted never resolves to something from the remote side.
1456 files = mresult.files(
1456 files = mresult.files(
1457 [
1457 [
1458 mergestatemod.ACTION_GET,
1458 mergestatemod.ACTION_GET,
1459 mergestatemod.ACTION_DELETED_CHANGED,
1459 mergestatemod.ACTION_DELETED_CHANGED,
1460 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1460 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1461 mergestatemod.ACTION_MERGE,
1461 mergestatemod.ACTION_MERGE,
1462 ]
1462 ]
1463 )
1463 )
1464
1464
1465 prefetch = scmutil.prefetchfiles
1465 prefetch = scmutil.prefetchfiles
1466 matchfiles = scmutil.matchfiles
1466 matchfiles = scmutil.matchfiles
1467 prefetch(
1467 prefetch(
1468 repo,
1468 repo,
1469 [
1469 [
1470 (
1470 (
1471 ctx.rev(),
1471 ctx.rev(),
1472 matchfiles(repo, files),
1472 matchfiles(repo, files),
1473 )
1473 )
1474 ],
1474 ],
1475 )
1475 )
1476
1476
1477
1477
1478 @attr.s(frozen=True)
1478 @attr.s(frozen=True)
1479 class updateresult(object):
1479 class updateresult(object):
1480 updatedcount = attr.ib()
1480 updatedcount = attr.ib()
1481 mergedcount = attr.ib()
1481 mergedcount = attr.ib()
1482 removedcount = attr.ib()
1482 removedcount = attr.ib()
1483 unresolvedcount = attr.ib()
1483 unresolvedcount = attr.ib()
1484
1484
1485 def isempty(self):
1485 def isempty(self):
1486 return not (
1486 return not (
1487 self.updatedcount
1487 self.updatedcount
1488 or self.mergedcount
1488 or self.mergedcount
1489 or self.removedcount
1489 or self.removedcount
1490 or self.unresolvedcount
1490 or self.unresolvedcount
1491 )
1491 )
1492
1492
1493
1493
1494 def applyupdates(
1494 def applyupdates(
1495 repo,
1495 repo,
1496 mresult,
1496 mresult,
1497 wctx,
1497 wctx,
1498 mctx,
1498 mctx,
1499 overwrite,
1499 overwrite,
1500 wantfiledata,
1500 wantfiledata,
1501 labels=None,
1501 labels=None,
1502 ):
1502 ):
1503 """apply the merge action list to the working directory
1503 """apply the merge action list to the working directory
1504
1504
1505 mresult is a mergeresult object representing result of the merge
1505 mresult is a mergeresult object representing result of the merge
1506 wctx is the working copy context
1506 wctx is the working copy context
1507 mctx is the context to be merged into the working copy
1507 mctx is the context to be merged into the working copy
1508
1508
1509 Return a tuple of (counts, filedata), where counts is a tuple
1509 Return a tuple of (counts, filedata), where counts is a tuple
1510 (updated, merged, removed, unresolved) that describes how many
1510 (updated, merged, removed, unresolved) that describes how many
1511 files were affected by the update, and filedata is as described in
1511 files were affected by the update, and filedata is as described in
1512 batchget.
1512 batchget.
1513 """
1513 """
1514
1514
1515 _prefetchfiles(repo, mctx, mresult)
1515 _prefetchfiles(repo, mctx, mresult)
1516
1516
1517 updated, merged, removed = 0, 0, 0
1517 updated, merged, removed = 0, 0, 0
1518 ms = wctx.mergestate(clean=True)
1518 ms = wctx.mergestate(clean=True)
1519 ms.start(wctx.p1().node(), mctx.node(), labels)
1519 ms.start(wctx.p1().node(), mctx.node(), labels)
1520
1520
1521 for f, op in pycompat.iteritems(mresult.commitinfo):
1521 for f, op in pycompat.iteritems(mresult.commitinfo):
1522 # the other side of filenode was choosen while merging, store this in
1522 # the other side of filenode was choosen while merging, store this in
1523 # mergestate so that it can be reused on commit
1523 # mergestate so that it can be reused on commit
1524 ms.addcommitinfo(f, op)
1524 ms.addcommitinfo(f, op)
1525
1525
1526 numupdates = mresult.len() - mresult.len(mergestatemod.NO_OP_ACTIONS)
1526 numupdates = mresult.len() - mresult.len(mergestatemod.NO_OP_ACTIONS)
1527 progress = repo.ui.makeprogress(
1527 progress = repo.ui.makeprogress(
1528 _(b'updating'), unit=_(b'files'), total=numupdates
1528 _(b'updating'), unit=_(b'files'), total=numupdates
1529 )
1529 )
1530
1530
1531 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_REMOVE]:
1531 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_REMOVE]:
1532 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1532 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1533
1533
1534 # record path conflicts
1534 # record path conflicts
1535 for f, args, msg in mresult.getactions(
1535 for f, args, msg in mresult.getactions(
1536 [mergestatemod.ACTION_PATH_CONFLICT], sort=True
1536 [mergestatemod.ACTION_PATH_CONFLICT], sort=True
1537 ):
1537 ):
1538 f1, fo = args
1538 f1, fo = args
1539 s = repo.ui.status
1539 s = repo.ui.status
1540 s(
1540 s(
1541 _(
1541 _(
1542 b"%s: path conflict - a file or link has the same name as a "
1542 b"%s: path conflict - a file or link has the same name as a "
1543 b"directory\n"
1543 b"directory\n"
1544 )
1544 )
1545 % f
1545 % f
1546 )
1546 )
1547 if fo == b'l':
1547 if fo == b'l':
1548 s(_(b"the local file has been renamed to %s\n") % f1)
1548 s(_(b"the local file has been renamed to %s\n") % f1)
1549 else:
1549 else:
1550 s(_(b"the remote file has been renamed to %s\n") % f1)
1550 s(_(b"the remote file has been renamed to %s\n") % f1)
1551 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1551 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1552 ms.addpathconflict(f, f1, fo)
1552 ms.addpathconflict(f, f1, fo)
1553 progress.increment(item=f)
1553 progress.increment(item=f)
1554
1554
1555 # When merging in-memory, we can't support worker processes, so set the
1555 # When merging in-memory, we can't support worker processes, so set the
1556 # per-item cost at 0 in that case.
1556 # per-item cost at 0 in that case.
1557 cost = 0 if wctx.isinmemory() else 0.001
1557 cost = 0 if wctx.isinmemory() else 0.001
1558
1558
1559 # remove in parallel (must come before resolving path conflicts and getting)
1559 # remove in parallel (must come before resolving path conflicts and getting)
1560 prog = worker.worker(
1560 prog = worker.worker(
1561 repo.ui,
1561 repo.ui,
1562 cost,
1562 cost,
1563 batchremove,
1563 batchremove,
1564 (repo, wctx),
1564 (repo, wctx),
1565 list(mresult.getactions([mergestatemod.ACTION_REMOVE], sort=True)),
1565 list(mresult.getactions([mergestatemod.ACTION_REMOVE], sort=True)),
1566 )
1566 )
1567 for i, item in prog:
1567 for i, item in prog:
1568 progress.increment(step=i, item=item)
1568 progress.increment(step=i, item=item)
1569 removed = mresult.len((mergestatemod.ACTION_REMOVE,))
1569 removed = mresult.len((mergestatemod.ACTION_REMOVE,))
1570
1570
1571 # resolve path conflicts (must come before getting)
1571 # resolve path conflicts (must come before getting)
1572 for f, args, msg in mresult.getactions(
1572 for f, args, msg in mresult.getactions(
1573 [mergestatemod.ACTION_PATH_CONFLICT_RESOLVE], sort=True
1573 [mergestatemod.ACTION_PATH_CONFLICT_RESOLVE], sort=True
1574 ):
1574 ):
1575 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1575 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1576 (f0, origf0) = args
1576 (f0, origf0) = args
1577 if wctx[f0].lexists():
1577 if wctx[f0].lexists():
1578 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1578 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1579 wctx[f].audit()
1579 wctx[f].audit()
1580 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1580 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1581 wctx[f0].remove()
1581 wctx[f0].remove()
1582 progress.increment(item=f)
1582 progress.increment(item=f)
1583
1583
1584 # get in parallel.
1584 # get in parallel.
1585 threadsafe = repo.ui.configbool(
1585 threadsafe = repo.ui.configbool(
1586 b'experimental', b'worker.wdir-get-thread-safe'
1586 b'experimental', b'worker.wdir-get-thread-safe'
1587 )
1587 )
1588 prog = worker.worker(
1588 prog = worker.worker(
1589 repo.ui,
1589 repo.ui,
1590 cost,
1590 cost,
1591 batchget,
1591 batchget,
1592 (repo, mctx, wctx, wantfiledata),
1592 (repo, mctx, wctx, wantfiledata),
1593 list(mresult.getactions([mergestatemod.ACTION_GET], sort=True)),
1593 list(mresult.getactions([mergestatemod.ACTION_GET], sort=True)),
1594 threadsafe=threadsafe,
1594 threadsafe=threadsafe,
1595 hasretval=True,
1595 hasretval=True,
1596 )
1596 )
1597 getfiledata = {}
1597 getfiledata = {}
1598 for final, res in prog:
1598 for final, res in prog:
1599 if final:
1599 if final:
1600 getfiledata = res
1600 getfiledata = res
1601 else:
1601 else:
1602 i, item = res
1602 i, item = res
1603 progress.increment(step=i, item=item)
1603 progress.increment(step=i, item=item)
1604
1604
1605 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_GET]:
1605 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_GET]:
1606 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1606 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1607
1607
1608 # forget (manifest only, just log it) (must come first)
1608 # forget (manifest only, just log it) (must come first)
1609 for f, args, msg in mresult.getactions(
1609 for f, args, msg in mresult.getactions(
1610 (mergestatemod.ACTION_FORGET,), sort=True
1610 (mergestatemod.ACTION_FORGET,), sort=True
1611 ):
1611 ):
1612 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1612 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1613 progress.increment(item=f)
1613 progress.increment(item=f)
1614
1614
1615 # re-add (manifest only, just log it)
1615 # re-add (manifest only, just log it)
1616 for f, args, msg in mresult.getactions(
1616 for f, args, msg in mresult.getactions(
1617 (mergestatemod.ACTION_ADD,), sort=True
1617 (mergestatemod.ACTION_ADD,), sort=True
1618 ):
1618 ):
1619 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1619 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1620 progress.increment(item=f)
1620 progress.increment(item=f)
1621
1621
1622 # re-add/mark as modified (manifest only, just log it)
1622 # re-add/mark as modified (manifest only, just log it)
1623 for f, args, msg in mresult.getactions(
1623 for f, args, msg in mresult.getactions(
1624 (mergestatemod.ACTION_ADD_MODIFIED,), sort=True
1624 (mergestatemod.ACTION_ADD_MODIFIED,), sort=True
1625 ):
1625 ):
1626 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1626 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1627 progress.increment(item=f)
1627 progress.increment(item=f)
1628
1628
1629 # keep (noop, just log it)
1629 # keep (noop, just log it)
1630 for a in mergestatemod.NO_OP_ACTIONS:
1630 for a in mergestatemod.NO_OP_ACTIONS:
1631 for f, args, msg in mresult.getactions((a,), sort=True):
1631 for f, args, msg in mresult.getactions((a,), sort=True):
1632 repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a))
1632 repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a))
1633 # no progress
1633 # no progress
1634
1634
1635 # directory rename, move local
1635 # directory rename, move local
1636 for f, args, msg in mresult.getactions(
1636 for f, args, msg in mresult.getactions(
1637 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,), sort=True
1637 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,), sort=True
1638 ):
1638 ):
1639 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1639 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1640 progress.increment(item=f)
1640 progress.increment(item=f)
1641 f0, flags = args
1641 f0, flags = args
1642 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1642 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1643 wctx[f].audit()
1643 wctx[f].audit()
1644 wctx[f].write(wctx.filectx(f0).data(), flags)
1644 wctx[f].write(wctx.filectx(f0).data(), flags)
1645 wctx[f0].remove()
1645 wctx[f0].remove()
1646
1646
1647 # local directory rename, get
1647 # local directory rename, get
1648 for f, args, msg in mresult.getactions(
1648 for f, args, msg in mresult.getactions(
1649 (mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,), sort=True
1649 (mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,), sort=True
1650 ):
1650 ):
1651 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1651 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1652 progress.increment(item=f)
1652 progress.increment(item=f)
1653 f0, flags = args
1653 f0, flags = args
1654 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1654 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1655 wctx[f].write(mctx.filectx(f0).data(), flags)
1655 wctx[f].write(mctx.filectx(f0).data(), flags)
1656
1656
1657 # exec
1657 # exec
1658 for f, args, msg in mresult.getactions(
1658 for f, args, msg in mresult.getactions(
1659 (mergestatemod.ACTION_EXEC,), sort=True
1659 (mergestatemod.ACTION_EXEC,), sort=True
1660 ):
1660 ):
1661 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1661 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1662 progress.increment(item=f)
1662 progress.increment(item=f)
1663 (flags,) = args
1663 (flags,) = args
1664 wctx[f].audit()
1664 wctx[f].audit()
1665 wctx[f].setflags(b'l' in flags, b'x' in flags)
1665 wctx[f].setflags(b'l' in flags, b'x' in flags)
1666
1666
1667 moves = []
1667 moves = []
1668
1668
1669 # 'cd' and 'dc' actions are treated like other merge conflicts
1669 # 'cd' and 'dc' actions are treated like other merge conflicts
1670 mergeactions = list(
1670 mergeactions = list(
1671 mresult.getactions(
1671 mresult.getactions(
1672 [
1672 [
1673 mergestatemod.ACTION_CHANGED_DELETED,
1673 mergestatemod.ACTION_CHANGED_DELETED,
1674 mergestatemod.ACTION_DELETED_CHANGED,
1674 mergestatemod.ACTION_DELETED_CHANGED,
1675 mergestatemod.ACTION_MERGE,
1675 mergestatemod.ACTION_MERGE,
1676 ],
1676 ],
1677 sort=True,
1677 sort=True,
1678 )
1678 )
1679 )
1679 )
1680 for f, args, msg in mergeactions:
1680 for f, args, msg in mergeactions:
1681 f1, f2, fa, move, anc = args
1681 f1, f2, fa, move, anc = args
1682 if f == b'.hgsubstate': # merged internally
1682 if f == b'.hgsubstate': # merged internally
1683 continue
1683 continue
1684 if f1 is None:
1684 if f1 is None:
1685 fcl = filemerge.absentfilectx(wctx, fa)
1685 fcl = filemerge.absentfilectx(wctx, fa)
1686 else:
1686 else:
1687 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1687 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1688 fcl = wctx[f1]
1688 fcl = wctx[f1]
1689 if f2 is None:
1689 if f2 is None:
1690 fco = filemerge.absentfilectx(mctx, fa)
1690 fco = filemerge.absentfilectx(mctx, fa)
1691 else:
1691 else:
1692 fco = mctx[f2]
1692 fco = mctx[f2]
1693 actx = repo[anc]
1693 actx = repo[anc]
1694 if fa in actx:
1694 if fa in actx:
1695 fca = actx[fa]
1695 fca = actx[fa]
1696 else:
1696 else:
1697 # TODO: move to absentfilectx
1697 # TODO: move to absentfilectx
1698 fca = repo.filectx(f1, fileid=nullrev)
1698 fca = repo.filectx(f1, fileid=nullrev)
1699 ms.add(fcl, fco, fca, f)
1699 ms.add(fcl, fco, fca, f)
1700 if f1 != f and move:
1700 if f1 != f and move:
1701 moves.append(f1)
1701 moves.append(f1)
1702
1702
1703 # remove renamed files after safely stored
1703 # remove renamed files after safely stored
1704 for f in moves:
1704 for f in moves:
1705 if wctx[f].lexists():
1705 if wctx[f].lexists():
1706 repo.ui.debug(b"removing %s\n" % f)
1706 repo.ui.debug(b"removing %s\n" % f)
1707 wctx[f].audit()
1707 wctx[f].audit()
1708 wctx[f].remove()
1708 wctx[f].remove()
1709
1709
1710 # these actions updates the file
1710 # these actions updates the file
1711 updated = mresult.len(
1711 updated = mresult.len(
1712 (
1712 (
1713 mergestatemod.ACTION_GET,
1713 mergestatemod.ACTION_GET,
1714 mergestatemod.ACTION_EXEC,
1714 mergestatemod.ACTION_EXEC,
1715 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1715 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1716 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1716 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1717 )
1717 )
1718 )
1718 )
1719
1719
1720 try:
1720 try:
1721 # premerge
1722 tocomplete = []
1723 for f, args, msg in mergeactions:
1721 for f, args, msg in mergeactions:
1724 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1722 repo.ui.debug(b" %s: %s -> m\n" % (f, msg))
1725 ms.addcommitinfo(f, {b'merged': b'yes'})
1723 ms.addcommitinfo(f, {b'merged': b'yes'})
1726 progress.increment(item=f)
1724 progress.increment(item=f)
1727 if f == b'.hgsubstate': # subrepo states need updating
1725 if f == b'.hgsubstate': # subrepo states need updating
1728 subrepoutil.submerge(
1726 subrepoutil.submerge(
1729 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1727 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1730 )
1728 )
1731 continue
1729 continue
1732 wctx[f].audit()
1730 wctx[f].audit()
1733 complete, r = ms.preresolve(f, wctx)
1734 if not complete:
1735 numupdates += 1
1736 tocomplete.append((f, args, msg))
1737
1738 # merge
1739 for f, args, msg in tocomplete:
1740 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1741 ms.addcommitinfo(f, {b'merged': b'yes'})
1742 progress.increment(item=f, total=numupdates)
1743 ms.resolve(f, wctx)
1731 ms.resolve(f, wctx)
1744
1732
1745 except error.InterventionRequired:
1733 except error.InterventionRequired:
1746 # If the user has merge.on-failure=halt, catch the error and close the
1734 # If the user has merge.on-failure=halt, catch the error and close the
1747 # merge state "properly".
1735 # merge state "properly".
1748 pass
1736 pass
1749 finally:
1737 finally:
1750 ms.commit()
1738 ms.commit()
1751
1739
1752 unresolved = ms.unresolvedcount()
1740 unresolved = ms.unresolvedcount()
1753
1741
1754 msupdated, msmerged, msremoved = ms.counts()
1742 msupdated, msmerged, msremoved = ms.counts()
1755 updated += msupdated
1743 updated += msupdated
1756 merged += msmerged
1744 merged += msmerged
1757 removed += msremoved
1745 removed += msremoved
1758
1746
1759 extraactions = ms.actions()
1747 extraactions = ms.actions()
1760
1748
1761 progress.complete()
1749 progress.complete()
1762 return (
1750 return (
1763 updateresult(updated, merged, removed, unresolved),
1751 updateresult(updated, merged, removed, unresolved),
1764 getfiledata,
1752 getfiledata,
1765 extraactions,
1753 extraactions,
1766 )
1754 )
1767
1755
1768
1756
1769 def _advertisefsmonitor(repo, num_gets, p1node):
1757 def _advertisefsmonitor(repo, num_gets, p1node):
1770 # Advertise fsmonitor when its presence could be useful.
1758 # Advertise fsmonitor when its presence could be useful.
1771 #
1759 #
1772 # We only advertise when performing an update from an empty working
1760 # We only advertise when performing an update from an empty working
1773 # directory. This typically only occurs during initial clone.
1761 # directory. This typically only occurs during initial clone.
1774 #
1762 #
1775 # We give users a mechanism to disable the warning in case it is
1763 # We give users a mechanism to disable the warning in case it is
1776 # annoying.
1764 # annoying.
1777 #
1765 #
1778 # We only allow on Linux and MacOS because that's where fsmonitor is
1766 # We only allow on Linux and MacOS because that's where fsmonitor is
1779 # considered stable.
1767 # considered stable.
1780 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1768 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1781 fsmonitorthreshold = repo.ui.configint(
1769 fsmonitorthreshold = repo.ui.configint(
1782 b'fsmonitor', b'warn_update_file_count'
1770 b'fsmonitor', b'warn_update_file_count'
1783 )
1771 )
1784 # avoid cycle dirstate -> sparse -> merge -> dirstate
1772 # avoid cycle dirstate -> sparse -> merge -> dirstate
1785 from . import dirstate
1773 from . import dirstate
1786
1774
1787 if dirstate.rustmod is not None:
1775 if dirstate.rustmod is not None:
1788 # When using rust status, fsmonitor becomes necessary at higher sizes
1776 # When using rust status, fsmonitor becomes necessary at higher sizes
1789 fsmonitorthreshold = repo.ui.configint(
1777 fsmonitorthreshold = repo.ui.configint(
1790 b'fsmonitor',
1778 b'fsmonitor',
1791 b'warn_update_file_count_rust',
1779 b'warn_update_file_count_rust',
1792 )
1780 )
1793
1781
1794 try:
1782 try:
1795 # avoid cycle: extensions -> cmdutil -> merge
1783 # avoid cycle: extensions -> cmdutil -> merge
1796 from . import extensions
1784 from . import extensions
1797
1785
1798 extensions.find(b'fsmonitor')
1786 extensions.find(b'fsmonitor')
1799 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1787 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1800 # We intentionally don't look at whether fsmonitor has disabled
1788 # We intentionally don't look at whether fsmonitor has disabled
1801 # itself because a) fsmonitor may have already printed a warning
1789 # itself because a) fsmonitor may have already printed a warning
1802 # b) we only care about the config state here.
1790 # b) we only care about the config state here.
1803 except KeyError:
1791 except KeyError:
1804 fsmonitorenabled = False
1792 fsmonitorenabled = False
1805
1793
1806 if (
1794 if (
1807 fsmonitorwarning
1795 fsmonitorwarning
1808 and not fsmonitorenabled
1796 and not fsmonitorenabled
1809 and p1node == repo.nullid
1797 and p1node == repo.nullid
1810 and num_gets >= fsmonitorthreshold
1798 and num_gets >= fsmonitorthreshold
1811 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1799 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1812 ):
1800 ):
1813 repo.ui.warn(
1801 repo.ui.warn(
1814 _(
1802 _(
1815 b'(warning: large working directory being used without '
1803 b'(warning: large working directory being used without '
1816 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1804 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1817 b'see "hg help -e fsmonitor")\n'
1805 b'see "hg help -e fsmonitor")\n'
1818 )
1806 )
1819 )
1807 )
1820
1808
1821
1809
1822 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1810 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1823 UPDATECHECK_NONE = b'none'
1811 UPDATECHECK_NONE = b'none'
1824 UPDATECHECK_LINEAR = b'linear'
1812 UPDATECHECK_LINEAR = b'linear'
1825 UPDATECHECK_NO_CONFLICT = b'noconflict'
1813 UPDATECHECK_NO_CONFLICT = b'noconflict'
1826
1814
1827
1815
1828 def _update(
1816 def _update(
1829 repo,
1817 repo,
1830 node,
1818 node,
1831 branchmerge,
1819 branchmerge,
1832 force,
1820 force,
1833 ancestor=None,
1821 ancestor=None,
1834 mergeancestor=False,
1822 mergeancestor=False,
1835 labels=None,
1823 labels=None,
1836 matcher=None,
1824 matcher=None,
1837 mergeforce=False,
1825 mergeforce=False,
1838 updatedirstate=True,
1826 updatedirstate=True,
1839 updatecheck=None,
1827 updatecheck=None,
1840 wc=None,
1828 wc=None,
1841 ):
1829 ):
1842 """
1830 """
1843 Perform a merge between the working directory and the given node
1831 Perform a merge between the working directory and the given node
1844
1832
1845 node = the node to update to
1833 node = the node to update to
1846 branchmerge = whether to merge between branches
1834 branchmerge = whether to merge between branches
1847 force = whether to force branch merging or file overwriting
1835 force = whether to force branch merging or file overwriting
1848 matcher = a matcher to filter file lists (dirstate not updated)
1836 matcher = a matcher to filter file lists (dirstate not updated)
1849 mergeancestor = whether it is merging with an ancestor. If true,
1837 mergeancestor = whether it is merging with an ancestor. If true,
1850 we should accept the incoming changes for any prompts that occur.
1838 we should accept the incoming changes for any prompts that occur.
1851 If false, merging with an ancestor (fast-forward) is only allowed
1839 If false, merging with an ancestor (fast-forward) is only allowed
1852 between different named branches. This flag is used by rebase extension
1840 between different named branches. This flag is used by rebase extension
1853 as a temporary fix and should be avoided in general.
1841 as a temporary fix and should be avoided in general.
1854 labels = labels to use for base, local and other
1842 labels = labels to use for base, local and other
1855 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1843 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1856 this is True, then 'force' should be True as well.
1844 this is True, then 'force' should be True as well.
1857
1845
1858 The table below shows all the behaviors of the update command given the
1846 The table below shows all the behaviors of the update command given the
1859 -c/--check and -C/--clean or no options, whether the working directory is
1847 -c/--check and -C/--clean or no options, whether the working directory is
1860 dirty, whether a revision is specified, and the relationship of the parent
1848 dirty, whether a revision is specified, and the relationship of the parent
1861 rev to the target rev (linear or not). Match from top first. The -n
1849 rev to the target rev (linear or not). Match from top first. The -n
1862 option doesn't exist on the command line, but represents the
1850 option doesn't exist on the command line, but represents the
1863 experimental.updatecheck=noconflict option.
1851 experimental.updatecheck=noconflict option.
1864
1852
1865 This logic is tested by test-update-branches.t.
1853 This logic is tested by test-update-branches.t.
1866
1854
1867 -c -C -n -m dirty rev linear | result
1855 -c -C -n -m dirty rev linear | result
1868 y y * * * * * | (1)
1856 y y * * * * * | (1)
1869 y * y * * * * | (1)
1857 y * y * * * * | (1)
1870 y * * y * * * | (1)
1858 y * * y * * * | (1)
1871 * y y * * * * | (1)
1859 * y y * * * * | (1)
1872 * y * y * * * | (1)
1860 * y * y * * * | (1)
1873 * * y y * * * | (1)
1861 * * y y * * * | (1)
1874 * * * * * n n | x
1862 * * * * * n n | x
1875 * * * * n * * | ok
1863 * * * * n * * | ok
1876 n n n n y * y | merge
1864 n n n n y * y | merge
1877 n n n n y y n | (2)
1865 n n n n y y n | (2)
1878 n n n y y * * | merge
1866 n n n y y * * | merge
1879 n n y n y * * | merge if no conflict
1867 n n y n y * * | merge if no conflict
1880 n y n n y * * | discard
1868 n y n n y * * | discard
1881 y n n n y * * | (3)
1869 y n n n y * * | (3)
1882
1870
1883 x = can't happen
1871 x = can't happen
1884 * = don't-care
1872 * = don't-care
1885 1 = incompatible options (checked in commands.py)
1873 1 = incompatible options (checked in commands.py)
1886 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1874 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1887 3 = abort: uncommitted changes (checked in commands.py)
1875 3 = abort: uncommitted changes (checked in commands.py)
1888
1876
1889 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1877 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1890 to repo[None] if None is passed.
1878 to repo[None] if None is passed.
1891
1879
1892 Return the same tuple as applyupdates().
1880 Return the same tuple as applyupdates().
1893 """
1881 """
1894 # Avoid cycle.
1882 # Avoid cycle.
1895 from . import sparse
1883 from . import sparse
1896
1884
1897 # This function used to find the default destination if node was None, but
1885 # This function used to find the default destination if node was None, but
1898 # that's now in destutil.py.
1886 # that's now in destutil.py.
1899 assert node is not None
1887 assert node is not None
1900 if not branchmerge and not force:
1888 if not branchmerge and not force:
1901 # TODO: remove the default once all callers that pass branchmerge=False
1889 # TODO: remove the default once all callers that pass branchmerge=False
1902 # and force=False pass a value for updatecheck. We may want to allow
1890 # and force=False pass a value for updatecheck. We may want to allow
1903 # updatecheck='abort' to better suppport some of these callers.
1891 # updatecheck='abort' to better suppport some of these callers.
1904 if updatecheck is None:
1892 if updatecheck is None:
1905 updatecheck = UPDATECHECK_LINEAR
1893 updatecheck = UPDATECHECK_LINEAR
1906 if updatecheck not in (
1894 if updatecheck not in (
1907 UPDATECHECK_NONE,
1895 UPDATECHECK_NONE,
1908 UPDATECHECK_LINEAR,
1896 UPDATECHECK_LINEAR,
1909 UPDATECHECK_NO_CONFLICT,
1897 UPDATECHECK_NO_CONFLICT,
1910 ):
1898 ):
1911 raise ValueError(
1899 raise ValueError(
1912 r'Invalid updatecheck %r (can accept %r)'
1900 r'Invalid updatecheck %r (can accept %r)'
1913 % (
1901 % (
1914 updatecheck,
1902 updatecheck,
1915 (
1903 (
1916 UPDATECHECK_NONE,
1904 UPDATECHECK_NONE,
1917 UPDATECHECK_LINEAR,
1905 UPDATECHECK_LINEAR,
1918 UPDATECHECK_NO_CONFLICT,
1906 UPDATECHECK_NO_CONFLICT,
1919 ),
1907 ),
1920 )
1908 )
1921 )
1909 )
1922 if wc is not None and wc.isinmemory():
1910 if wc is not None and wc.isinmemory():
1923 maybe_wlock = util.nullcontextmanager()
1911 maybe_wlock = util.nullcontextmanager()
1924 else:
1912 else:
1925 maybe_wlock = repo.wlock()
1913 maybe_wlock = repo.wlock()
1926 with maybe_wlock:
1914 with maybe_wlock:
1927 if wc is None:
1915 if wc is None:
1928 wc = repo[None]
1916 wc = repo[None]
1929 pl = wc.parents()
1917 pl = wc.parents()
1930 p1 = pl[0]
1918 p1 = pl[0]
1931 p2 = repo[node]
1919 p2 = repo[node]
1932 if ancestor is not None:
1920 if ancestor is not None:
1933 pas = [repo[ancestor]]
1921 pas = [repo[ancestor]]
1934 else:
1922 else:
1935 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1923 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1936 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1924 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1937 pas = [repo[anc] for anc in (sorted(cahs) or [repo.nullid])]
1925 pas = [repo[anc] for anc in (sorted(cahs) or [repo.nullid])]
1938 else:
1926 else:
1939 pas = [p1.ancestor(p2, warn=branchmerge)]
1927 pas = [p1.ancestor(p2, warn=branchmerge)]
1940
1928
1941 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1929 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1942
1930
1943 overwrite = force and not branchmerge
1931 overwrite = force and not branchmerge
1944 ### check phase
1932 ### check phase
1945 if not overwrite:
1933 if not overwrite:
1946 if len(pl) > 1:
1934 if len(pl) > 1:
1947 raise error.StateError(_(b"outstanding uncommitted merge"))
1935 raise error.StateError(_(b"outstanding uncommitted merge"))
1948 ms = wc.mergestate()
1936 ms = wc.mergestate()
1949 if ms.unresolvedcount():
1937 if ms.unresolvedcount():
1950 raise error.StateError(
1938 raise error.StateError(
1951 _(b"outstanding merge conflicts"),
1939 _(b"outstanding merge conflicts"),
1952 hint=_(b"use 'hg resolve' to resolve"),
1940 hint=_(b"use 'hg resolve' to resolve"),
1953 )
1941 )
1954 if branchmerge:
1942 if branchmerge:
1955 if pas == [p2]:
1943 if pas == [p2]:
1956 raise error.Abort(
1944 raise error.Abort(
1957 _(
1945 _(
1958 b"merging with a working directory ancestor"
1946 b"merging with a working directory ancestor"
1959 b" has no effect"
1947 b" has no effect"
1960 )
1948 )
1961 )
1949 )
1962 elif pas == [p1]:
1950 elif pas == [p1]:
1963 if not mergeancestor and wc.branch() == p2.branch():
1951 if not mergeancestor and wc.branch() == p2.branch():
1964 raise error.Abort(
1952 raise error.Abort(
1965 _(b"nothing to merge"),
1953 _(b"nothing to merge"),
1966 hint=_(b"use 'hg update' or check 'hg heads'"),
1954 hint=_(b"use 'hg update' or check 'hg heads'"),
1967 )
1955 )
1968 if not force and (wc.files() or wc.deleted()):
1956 if not force and (wc.files() or wc.deleted()):
1969 raise error.StateError(
1957 raise error.StateError(
1970 _(b"uncommitted changes"),
1958 _(b"uncommitted changes"),
1971 hint=_(b"use 'hg status' to list changes"),
1959 hint=_(b"use 'hg status' to list changes"),
1972 )
1960 )
1973 if not wc.isinmemory():
1961 if not wc.isinmemory():
1974 for s in sorted(wc.substate):
1962 for s in sorted(wc.substate):
1975 wc.sub(s).bailifchanged()
1963 wc.sub(s).bailifchanged()
1976
1964
1977 elif not overwrite:
1965 elif not overwrite:
1978 if p1 == p2: # no-op update
1966 if p1 == p2: # no-op update
1979 # call the hooks and exit early
1967 # call the hooks and exit early
1980 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1968 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1981 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1969 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1982 return updateresult(0, 0, 0, 0)
1970 return updateresult(0, 0, 0, 0)
1983
1971
1984 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1972 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1985 [p1],
1973 [p1],
1986 [p2],
1974 [p2],
1987 ): # nonlinear
1975 ): # nonlinear
1988 dirty = wc.dirty(missing=True)
1976 dirty = wc.dirty(missing=True)
1989 if dirty:
1977 if dirty:
1990 # Branching is a bit strange to ensure we do the minimal
1978 # Branching is a bit strange to ensure we do the minimal
1991 # amount of call to obsutil.foreground.
1979 # amount of call to obsutil.foreground.
1992 foreground = obsutil.foreground(repo, [p1.node()])
1980 foreground = obsutil.foreground(repo, [p1.node()])
1993 # note: the <node> variable contains a random identifier
1981 # note: the <node> variable contains a random identifier
1994 if repo[node].node() in foreground:
1982 if repo[node].node() in foreground:
1995 pass # allow updating to successors
1983 pass # allow updating to successors
1996 else:
1984 else:
1997 msg = _(b"uncommitted changes")
1985 msg = _(b"uncommitted changes")
1998 hint = _(b"commit or update --clean to discard changes")
1986 hint = _(b"commit or update --clean to discard changes")
1999 raise error.UpdateAbort(msg, hint=hint)
1987 raise error.UpdateAbort(msg, hint=hint)
2000 else:
1988 else:
2001 # Allow jumping branches if clean and specific rev given
1989 # Allow jumping branches if clean and specific rev given
2002 pass
1990 pass
2003
1991
2004 if overwrite:
1992 if overwrite:
2005 pas = [wc]
1993 pas = [wc]
2006 elif not branchmerge:
1994 elif not branchmerge:
2007 pas = [p1]
1995 pas = [p1]
2008
1996
2009 # deprecated config: merge.followcopies
1997 # deprecated config: merge.followcopies
2010 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1998 followcopies = repo.ui.configbool(b'merge', b'followcopies')
2011 if overwrite:
1999 if overwrite:
2012 followcopies = False
2000 followcopies = False
2013 elif not pas[0]:
2001 elif not pas[0]:
2014 followcopies = False
2002 followcopies = False
2015 if not branchmerge and not wc.dirty(missing=True):
2003 if not branchmerge and not wc.dirty(missing=True):
2016 followcopies = False
2004 followcopies = False
2017
2005
2018 ### calculate phase
2006 ### calculate phase
2019 mresult = calculateupdates(
2007 mresult = calculateupdates(
2020 repo,
2008 repo,
2021 wc,
2009 wc,
2022 p2,
2010 p2,
2023 pas,
2011 pas,
2024 branchmerge,
2012 branchmerge,
2025 force,
2013 force,
2026 mergeancestor,
2014 mergeancestor,
2027 followcopies,
2015 followcopies,
2028 matcher=matcher,
2016 matcher=matcher,
2029 mergeforce=mergeforce,
2017 mergeforce=mergeforce,
2030 )
2018 )
2031
2019
2032 if updatecheck == UPDATECHECK_NO_CONFLICT:
2020 if updatecheck == UPDATECHECK_NO_CONFLICT:
2033 if mresult.hasconflicts():
2021 if mresult.hasconflicts():
2034 msg = _(b"conflicting changes")
2022 msg = _(b"conflicting changes")
2035 hint = _(b"commit or update --clean to discard changes")
2023 hint = _(b"commit or update --clean to discard changes")
2036 raise error.StateError(msg, hint=hint)
2024 raise error.StateError(msg, hint=hint)
2037
2025
2038 # Prompt and create actions. Most of this is in the resolve phase
2026 # Prompt and create actions. Most of this is in the resolve phase
2039 # already, but we can't handle .hgsubstate in filemerge or
2027 # already, but we can't handle .hgsubstate in filemerge or
2040 # subrepoutil.submerge yet so we have to keep prompting for it.
2028 # subrepoutil.submerge yet so we have to keep prompting for it.
2041 vals = mresult.getfile(b'.hgsubstate')
2029 vals = mresult.getfile(b'.hgsubstate')
2042 if vals:
2030 if vals:
2043 f = b'.hgsubstate'
2031 f = b'.hgsubstate'
2044 m, args, msg = vals
2032 m, args, msg = vals
2045 prompts = filemerge.partextras(labels)
2033 prompts = filemerge.partextras(labels)
2046 prompts[b'f'] = f
2034 prompts[b'f'] = f
2047 if m == mergestatemod.ACTION_CHANGED_DELETED:
2035 if m == mergestatemod.ACTION_CHANGED_DELETED:
2048 if repo.ui.promptchoice(
2036 if repo.ui.promptchoice(
2049 _(
2037 _(
2050 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2038 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2051 b"use (c)hanged version or (d)elete?"
2039 b"use (c)hanged version or (d)elete?"
2052 b"$$ &Changed $$ &Delete"
2040 b"$$ &Changed $$ &Delete"
2053 )
2041 )
2054 % prompts,
2042 % prompts,
2055 0,
2043 0,
2056 ):
2044 ):
2057 mresult.addfile(
2045 mresult.addfile(
2058 f,
2046 f,
2059 mergestatemod.ACTION_REMOVE,
2047 mergestatemod.ACTION_REMOVE,
2060 None,
2048 None,
2061 b'prompt delete',
2049 b'prompt delete',
2062 )
2050 )
2063 elif f in p1:
2051 elif f in p1:
2064 mresult.addfile(
2052 mresult.addfile(
2065 f,
2053 f,
2066 mergestatemod.ACTION_ADD_MODIFIED,
2054 mergestatemod.ACTION_ADD_MODIFIED,
2067 None,
2055 None,
2068 b'prompt keep',
2056 b'prompt keep',
2069 )
2057 )
2070 else:
2058 else:
2071 mresult.addfile(
2059 mresult.addfile(
2072 f,
2060 f,
2073 mergestatemod.ACTION_ADD,
2061 mergestatemod.ACTION_ADD,
2074 None,
2062 None,
2075 b'prompt keep',
2063 b'prompt keep',
2076 )
2064 )
2077 elif m == mergestatemod.ACTION_DELETED_CHANGED:
2065 elif m == mergestatemod.ACTION_DELETED_CHANGED:
2078 f1, f2, fa, move, anc = args
2066 f1, f2, fa, move, anc = args
2079 flags = p2[f2].flags()
2067 flags = p2[f2].flags()
2080 if (
2068 if (
2081 repo.ui.promptchoice(
2069 repo.ui.promptchoice(
2082 _(
2070 _(
2083 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2071 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2084 b"use (c)hanged version or leave (d)eleted?"
2072 b"use (c)hanged version or leave (d)eleted?"
2085 b"$$ &Changed $$ &Deleted"
2073 b"$$ &Changed $$ &Deleted"
2086 )
2074 )
2087 % prompts,
2075 % prompts,
2088 0,
2076 0,
2089 )
2077 )
2090 == 0
2078 == 0
2091 ):
2079 ):
2092 mresult.addfile(
2080 mresult.addfile(
2093 f,
2081 f,
2094 mergestatemod.ACTION_GET,
2082 mergestatemod.ACTION_GET,
2095 (flags, False),
2083 (flags, False),
2096 b'prompt recreating',
2084 b'prompt recreating',
2097 )
2085 )
2098 else:
2086 else:
2099 mresult.removefile(f)
2087 mresult.removefile(f)
2100
2088
2101 if not util.fscasesensitive(repo.path):
2089 if not util.fscasesensitive(repo.path):
2102 # check collision between files only in p2 for clean update
2090 # check collision between files only in p2 for clean update
2103 if not branchmerge and (
2091 if not branchmerge and (
2104 force or not wc.dirty(missing=True, branch=False)
2092 force or not wc.dirty(missing=True, branch=False)
2105 ):
2093 ):
2106 _checkcollision(repo, p2.manifest(), None)
2094 _checkcollision(repo, p2.manifest(), None)
2107 else:
2095 else:
2108 _checkcollision(repo, wc.manifest(), mresult)
2096 _checkcollision(repo, wc.manifest(), mresult)
2109
2097
2110 # divergent renames
2098 # divergent renames
2111 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
2099 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
2112 repo.ui.warn(
2100 repo.ui.warn(
2113 _(
2101 _(
2114 b"note: possible conflict - %s was renamed "
2102 b"note: possible conflict - %s was renamed "
2115 b"multiple times to:\n"
2103 b"multiple times to:\n"
2116 )
2104 )
2117 % f
2105 % f
2118 )
2106 )
2119 for nf in sorted(fl):
2107 for nf in sorted(fl):
2120 repo.ui.warn(b" %s\n" % nf)
2108 repo.ui.warn(b" %s\n" % nf)
2121
2109
2122 # rename and delete
2110 # rename and delete
2123 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
2111 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
2124 repo.ui.warn(
2112 repo.ui.warn(
2125 _(
2113 _(
2126 b"note: possible conflict - %s was deleted "
2114 b"note: possible conflict - %s was deleted "
2127 b"and renamed to:\n"
2115 b"and renamed to:\n"
2128 )
2116 )
2129 % f
2117 % f
2130 )
2118 )
2131 for nf in sorted(fl):
2119 for nf in sorted(fl):
2132 repo.ui.warn(b" %s\n" % nf)
2120 repo.ui.warn(b" %s\n" % nf)
2133
2121
2134 ### apply phase
2122 ### apply phase
2135 if not branchmerge: # just jump to the new rev
2123 if not branchmerge: # just jump to the new rev
2136 fp1, fp2, xp1, xp2 = fp2, repo.nullid, xp2, b''
2124 fp1, fp2, xp1, xp2 = fp2, repo.nullid, xp2, b''
2137 # If we're doing a partial update, we need to skip updating
2125 # If we're doing a partial update, we need to skip updating
2138 # the dirstate.
2126 # the dirstate.
2139 always = matcher is None or matcher.always()
2127 always = matcher is None or matcher.always()
2140 updatedirstate = updatedirstate and always and not wc.isinmemory()
2128 updatedirstate = updatedirstate and always and not wc.isinmemory()
2141 if updatedirstate:
2129 if updatedirstate:
2142 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2130 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2143 # note that we're in the middle of an update
2131 # note that we're in the middle of an update
2144 repo.vfs.write(b'updatestate', p2.hex())
2132 repo.vfs.write(b'updatestate', p2.hex())
2145
2133
2146 _advertisefsmonitor(
2134 _advertisefsmonitor(
2147 repo, mresult.len((mergestatemod.ACTION_GET,)), p1.node()
2135 repo, mresult.len((mergestatemod.ACTION_GET,)), p1.node()
2148 )
2136 )
2149
2137
2150 wantfiledata = updatedirstate and not branchmerge
2138 wantfiledata = updatedirstate and not branchmerge
2151 stats, getfiledata, extraactions = applyupdates(
2139 stats, getfiledata, extraactions = applyupdates(
2152 repo,
2140 repo,
2153 mresult,
2141 mresult,
2154 wc,
2142 wc,
2155 p2,
2143 p2,
2156 overwrite,
2144 overwrite,
2157 wantfiledata,
2145 wantfiledata,
2158 labels=labels,
2146 labels=labels,
2159 )
2147 )
2160
2148
2161 if updatedirstate:
2149 if updatedirstate:
2162 if extraactions:
2150 if extraactions:
2163 for k, acts in pycompat.iteritems(extraactions):
2151 for k, acts in pycompat.iteritems(extraactions):
2164 for a in acts:
2152 for a in acts:
2165 mresult.addfile(a[0], k, *a[1:])
2153 mresult.addfile(a[0], k, *a[1:])
2166 if k == mergestatemod.ACTION_GET and wantfiledata:
2154 if k == mergestatemod.ACTION_GET and wantfiledata:
2167 # no filedata until mergestate is updated to provide it
2155 # no filedata until mergestate is updated to provide it
2168 for a in acts:
2156 for a in acts:
2169 getfiledata[a[0]] = None
2157 getfiledata[a[0]] = None
2170
2158
2171 assert len(getfiledata) == (
2159 assert len(getfiledata) == (
2172 mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0
2160 mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0
2173 )
2161 )
2174 with repo.dirstate.parentchange():
2162 with repo.dirstate.parentchange():
2175 ### Filter Filedata
2163 ### Filter Filedata
2176 #
2164 #
2177 # We gathered "cache" information for the clean file while
2165 # We gathered "cache" information for the clean file while
2178 # updating them: mtime, size and mode.
2166 # updating them: mtime, size and mode.
2179 #
2167 #
2180 # At the time this comment is written, they are various issues
2168 # At the time this comment is written, they are various issues
2181 # with how we gather the `mode` and `mtime` information (see
2169 # with how we gather the `mode` and `mtime` information (see
2182 # the comment in `batchget`).
2170 # the comment in `batchget`).
2183 #
2171 #
2184 # We are going to smooth one of this issue here : mtime ambiguity.
2172 # We are going to smooth one of this issue here : mtime ambiguity.
2185 #
2173 #
2186 # i.e. even if the mtime gathered during `batchget` was
2174 # i.e. even if the mtime gathered during `batchget` was
2187 # correct[1] a change happening right after it could change the
2175 # correct[1] a change happening right after it could change the
2188 # content while keeping the same mtime[2].
2176 # content while keeping the same mtime[2].
2189 #
2177 #
2190 # When we reach the current code, the "on disk" part of the
2178 # When we reach the current code, the "on disk" part of the
2191 # update operation is finished. We still assume that no other
2179 # update operation is finished. We still assume that no other
2192 # process raced that "on disk" part, but we want to at least
2180 # process raced that "on disk" part, but we want to at least
2193 # prevent later file change to alter the content of the file
2181 # prevent later file change to alter the content of the file
2194 # right after the update operation. So quickly that the same
2182 # right after the update operation. So quickly that the same
2195 # mtime is record for the operation.
2183 # mtime is record for the operation.
2196 # To prevent such ambiguity to happens, we will only keep the
2184 # To prevent such ambiguity to happens, we will only keep the
2197 # "file data" for files with mtime that are stricly in the past,
2185 # "file data" for files with mtime that are stricly in the past,
2198 # i.e. whose mtime is strictly lower than the current time.
2186 # i.e. whose mtime is strictly lower than the current time.
2199 #
2187 #
2200 # This protect us from race conditions from operation that could
2188 # This protect us from race conditions from operation that could
2201 # run right after this one, especially other Mercurial
2189 # run right after this one, especially other Mercurial
2202 # operation that could be waiting for the wlock to touch files
2190 # operation that could be waiting for the wlock to touch files
2203 # content and the dirstate.
2191 # content and the dirstate.
2204 #
2192 #
2205 # In an ideal world, we could only get reliable information in
2193 # In an ideal world, we could only get reliable information in
2206 # `getfiledata` (from `getbatch`), however the current approach
2194 # `getfiledata` (from `getbatch`), however the current approach
2207 # have been a successful compromise since many years.
2195 # have been a successful compromise since many years.
2208 #
2196 #
2209 # At the time this comment is written, not using any "cache"
2197 # At the time this comment is written, not using any "cache"
2210 # file data at all here would not be viable. As it would result is
2198 # file data at all here would not be viable. As it would result is
2211 # a very large amount of work (equivalent to the previous `hg
2199 # a very large amount of work (equivalent to the previous `hg
2212 # update` during the next status after an update).
2200 # update` during the next status after an update).
2213 #
2201 #
2214 # [1] the current code cannot grantee that the `mtime` and
2202 # [1] the current code cannot grantee that the `mtime` and
2215 # `mode` are correct, but the result is "okay in practice".
2203 # `mode` are correct, but the result is "okay in practice".
2216 # (see the comment in `batchget`). #
2204 # (see the comment in `batchget`). #
2217 #
2205 #
2218 # [2] using nano-second precision can greatly help here because
2206 # [2] using nano-second precision can greatly help here because
2219 # it makes the "different write with same mtime" issue
2207 # it makes the "different write with same mtime" issue
2220 # virtually vanish. However, dirstate v1 cannot store such
2208 # virtually vanish. However, dirstate v1 cannot store such
2221 # precision and a bunch of python-runtime, operating-system and
2209 # precision and a bunch of python-runtime, operating-system and
2222 # filesystem does not provide use with such precision , so we
2210 # filesystem does not provide use with such precision , so we
2223 # have to operate as if it wasn't available.
2211 # have to operate as if it wasn't available.
2224 if getfiledata:
2212 if getfiledata:
2225 ambiguous_mtime = {}
2213 ambiguous_mtime = {}
2226 now = timestamp.get_fs_now(repo.vfs)
2214 now = timestamp.get_fs_now(repo.vfs)
2227 if now is None:
2215 if now is None:
2228 # we can't write to the FS, so we won't actually update
2216 # we can't write to the FS, so we won't actually update
2229 # the dirstate content anyway, no need to put cache
2217 # the dirstate content anyway, no need to put cache
2230 # information.
2218 # information.
2231 getfiledata = None
2219 getfiledata = None
2232 else:
2220 else:
2233 now_sec = now[0]
2221 now_sec = now[0]
2234 for f, m in pycompat.iteritems(getfiledata):
2222 for f, m in pycompat.iteritems(getfiledata):
2235 if m is not None and m[2][0] >= now_sec:
2223 if m is not None and m[2][0] >= now_sec:
2236 ambiguous_mtime[f] = (m[0], m[1], None)
2224 ambiguous_mtime[f] = (m[0], m[1], None)
2237 for f, m in pycompat.iteritems(ambiguous_mtime):
2225 for f, m in pycompat.iteritems(ambiguous_mtime):
2238 getfiledata[f] = m
2226 getfiledata[f] = m
2239
2227
2240 repo.setparents(fp1, fp2)
2228 repo.setparents(fp1, fp2)
2241 mergestatemod.recordupdates(
2229 mergestatemod.recordupdates(
2242 repo, mresult.actionsdict, branchmerge, getfiledata
2230 repo, mresult.actionsdict, branchmerge, getfiledata
2243 )
2231 )
2244 # update completed, clear state
2232 # update completed, clear state
2245 util.unlink(repo.vfs.join(b'updatestate'))
2233 util.unlink(repo.vfs.join(b'updatestate'))
2246
2234
2247 if not branchmerge:
2235 if not branchmerge:
2248 repo.dirstate.setbranch(p2.branch())
2236 repo.dirstate.setbranch(p2.branch())
2249
2237
2250 # If we're updating to a location, clean up any stale temporary includes
2238 # If we're updating to a location, clean up any stale temporary includes
2251 # (ex: this happens during hg rebase --abort).
2239 # (ex: this happens during hg rebase --abort).
2252 if not branchmerge:
2240 if not branchmerge:
2253 sparse.prunetemporaryincludes(repo)
2241 sparse.prunetemporaryincludes(repo)
2254
2242
2255 if updatedirstate:
2243 if updatedirstate:
2256 repo.hook(
2244 repo.hook(
2257 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2245 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2258 )
2246 )
2259 return stats
2247 return stats
2260
2248
2261
2249
2262 def merge(ctx, labels=None, force=False, wc=None):
2250 def merge(ctx, labels=None, force=False, wc=None):
2263 """Merge another topological branch into the working copy.
2251 """Merge another topological branch into the working copy.
2264
2252
2265 force = whether the merge was run with 'merge --force' (deprecated)
2253 force = whether the merge was run with 'merge --force' (deprecated)
2266 """
2254 """
2267
2255
2268 return _update(
2256 return _update(
2269 ctx.repo(),
2257 ctx.repo(),
2270 ctx.rev(),
2258 ctx.rev(),
2271 labels=labels,
2259 labels=labels,
2272 branchmerge=True,
2260 branchmerge=True,
2273 force=force,
2261 force=force,
2274 mergeforce=force,
2262 mergeforce=force,
2275 wc=wc,
2263 wc=wc,
2276 )
2264 )
2277
2265
2278
2266
2279 def update(ctx, updatecheck=None, wc=None):
2267 def update(ctx, updatecheck=None, wc=None):
2280 """Do a regular update to the given commit, aborting if there are conflicts.
2268 """Do a regular update to the given commit, aborting if there are conflicts.
2281
2269
2282 The 'updatecheck' argument can be used to control what to do in case of
2270 The 'updatecheck' argument can be used to control what to do in case of
2283 conflicts.
2271 conflicts.
2284
2272
2285 Note: This is a new, higher-level update() than the one that used to exist
2273 Note: This is a new, higher-level update() than the one that used to exist
2286 in this module. That function is now called _update(). You can hopefully
2274 in this module. That function is now called _update(). You can hopefully
2287 replace your callers to use this new update(), or clean_update(), merge(),
2275 replace your callers to use this new update(), or clean_update(), merge(),
2288 revert_to(), or graft().
2276 revert_to(), or graft().
2289 """
2277 """
2290 return _update(
2278 return _update(
2291 ctx.repo(),
2279 ctx.repo(),
2292 ctx.rev(),
2280 ctx.rev(),
2293 branchmerge=False,
2281 branchmerge=False,
2294 force=False,
2282 force=False,
2295 labels=[b'working copy', b'destination'],
2283 labels=[b'working copy', b'destination'],
2296 updatecheck=updatecheck,
2284 updatecheck=updatecheck,
2297 wc=wc,
2285 wc=wc,
2298 )
2286 )
2299
2287
2300
2288
2301 def clean_update(ctx, wc=None):
2289 def clean_update(ctx, wc=None):
2302 """Do a clean update to the given commit.
2290 """Do a clean update to the given commit.
2303
2291
2304 This involves updating to the commit and discarding any changes in the
2292 This involves updating to the commit and discarding any changes in the
2305 working copy.
2293 working copy.
2306 """
2294 """
2307 return _update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2295 return _update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2308
2296
2309
2297
2310 def revert_to(ctx, matcher=None, wc=None):
2298 def revert_to(ctx, matcher=None, wc=None):
2311 """Revert the working copy to the given commit.
2299 """Revert the working copy to the given commit.
2312
2300
2313 The working copy will keep its current parent(s) but its content will
2301 The working copy will keep its current parent(s) but its content will
2314 be the same as in the given commit.
2302 be the same as in the given commit.
2315 """
2303 """
2316
2304
2317 return _update(
2305 return _update(
2318 ctx.repo(),
2306 ctx.repo(),
2319 ctx.rev(),
2307 ctx.rev(),
2320 branchmerge=False,
2308 branchmerge=False,
2321 force=True,
2309 force=True,
2322 updatedirstate=False,
2310 updatedirstate=False,
2323 matcher=matcher,
2311 matcher=matcher,
2324 wc=wc,
2312 wc=wc,
2325 )
2313 )
2326
2314
2327
2315
2328 def graft(
2316 def graft(
2329 repo,
2317 repo,
2330 ctx,
2318 ctx,
2331 base=None,
2319 base=None,
2332 labels=None,
2320 labels=None,
2333 keepparent=False,
2321 keepparent=False,
2334 keepconflictparent=False,
2322 keepconflictparent=False,
2335 wctx=None,
2323 wctx=None,
2336 ):
2324 ):
2337 """Do a graft-like merge.
2325 """Do a graft-like merge.
2338
2326
2339 This is a merge where the merge ancestor is chosen such that one
2327 This is a merge where the merge ancestor is chosen such that one
2340 or more changesets are grafted onto the current changeset. In
2328 or more changesets are grafted onto the current changeset. In
2341 addition to the merge, this fixes up the dirstate to include only
2329 addition to the merge, this fixes up the dirstate to include only
2342 a single parent (if keepparent is False) and tries to duplicate any
2330 a single parent (if keepparent is False) and tries to duplicate any
2343 renames/copies appropriately.
2331 renames/copies appropriately.
2344
2332
2345 ctx - changeset to rebase
2333 ctx - changeset to rebase
2346 base - merge base, or ctx.p1() if not specified
2334 base - merge base, or ctx.p1() if not specified
2347 labels - merge labels eg ['local', 'graft']
2335 labels - merge labels eg ['local', 'graft']
2348 keepparent - keep second parent if any
2336 keepparent - keep second parent if any
2349 keepconflictparent - if unresolved, keep parent used for the merge
2337 keepconflictparent - if unresolved, keep parent used for the merge
2350
2338
2351 """
2339 """
2352 # If we're grafting a descendant onto an ancestor, be sure to pass
2340 # If we're grafting a descendant onto an ancestor, be sure to pass
2353 # mergeancestor=True to update. This does two things: 1) allows the merge if
2341 # mergeancestor=True to update. This does two things: 1) allows the merge if
2354 # the destination is the same as the parent of the ctx (so we can use graft
2342 # the destination is the same as the parent of the ctx (so we can use graft
2355 # to copy commits), and 2) informs update that the incoming changes are
2343 # to copy commits), and 2) informs update that the incoming changes are
2356 # newer than the destination so it doesn't prompt about "remote changed foo
2344 # newer than the destination so it doesn't prompt about "remote changed foo
2357 # which local deleted".
2345 # which local deleted".
2358 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2346 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2359 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2347 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2360 wctx = wctx or repo[None]
2348 wctx = wctx or repo[None]
2361 pctx = wctx.p1()
2349 pctx = wctx.p1()
2362 base = base or ctx.p1()
2350 base = base or ctx.p1()
2363 mergeancestor = (
2351 mergeancestor = (
2364 repo.changelog.isancestor(pctx.node(), ctx.node())
2352 repo.changelog.isancestor(pctx.node(), ctx.node())
2365 or pctx.rev() == base.rev()
2353 or pctx.rev() == base.rev()
2366 )
2354 )
2367
2355
2368 stats = _update(
2356 stats = _update(
2369 repo,
2357 repo,
2370 ctx.node(),
2358 ctx.node(),
2371 True,
2359 True,
2372 True,
2360 True,
2373 base.node(),
2361 base.node(),
2374 mergeancestor=mergeancestor,
2362 mergeancestor=mergeancestor,
2375 labels=labels,
2363 labels=labels,
2376 wc=wctx,
2364 wc=wctx,
2377 )
2365 )
2378
2366
2379 if keepconflictparent and stats.unresolvedcount:
2367 if keepconflictparent and stats.unresolvedcount:
2380 pother = ctx.node()
2368 pother = ctx.node()
2381 else:
2369 else:
2382 pother = repo.nullid
2370 pother = repo.nullid
2383 parents = ctx.parents()
2371 parents = ctx.parents()
2384 if keepparent and len(parents) == 2 and base in parents:
2372 if keepparent and len(parents) == 2 and base in parents:
2385 parents.remove(base)
2373 parents.remove(base)
2386 pother = parents[0].node()
2374 pother = parents[0].node()
2387 # Never set both parents equal to each other
2375 # Never set both parents equal to each other
2388 if pother == pctx.node():
2376 if pother == pctx.node():
2389 pother = repo.nullid
2377 pother = repo.nullid
2390
2378
2391 if wctx.isinmemory():
2379 if wctx.isinmemory():
2392 wctx.setparents(pctx.node(), pother)
2380 wctx.setparents(pctx.node(), pother)
2393 # fix up dirstate for copies and renames
2381 # fix up dirstate for copies and renames
2394 copies.graftcopies(wctx, ctx, base)
2382 copies.graftcopies(wctx, ctx, base)
2395 else:
2383 else:
2396 with repo.dirstate.parentchange():
2384 with repo.dirstate.parentchange():
2397 repo.setparents(pctx.node(), pother)
2385 repo.setparents(pctx.node(), pother)
2398 repo.dirstate.write(repo.currenttransaction())
2386 repo.dirstate.write(repo.currenttransaction())
2399 # fix up dirstate for copies and renames
2387 # fix up dirstate for copies and renames
2400 copies.graftcopies(wctx, ctx, base)
2388 copies.graftcopies(wctx, ctx, base)
2401 return stats
2389 return stats
2402
2390
2403
2391
2404 def back_out(ctx, parent=None, wc=None):
2392 def back_out(ctx, parent=None, wc=None):
2405 if parent is None:
2393 if parent is None:
2406 if ctx.p2() is not None:
2394 if ctx.p2() is not None:
2407 raise error.ProgrammingError(
2395 raise error.ProgrammingError(
2408 b"must specify parent of merge commit to back out"
2396 b"must specify parent of merge commit to back out"
2409 )
2397 )
2410 parent = ctx.p1()
2398 parent = ctx.p1()
2411 return _update(
2399 return _update(
2412 ctx.repo(),
2400 ctx.repo(),
2413 parent,
2401 parent,
2414 branchmerge=True,
2402 branchmerge=True,
2415 force=True,
2403 force=True,
2416 ancestor=ctx.node(),
2404 ancestor=ctx.node(),
2417 mergeancestor=False,
2405 mergeancestor=False,
2418 )
2406 )
2419
2407
2420
2408
2421 def purge(
2409 def purge(
2422 repo,
2410 repo,
2423 matcher,
2411 matcher,
2424 unknown=True,
2412 unknown=True,
2425 ignored=False,
2413 ignored=False,
2426 removeemptydirs=True,
2414 removeemptydirs=True,
2427 removefiles=True,
2415 removefiles=True,
2428 abortonerror=False,
2416 abortonerror=False,
2429 noop=False,
2417 noop=False,
2430 confirm=False,
2418 confirm=False,
2431 ):
2419 ):
2432 """Purge the working directory of untracked files.
2420 """Purge the working directory of untracked files.
2433
2421
2434 ``matcher`` is a matcher configured to scan the working directory -
2422 ``matcher`` is a matcher configured to scan the working directory -
2435 potentially a subset.
2423 potentially a subset.
2436
2424
2437 ``unknown`` controls whether unknown files should be purged.
2425 ``unknown`` controls whether unknown files should be purged.
2438
2426
2439 ``ignored`` controls whether ignored files should be purged.
2427 ``ignored`` controls whether ignored files should be purged.
2440
2428
2441 ``removeemptydirs`` controls whether empty directories should be removed.
2429 ``removeemptydirs`` controls whether empty directories should be removed.
2442
2430
2443 ``removefiles`` controls whether files are removed.
2431 ``removefiles`` controls whether files are removed.
2444
2432
2445 ``abortonerror`` causes an exception to be raised if an error occurs
2433 ``abortonerror`` causes an exception to be raised if an error occurs
2446 deleting a file or directory.
2434 deleting a file or directory.
2447
2435
2448 ``noop`` controls whether to actually remove files. If not defined, actions
2436 ``noop`` controls whether to actually remove files. If not defined, actions
2449 will be taken.
2437 will be taken.
2450
2438
2451 ``confirm`` ask confirmation before actually removing anything.
2439 ``confirm`` ask confirmation before actually removing anything.
2452
2440
2453 Returns an iterable of relative paths in the working directory that were
2441 Returns an iterable of relative paths in the working directory that were
2454 or would be removed.
2442 or would be removed.
2455 """
2443 """
2456
2444
2457 def remove(removefn, path):
2445 def remove(removefn, path):
2458 try:
2446 try:
2459 removefn(path)
2447 removefn(path)
2460 except OSError:
2448 except OSError:
2461 m = _(b'%s cannot be removed') % path
2449 m = _(b'%s cannot be removed') % path
2462 if abortonerror:
2450 if abortonerror:
2463 raise error.Abort(m)
2451 raise error.Abort(m)
2464 else:
2452 else:
2465 repo.ui.warn(_(b'warning: %s\n') % m)
2453 repo.ui.warn(_(b'warning: %s\n') % m)
2466
2454
2467 # There's no API to copy a matcher. So mutate the passed matcher and
2455 # There's no API to copy a matcher. So mutate the passed matcher and
2468 # restore it when we're done.
2456 # restore it when we're done.
2469 oldtraversedir = matcher.traversedir
2457 oldtraversedir = matcher.traversedir
2470
2458
2471 res = []
2459 res = []
2472
2460
2473 try:
2461 try:
2474 if removeemptydirs:
2462 if removeemptydirs:
2475 directories = []
2463 directories = []
2476 matcher.traversedir = directories.append
2464 matcher.traversedir = directories.append
2477
2465
2478 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2466 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2479
2467
2480 if confirm:
2468 if confirm:
2481 nb_ignored = len(status.ignored)
2469 nb_ignored = len(status.ignored)
2482 nb_unknown = len(status.unknown)
2470 nb_unknown = len(status.unknown)
2483 if nb_unknown and nb_ignored:
2471 if nb_unknown and nb_ignored:
2484 msg = _(b"permanently delete %d unknown and %d ignored files?")
2472 msg = _(b"permanently delete %d unknown and %d ignored files?")
2485 msg %= (nb_unknown, nb_ignored)
2473 msg %= (nb_unknown, nb_ignored)
2486 elif nb_unknown:
2474 elif nb_unknown:
2487 msg = _(b"permanently delete %d unknown files?")
2475 msg = _(b"permanently delete %d unknown files?")
2488 msg %= nb_unknown
2476 msg %= nb_unknown
2489 elif nb_ignored:
2477 elif nb_ignored:
2490 msg = _(b"permanently delete %d ignored files?")
2478 msg = _(b"permanently delete %d ignored files?")
2491 msg %= nb_ignored
2479 msg %= nb_ignored
2492 elif removeemptydirs:
2480 elif removeemptydirs:
2493 dir_count = 0
2481 dir_count = 0
2494 for f in directories:
2482 for f in directories:
2495 if matcher(f) and not repo.wvfs.listdir(f):
2483 if matcher(f) and not repo.wvfs.listdir(f):
2496 dir_count += 1
2484 dir_count += 1
2497 if dir_count:
2485 if dir_count:
2498 msg = _(
2486 msg = _(
2499 b"permanently delete at least %d empty directories?"
2487 b"permanently delete at least %d empty directories?"
2500 )
2488 )
2501 msg %= dir_count
2489 msg %= dir_count
2502 else:
2490 else:
2503 # XXX we might be missing directory there
2491 # XXX we might be missing directory there
2504 return res
2492 return res
2505 msg += b" (yN)$$ &Yes $$ &No"
2493 msg += b" (yN)$$ &Yes $$ &No"
2506 if repo.ui.promptchoice(msg, default=1) == 1:
2494 if repo.ui.promptchoice(msg, default=1) == 1:
2507 raise error.CanceledError(_(b'removal cancelled'))
2495 raise error.CanceledError(_(b'removal cancelled'))
2508
2496
2509 if removefiles:
2497 if removefiles:
2510 for f in sorted(status.unknown + status.ignored):
2498 for f in sorted(status.unknown + status.ignored):
2511 if not noop:
2499 if not noop:
2512 repo.ui.note(_(b'removing file %s\n') % f)
2500 repo.ui.note(_(b'removing file %s\n') % f)
2513 remove(repo.wvfs.unlink, f)
2501 remove(repo.wvfs.unlink, f)
2514 res.append(f)
2502 res.append(f)
2515
2503
2516 if removeemptydirs:
2504 if removeemptydirs:
2517 for f in sorted(directories, reverse=True):
2505 for f in sorted(directories, reverse=True):
2518 if matcher(f) and not repo.wvfs.listdir(f):
2506 if matcher(f) and not repo.wvfs.listdir(f):
2519 if not noop:
2507 if not noop:
2520 repo.ui.note(_(b'removing directory %s\n') % f)
2508 repo.ui.note(_(b'removing directory %s\n') % f)
2521 remove(repo.wvfs.rmdir, f)
2509 remove(repo.wvfs.rmdir, f)
2522 res.append(f)
2510 res.append(f)
2523
2511
2524 return res
2512 return res
2525
2513
2526 finally:
2514 finally:
2527 matcher.traversedir = oldtraversedir
2515 matcher.traversedir = oldtraversedir
@@ -1,871 +1,868 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import collections
3 import collections
4 import errno
4 import errno
5 import shutil
5 import shutil
6 import struct
6 import struct
7
7
8 from .i18n import _
8 from .i18n import _
9 from .node import (
9 from .node import (
10 bin,
10 bin,
11 hex,
11 hex,
12 nullrev,
12 nullrev,
13 )
13 )
14 from . import (
14 from . import (
15 error,
15 error,
16 filemerge,
16 filemerge,
17 pycompat,
17 pycompat,
18 util,
18 util,
19 )
19 )
20 from .utils import hashutil
20 from .utils import hashutil
21
21
22 _pack = struct.pack
22 _pack = struct.pack
23 _unpack = struct.unpack
23 _unpack = struct.unpack
24
24
25
25
26 def _droponode(data):
26 def _droponode(data):
27 # used for compatibility for v1
27 # used for compatibility for v1
28 bits = data.split(b'\0')
28 bits = data.split(b'\0')
29 bits = bits[:-2] + bits[-1:]
29 bits = bits[:-2] + bits[-1:]
30 return b'\0'.join(bits)
30 return b'\0'.join(bits)
31
31
32
32
33 def _filectxorabsent(hexnode, ctx, f):
33 def _filectxorabsent(hexnode, ctx, f):
34 if hexnode == ctx.repo().nodeconstants.nullhex:
34 if hexnode == ctx.repo().nodeconstants.nullhex:
35 return filemerge.absentfilectx(ctx, f)
35 return filemerge.absentfilectx(ctx, f)
36 else:
36 else:
37 return ctx[f]
37 return ctx[f]
38
38
39
39
40 # Merge state record types. See ``mergestate`` docs for more.
40 # Merge state record types. See ``mergestate`` docs for more.
41
41
42 ####
42 ####
43 # merge records which records metadata about a current merge
43 # merge records which records metadata about a current merge
44 # exists only once in a mergestate
44 # exists only once in a mergestate
45 #####
45 #####
46 RECORD_LOCAL = b'L'
46 RECORD_LOCAL = b'L'
47 RECORD_OTHER = b'O'
47 RECORD_OTHER = b'O'
48 # record merge labels
48 # record merge labels
49 RECORD_LABELS = b'l'
49 RECORD_LABELS = b'l'
50
50
51 #####
51 #####
52 # record extra information about files, with one entry containing info about one
52 # record extra information about files, with one entry containing info about one
53 # file. Hence, multiple of them can exists
53 # file. Hence, multiple of them can exists
54 #####
54 #####
55 RECORD_FILE_VALUES = b'f'
55 RECORD_FILE_VALUES = b'f'
56
56
57 #####
57 #####
58 # merge records which represents state of individual merges of files/folders
58 # merge records which represents state of individual merges of files/folders
59 # These are top level records for each entry containing merge related info.
59 # These are top level records for each entry containing merge related info.
60 # Each record of these has info about one file. Hence multiple of them can
60 # Each record of these has info about one file. Hence multiple of them can
61 # exists
61 # exists
62 #####
62 #####
63 RECORD_MERGED = b'F'
63 RECORD_MERGED = b'F'
64 RECORD_CHANGEDELETE_CONFLICT = b'C'
64 RECORD_CHANGEDELETE_CONFLICT = b'C'
65 # the path was dir on one side of merge and file on another
65 # the path was dir on one side of merge and file on another
66 RECORD_PATH_CONFLICT = b'P'
66 RECORD_PATH_CONFLICT = b'P'
67
67
68 #####
68 #####
69 # possible state which a merge entry can have. These are stored inside top-level
69 # possible state which a merge entry can have. These are stored inside top-level
70 # merge records mentioned just above.
70 # merge records mentioned just above.
71 #####
71 #####
72 MERGE_RECORD_UNRESOLVED = b'u'
72 MERGE_RECORD_UNRESOLVED = b'u'
73 MERGE_RECORD_RESOLVED = b'r'
73 MERGE_RECORD_RESOLVED = b'r'
74 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
74 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
75 MERGE_RECORD_RESOLVED_PATH = b'pr'
75 MERGE_RECORD_RESOLVED_PATH = b'pr'
76 # represents that the file was automatically merged in favor
76 # represents that the file was automatically merged in favor
77 # of other version. This info is used on commit.
77 # of other version. This info is used on commit.
78 # This is now deprecated and commit related information is now
78 # This is now deprecated and commit related information is now
79 # stored in RECORD_FILE_VALUES
79 # stored in RECORD_FILE_VALUES
80 MERGE_RECORD_MERGED_OTHER = b'o'
80 MERGE_RECORD_MERGED_OTHER = b'o'
81
81
82 #####
82 #####
83 # top level record which stores other unknown records. Multiple of these can
83 # top level record which stores other unknown records. Multiple of these can
84 # exists
84 # exists
85 #####
85 #####
86 RECORD_OVERRIDE = b't'
86 RECORD_OVERRIDE = b't'
87
87
88 #####
88 #####
89 # legacy records which are no longer used but kept to prevent breaking BC
89 # legacy records which are no longer used but kept to prevent breaking BC
90 #####
90 #####
91 # This record was release in 5.4 and usage was removed in 5.5
91 # This record was release in 5.4 and usage was removed in 5.5
92 LEGACY_RECORD_RESOLVED_OTHER = b'R'
92 LEGACY_RECORD_RESOLVED_OTHER = b'R'
93 # This record was release in 3.7 and usage was removed in 5.6
93 # This record was release in 3.7 and usage was removed in 5.6
94 LEGACY_RECORD_DRIVER_RESOLVED = b'd'
94 LEGACY_RECORD_DRIVER_RESOLVED = b'd'
95 # This record was release in 3.7 and usage was removed in 5.6
95 # This record was release in 3.7 and usage was removed in 5.6
96 LEGACY_MERGE_DRIVER_STATE = b'm'
96 LEGACY_MERGE_DRIVER_STATE = b'm'
97 # This record was release in 3.7 and usage was removed in 5.6
97 # This record was release in 3.7 and usage was removed in 5.6
98 LEGACY_MERGE_DRIVER_MERGE = b'D'
98 LEGACY_MERGE_DRIVER_MERGE = b'D'
99
99
100
100
101 ACTION_FORGET = b'f'
101 ACTION_FORGET = b'f'
102 ACTION_REMOVE = b'r'
102 ACTION_REMOVE = b'r'
103 ACTION_ADD = b'a'
103 ACTION_ADD = b'a'
104 ACTION_GET = b'g'
104 ACTION_GET = b'g'
105 ACTION_PATH_CONFLICT = b'p'
105 ACTION_PATH_CONFLICT = b'p'
106 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
106 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
107 ACTION_ADD_MODIFIED = b'am'
107 ACTION_ADD_MODIFIED = b'am'
108 ACTION_CREATED = b'c'
108 ACTION_CREATED = b'c'
109 ACTION_DELETED_CHANGED = b'dc'
109 ACTION_DELETED_CHANGED = b'dc'
110 ACTION_CHANGED_DELETED = b'cd'
110 ACTION_CHANGED_DELETED = b'cd'
111 ACTION_MERGE = b'm'
111 ACTION_MERGE = b'm'
112 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
112 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
113 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
113 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
114 ACTION_KEEP = b'k'
114 ACTION_KEEP = b'k'
115 # the file was absent on local side before merge and we should
115 # the file was absent on local side before merge and we should
116 # keep it absent (absent means file not present, it can be a result
116 # keep it absent (absent means file not present, it can be a result
117 # of file deletion, rename etc.)
117 # of file deletion, rename etc.)
118 ACTION_KEEP_ABSENT = b'ka'
118 ACTION_KEEP_ABSENT = b'ka'
119 # the file is absent on the ancestor and remote side of the merge
119 # the file is absent on the ancestor and remote side of the merge
120 # hence this file is new and we should keep it
120 # hence this file is new and we should keep it
121 ACTION_KEEP_NEW = b'kn'
121 ACTION_KEEP_NEW = b'kn'
122 ACTION_EXEC = b'e'
122 ACTION_EXEC = b'e'
123 ACTION_CREATED_MERGE = b'cm'
123 ACTION_CREATED_MERGE = b'cm'
124
124
125 # actions which are no op
125 # actions which are no op
126 NO_OP_ACTIONS = (
126 NO_OP_ACTIONS = (
127 ACTION_KEEP,
127 ACTION_KEEP,
128 ACTION_KEEP_ABSENT,
128 ACTION_KEEP_ABSENT,
129 ACTION_KEEP_NEW,
129 ACTION_KEEP_NEW,
130 )
130 )
131
131
132
132
133 class _mergestate_base(object):
133 class _mergestate_base(object):
134 """track 3-way merge state of individual files
134 """track 3-way merge state of individual files
135
135
136 The merge state is stored on disk when needed. Two files are used: one with
136 The merge state is stored on disk when needed. Two files are used: one with
137 an old format (version 1), and one with a new format (version 2). Version 2
137 an old format (version 1), and one with a new format (version 2). Version 2
138 stores a superset of the data in version 1, including new kinds of records
138 stores a superset of the data in version 1, including new kinds of records
139 in the future. For more about the new format, see the documentation for
139 in the future. For more about the new format, see the documentation for
140 `_readrecordsv2`.
140 `_readrecordsv2`.
141
141
142 Each record can contain arbitrary content, and has an associated type. This
142 Each record can contain arbitrary content, and has an associated type. This
143 `type` should be a letter. If `type` is uppercase, the record is mandatory:
143 `type` should be a letter. If `type` is uppercase, the record is mandatory:
144 versions of Mercurial that don't support it should abort. If `type` is
144 versions of Mercurial that don't support it should abort. If `type` is
145 lowercase, the record can be safely ignored.
145 lowercase, the record can be safely ignored.
146
146
147 Currently known records:
147 Currently known records:
148
148
149 L: the node of the "local" part of the merge (hexified version)
149 L: the node of the "local" part of the merge (hexified version)
150 O: the node of the "other" part of the merge (hexified version)
150 O: the node of the "other" part of the merge (hexified version)
151 F: a file to be merged entry
151 F: a file to be merged entry
152 C: a change/delete or delete/change conflict
152 C: a change/delete or delete/change conflict
153 P: a path conflict (file vs directory)
153 P: a path conflict (file vs directory)
154 f: a (filename, dictionary) tuple of optional values for a given file
154 f: a (filename, dictionary) tuple of optional values for a given file
155 l: the labels for the parts of the merge.
155 l: the labels for the parts of the merge.
156
156
157 Merge record states (stored in self._state, indexed by filename):
157 Merge record states (stored in self._state, indexed by filename):
158 u: unresolved conflict
158 u: unresolved conflict
159 r: resolved conflict
159 r: resolved conflict
160 pu: unresolved path conflict (file conflicts with directory)
160 pu: unresolved path conflict (file conflicts with directory)
161 pr: resolved path conflict
161 pr: resolved path conflict
162 o: file was merged in favor of other parent of merge (DEPRECATED)
162 o: file was merged in favor of other parent of merge (DEPRECATED)
163
163
164 The resolve command transitions between 'u' and 'r' for conflicts and
164 The resolve command transitions between 'u' and 'r' for conflicts and
165 'pu' and 'pr' for path conflicts.
165 'pu' and 'pr' for path conflicts.
166 """
166 """
167
167
168 def __init__(self, repo):
168 def __init__(self, repo):
169 """Initialize the merge state.
169 """Initialize the merge state.
170
170
171 Do not use this directly! Instead call read() or clean()."""
171 Do not use this directly! Instead call read() or clean()."""
172 self._repo = repo
172 self._repo = repo
173 self._state = {}
173 self._state = {}
174 self._stateextras = collections.defaultdict(dict)
174 self._stateextras = collections.defaultdict(dict)
175 self._local = None
175 self._local = None
176 self._other = None
176 self._other = None
177 self._labels = None
177 self._labels = None
178 # contains a mapping of form:
178 # contains a mapping of form:
179 # {filename : (merge_return_value, action_to_be_performed}
179 # {filename : (merge_return_value, action_to_be_performed}
180 # these are results of re-running merge process
180 # these are results of re-running merge process
181 # this dict is used to perform actions on dirstate caused by re-running
181 # this dict is used to perform actions on dirstate caused by re-running
182 # the merge
182 # the merge
183 self._results = {}
183 self._results = {}
184 self._dirty = False
184 self._dirty = False
185
185
186 def reset(self):
186 def reset(self):
187 pass
187 pass
188
188
189 def start(self, node, other, labels=None):
189 def start(self, node, other, labels=None):
190 self._local = node
190 self._local = node
191 self._other = other
191 self._other = other
192 self._labels = labels
192 self._labels = labels
193
193
194 @util.propertycache
194 @util.propertycache
195 def local(self):
195 def local(self):
196 if self._local is None:
196 if self._local is None:
197 msg = b"local accessed but self._local isn't set"
197 msg = b"local accessed but self._local isn't set"
198 raise error.ProgrammingError(msg)
198 raise error.ProgrammingError(msg)
199 return self._local
199 return self._local
200
200
201 @util.propertycache
201 @util.propertycache
202 def localctx(self):
202 def localctx(self):
203 return self._repo[self.local]
203 return self._repo[self.local]
204
204
205 @util.propertycache
205 @util.propertycache
206 def other(self):
206 def other(self):
207 if self._other is None:
207 if self._other is None:
208 msg = b"other accessed but self._other isn't set"
208 msg = b"other accessed but self._other isn't set"
209 raise error.ProgrammingError(msg)
209 raise error.ProgrammingError(msg)
210 return self._other
210 return self._other
211
211
212 @util.propertycache
212 @util.propertycache
213 def otherctx(self):
213 def otherctx(self):
214 return self._repo[self.other]
214 return self._repo[self.other]
215
215
216 def active(self):
216 def active(self):
217 """Whether mergestate is active.
217 """Whether mergestate is active.
218
218
219 Returns True if there appears to be mergestate. This is a rough proxy
219 Returns True if there appears to be mergestate. This is a rough proxy
220 for "is a merge in progress."
220 for "is a merge in progress."
221 """
221 """
222 return bool(self._local) or bool(self._state)
222 return bool(self._local) or bool(self._state)
223
223
224 def commit(self):
224 def commit(self):
225 """Write current state on disk (if necessary)"""
225 """Write current state on disk (if necessary)"""
226
226
227 @staticmethod
227 @staticmethod
228 def getlocalkey(path):
228 def getlocalkey(path):
229 """hash the path of a local file context for storage in the .hg/merge
229 """hash the path of a local file context for storage in the .hg/merge
230 directory."""
230 directory."""
231
231
232 return hex(hashutil.sha1(path).digest())
232 return hex(hashutil.sha1(path).digest())
233
233
234 def _make_backup(self, fctx, localkey):
234 def _make_backup(self, fctx, localkey):
235 raise NotImplementedError()
235 raise NotImplementedError()
236
236
237 def _restore_backup(self, fctx, localkey, flags):
237 def _restore_backup(self, fctx, localkey, flags):
238 raise NotImplementedError()
238 raise NotImplementedError()
239
239
240 def add(self, fcl, fco, fca, fd):
240 def add(self, fcl, fco, fca, fd):
241 """add a new (potentially?) conflicting file the merge state
241 """add a new (potentially?) conflicting file the merge state
242 fcl: file context for local,
242 fcl: file context for local,
243 fco: file context for remote,
243 fco: file context for remote,
244 fca: file context for ancestors,
244 fca: file context for ancestors,
245 fd: file path of the resulting merge.
245 fd: file path of the resulting merge.
246
246
247 note: also write the local version to the `.hg/merge` directory.
247 note: also write the local version to the `.hg/merge` directory.
248 """
248 """
249 if fcl.isabsent():
249 if fcl.isabsent():
250 localkey = self._repo.nodeconstants.nullhex
250 localkey = self._repo.nodeconstants.nullhex
251 else:
251 else:
252 localkey = mergestate.getlocalkey(fcl.path())
252 localkey = mergestate.getlocalkey(fcl.path())
253 self._make_backup(fcl, localkey)
253 self._make_backup(fcl, localkey)
254 self._state[fd] = [
254 self._state[fd] = [
255 MERGE_RECORD_UNRESOLVED,
255 MERGE_RECORD_UNRESOLVED,
256 localkey,
256 localkey,
257 fcl.path(),
257 fcl.path(),
258 fca.path(),
258 fca.path(),
259 hex(fca.filenode()),
259 hex(fca.filenode()),
260 fco.path(),
260 fco.path(),
261 hex(fco.filenode()),
261 hex(fco.filenode()),
262 fcl.flags(),
262 fcl.flags(),
263 ]
263 ]
264 self._stateextras[fd][b'ancestorlinknode'] = hex(fca.node())
264 self._stateextras[fd][b'ancestorlinknode'] = hex(fca.node())
265 self._dirty = True
265 self._dirty = True
266
266
267 def addpathconflict(self, path, frename, forigin):
267 def addpathconflict(self, path, frename, forigin):
268 """add a new conflicting path to the merge state
268 """add a new conflicting path to the merge state
269 path: the path that conflicts
269 path: the path that conflicts
270 frename: the filename the conflicting file was renamed to
270 frename: the filename the conflicting file was renamed to
271 forigin: origin of the file ('l' or 'r' for local/remote)
271 forigin: origin of the file ('l' or 'r' for local/remote)
272 """
272 """
273 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
273 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
274 self._dirty = True
274 self._dirty = True
275
275
276 def addcommitinfo(self, path, data):
276 def addcommitinfo(self, path, data):
277 """stores information which is required at commit
277 """stores information which is required at commit
278 into _stateextras"""
278 into _stateextras"""
279 self._stateextras[path].update(data)
279 self._stateextras[path].update(data)
280 self._dirty = True
280 self._dirty = True
281
281
282 def __contains__(self, dfile):
282 def __contains__(self, dfile):
283 return dfile in self._state
283 return dfile in self._state
284
284
285 def __getitem__(self, dfile):
285 def __getitem__(self, dfile):
286 return self._state[dfile][0]
286 return self._state[dfile][0]
287
287
288 def __iter__(self):
288 def __iter__(self):
289 return iter(sorted(self._state))
289 return iter(sorted(self._state))
290
290
291 def files(self):
291 def files(self):
292 return self._state.keys()
292 return self._state.keys()
293
293
294 def mark(self, dfile, state):
294 def mark(self, dfile, state):
295 self._state[dfile][0] = state
295 self._state[dfile][0] = state
296 self._dirty = True
296 self._dirty = True
297
297
298 def unresolved(self):
298 def unresolved(self):
299 """Obtain the paths of unresolved files."""
299 """Obtain the paths of unresolved files."""
300
300
301 for f, entry in pycompat.iteritems(self._state):
301 for f, entry in pycompat.iteritems(self._state):
302 if entry[0] in (
302 if entry[0] in (
303 MERGE_RECORD_UNRESOLVED,
303 MERGE_RECORD_UNRESOLVED,
304 MERGE_RECORD_UNRESOLVED_PATH,
304 MERGE_RECORD_UNRESOLVED_PATH,
305 ):
305 ):
306 yield f
306 yield f
307
307
308 def allextras(self):
308 def allextras(self):
309 """return all extras information stored with the mergestate"""
309 """return all extras information stored with the mergestate"""
310 return self._stateextras
310 return self._stateextras
311
311
312 def extras(self, filename):
312 def extras(self, filename):
313 """return extras stored with the mergestate for the given filename"""
313 """return extras stored with the mergestate for the given filename"""
314 return self._stateextras[filename]
314 return self._stateextras[filename]
315
315
316 def _resolve(self, preresolve, dfile, wctx):
316 def _resolve(self, preresolve, dfile, wctx):
317 """rerun merge process for file path `dfile`.
317 """rerun merge process for file path `dfile`.
318 Returns whether the merge was completed and the return value of merge
318 Returns whether the merge was completed and the return value of merge
319 obtained from filemerge._filemerge().
319 obtained from filemerge._filemerge().
320 """
320 """
321 if self[dfile] in (
321 if self[dfile] in (
322 MERGE_RECORD_RESOLVED,
322 MERGE_RECORD_RESOLVED,
323 LEGACY_RECORD_DRIVER_RESOLVED,
323 LEGACY_RECORD_DRIVER_RESOLVED,
324 ):
324 ):
325 return True, 0
325 return True, 0
326 stateentry = self._state[dfile]
326 stateentry = self._state[dfile]
327 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
327 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
328 octx = self._repo[self._other]
328 octx = self._repo[self._other]
329 extras = self.extras(dfile)
329 extras = self.extras(dfile)
330 anccommitnode = extras.get(b'ancestorlinknode')
330 anccommitnode = extras.get(b'ancestorlinknode')
331 if anccommitnode:
331 if anccommitnode:
332 actx = self._repo[anccommitnode]
332 actx = self._repo[anccommitnode]
333 else:
333 else:
334 actx = None
334 actx = None
335 fcd = _filectxorabsent(localkey, wctx, dfile)
335 fcd = _filectxorabsent(localkey, wctx, dfile)
336 fco = _filectxorabsent(onode, octx, ofile)
336 fco = _filectxorabsent(onode, octx, ofile)
337 # TODO: move this to filectxorabsent
337 # TODO: move this to filectxorabsent
338 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
338 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
339 # "premerge" x flags
339 # "premerge" x flags
340 flo = fco.flags()
340 flo = fco.flags()
341 fla = fca.flags()
341 fla = fca.flags()
342 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
342 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
343 if fca.rev() == nullrev and flags != flo:
343 if fca.rev() == nullrev and flags != flo:
344 if preresolve:
344 if preresolve:
345 self._repo.ui.warn(
345 self._repo.ui.warn(
346 _(
346 _(
347 b'warning: cannot merge flags for %s '
347 b'warning: cannot merge flags for %s '
348 b'without common ancestor - keeping local flags\n'
348 b'without common ancestor - keeping local flags\n'
349 )
349 )
350 % afile
350 % afile
351 )
351 )
352 elif flags == fla:
352 elif flags == fla:
353 flags = flo
353 flags = flo
354 if preresolve:
354 if preresolve:
355 # restore local
355 # restore local
356 if localkey != self._repo.nodeconstants.nullhex:
356 if localkey != self._repo.nodeconstants.nullhex:
357 self._restore_backup(wctx[dfile], localkey, flags)
357 self._restore_backup(wctx[dfile], localkey, flags)
358 else:
358 else:
359 wctx[dfile].remove(ignoremissing=True)
359 wctx[dfile].remove(ignoremissing=True)
360 complete, merge_ret, deleted = filemerge.premerge(
360 complete, merge_ret, deleted = filemerge.premerge(
361 self._repo,
361 self._repo,
362 wctx,
362 wctx,
363 self._local,
363 self._local,
364 lfile,
364 lfile,
365 fcd,
365 fcd,
366 fco,
366 fco,
367 fca,
367 fca,
368 labels=self._labels,
368 labels=self._labels,
369 )
369 )
370 else:
370 else:
371 complete, merge_ret, deleted = filemerge.filemerge(
371 complete, merge_ret, deleted = filemerge.filemerge(
372 self._repo,
372 self._repo,
373 wctx,
373 wctx,
374 self._local,
374 self._local,
375 lfile,
375 lfile,
376 fcd,
376 fcd,
377 fco,
377 fco,
378 fca,
378 fca,
379 labels=self._labels,
379 labels=self._labels,
380 )
380 )
381 if merge_ret is None:
381 if merge_ret is None:
382 # If return value of merge is None, then there are no real conflict
382 # If return value of merge is None, then there are no real conflict
383 del self._state[dfile]
383 del self._state[dfile]
384 self._dirty = True
384 self._dirty = True
385 elif not merge_ret:
385 elif not merge_ret:
386 self.mark(dfile, MERGE_RECORD_RESOLVED)
386 self.mark(dfile, MERGE_RECORD_RESOLVED)
387
387
388 if complete:
388 if complete:
389 action = None
389 action = None
390 if deleted:
390 if deleted:
391 if fcd.isabsent():
391 if fcd.isabsent():
392 # dc: local picked. Need to drop if present, which may
392 # dc: local picked. Need to drop if present, which may
393 # happen on re-resolves.
393 # happen on re-resolves.
394 action = ACTION_FORGET
394 action = ACTION_FORGET
395 else:
395 else:
396 # cd: remote picked (or otherwise deleted)
396 # cd: remote picked (or otherwise deleted)
397 action = ACTION_REMOVE
397 action = ACTION_REMOVE
398 else:
398 else:
399 if fcd.isabsent(): # dc: remote picked
399 if fcd.isabsent(): # dc: remote picked
400 action = ACTION_GET
400 action = ACTION_GET
401 elif fco.isabsent(): # cd: local picked
401 elif fco.isabsent(): # cd: local picked
402 if dfile in self.localctx:
402 if dfile in self.localctx:
403 action = ACTION_ADD_MODIFIED
403 action = ACTION_ADD_MODIFIED
404 else:
404 else:
405 action = ACTION_ADD
405 action = ACTION_ADD
406 # else: regular merges (no action necessary)
406 # else: regular merges (no action necessary)
407 self._results[dfile] = merge_ret, action
407 self._results[dfile] = merge_ret, action
408
408
409 return complete, merge_ret
409 return complete, merge_ret
410
410
411 def preresolve(self, dfile, wctx):
412 """run premerge process for dfile
413
414 Returns whether the merge is complete, and the exit code."""
415 return self._resolve(True, dfile, wctx)
416
417 def resolve(self, dfile, wctx):
411 def resolve(self, dfile, wctx):
418 """run merge process (assuming premerge was run) for dfile
412 """run merge process for dfile
419
413
420 Returns the exit code of the merge."""
414 Returns the exit code of the merge."""
421 return self._resolve(False, dfile, wctx)[1]
415 complete, r = self._resolve(True, dfile, wctx)
416 if not complete:
417 r = self._resolve(False, dfile, wctx)[1]
418 return r
422
419
423 def counts(self):
420 def counts(self):
424 """return counts for updated, merged and removed files in this
421 """return counts for updated, merged and removed files in this
425 session"""
422 session"""
426 updated, merged, removed = 0, 0, 0
423 updated, merged, removed = 0, 0, 0
427 for r, action in pycompat.itervalues(self._results):
424 for r, action in pycompat.itervalues(self._results):
428 if r is None:
425 if r is None:
429 updated += 1
426 updated += 1
430 elif r == 0:
427 elif r == 0:
431 if action == ACTION_REMOVE:
428 if action == ACTION_REMOVE:
432 removed += 1
429 removed += 1
433 else:
430 else:
434 merged += 1
431 merged += 1
435 return updated, merged, removed
432 return updated, merged, removed
436
433
437 def unresolvedcount(self):
434 def unresolvedcount(self):
438 """get unresolved count for this merge (persistent)"""
435 """get unresolved count for this merge (persistent)"""
439 return len(list(self.unresolved()))
436 return len(list(self.unresolved()))
440
437
441 def actions(self):
438 def actions(self):
442 """return lists of actions to perform on the dirstate"""
439 """return lists of actions to perform on the dirstate"""
443 actions = {
440 actions = {
444 ACTION_REMOVE: [],
441 ACTION_REMOVE: [],
445 ACTION_FORGET: [],
442 ACTION_FORGET: [],
446 ACTION_ADD: [],
443 ACTION_ADD: [],
447 ACTION_ADD_MODIFIED: [],
444 ACTION_ADD_MODIFIED: [],
448 ACTION_GET: [],
445 ACTION_GET: [],
449 }
446 }
450 for f, (r, action) in pycompat.iteritems(self._results):
447 for f, (r, action) in pycompat.iteritems(self._results):
451 if action is not None:
448 if action is not None:
452 actions[action].append((f, None, b"merge result"))
449 actions[action].append((f, None, b"merge result"))
453 return actions
450 return actions
454
451
455
452
456 class mergestate(_mergestate_base):
453 class mergestate(_mergestate_base):
457
454
458 statepathv1 = b'merge/state'
455 statepathv1 = b'merge/state'
459 statepathv2 = b'merge/state2'
456 statepathv2 = b'merge/state2'
460
457
461 @staticmethod
458 @staticmethod
462 def clean(repo):
459 def clean(repo):
463 """Initialize a brand new merge state, removing any existing state on
460 """Initialize a brand new merge state, removing any existing state on
464 disk."""
461 disk."""
465 ms = mergestate(repo)
462 ms = mergestate(repo)
466 ms.reset()
463 ms.reset()
467 return ms
464 return ms
468
465
469 @staticmethod
466 @staticmethod
470 def read(repo):
467 def read(repo):
471 """Initialize the merge state, reading it from disk."""
468 """Initialize the merge state, reading it from disk."""
472 ms = mergestate(repo)
469 ms = mergestate(repo)
473 ms._read()
470 ms._read()
474 return ms
471 return ms
475
472
476 def _read(self):
473 def _read(self):
477 """Analyse each record content to restore a serialized state from disk
474 """Analyse each record content to restore a serialized state from disk
478
475
479 This function process "record" entry produced by the de-serialization
476 This function process "record" entry produced by the de-serialization
480 of on disk file.
477 of on disk file.
481 """
478 """
482 unsupported = set()
479 unsupported = set()
483 records = self._readrecords()
480 records = self._readrecords()
484 for rtype, record in records:
481 for rtype, record in records:
485 if rtype == RECORD_LOCAL:
482 if rtype == RECORD_LOCAL:
486 self._local = bin(record)
483 self._local = bin(record)
487 elif rtype == RECORD_OTHER:
484 elif rtype == RECORD_OTHER:
488 self._other = bin(record)
485 self._other = bin(record)
489 elif rtype == LEGACY_MERGE_DRIVER_STATE:
486 elif rtype == LEGACY_MERGE_DRIVER_STATE:
490 pass
487 pass
491 elif rtype in (
488 elif rtype in (
492 RECORD_MERGED,
489 RECORD_MERGED,
493 RECORD_CHANGEDELETE_CONFLICT,
490 RECORD_CHANGEDELETE_CONFLICT,
494 RECORD_PATH_CONFLICT,
491 RECORD_PATH_CONFLICT,
495 LEGACY_MERGE_DRIVER_MERGE,
492 LEGACY_MERGE_DRIVER_MERGE,
496 LEGACY_RECORD_RESOLVED_OTHER,
493 LEGACY_RECORD_RESOLVED_OTHER,
497 ):
494 ):
498 bits = record.split(b'\0')
495 bits = record.split(b'\0')
499 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
496 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
500 # and we now store related information in _stateextras, so
497 # and we now store related information in _stateextras, so
501 # lets write to _stateextras directly
498 # lets write to _stateextras directly
502 if bits[1] == MERGE_RECORD_MERGED_OTHER:
499 if bits[1] == MERGE_RECORD_MERGED_OTHER:
503 self._stateextras[bits[0]][b'filenode-source'] = b'other'
500 self._stateextras[bits[0]][b'filenode-source'] = b'other'
504 else:
501 else:
505 self._state[bits[0]] = bits[1:]
502 self._state[bits[0]] = bits[1:]
506 elif rtype == RECORD_FILE_VALUES:
503 elif rtype == RECORD_FILE_VALUES:
507 filename, rawextras = record.split(b'\0', 1)
504 filename, rawextras = record.split(b'\0', 1)
508 extraparts = rawextras.split(b'\0')
505 extraparts = rawextras.split(b'\0')
509 extras = {}
506 extras = {}
510 i = 0
507 i = 0
511 while i < len(extraparts):
508 while i < len(extraparts):
512 extras[extraparts[i]] = extraparts[i + 1]
509 extras[extraparts[i]] = extraparts[i + 1]
513 i += 2
510 i += 2
514
511
515 self._stateextras[filename] = extras
512 self._stateextras[filename] = extras
516 elif rtype == RECORD_LABELS:
513 elif rtype == RECORD_LABELS:
517 labels = record.split(b'\0', 2)
514 labels = record.split(b'\0', 2)
518 self._labels = [l for l in labels if len(l) > 0]
515 self._labels = [l for l in labels if len(l) > 0]
519 elif not rtype.islower():
516 elif not rtype.islower():
520 unsupported.add(rtype)
517 unsupported.add(rtype)
521
518
522 if unsupported:
519 if unsupported:
523 raise error.UnsupportedMergeRecords(unsupported)
520 raise error.UnsupportedMergeRecords(unsupported)
524
521
525 def _readrecords(self):
522 def _readrecords(self):
526 """Read merge state from disk and return a list of record (TYPE, data)
523 """Read merge state from disk and return a list of record (TYPE, data)
527
524
528 We read data from both v1 and v2 files and decide which one to use.
525 We read data from both v1 and v2 files and decide which one to use.
529
526
530 V1 has been used by version prior to 2.9.1 and contains less data than
527 V1 has been used by version prior to 2.9.1 and contains less data than
531 v2. We read both versions and check if no data in v2 contradicts
528 v2. We read both versions and check if no data in v2 contradicts
532 v1. If there is not contradiction we can safely assume that both v1
529 v1. If there is not contradiction we can safely assume that both v1
533 and v2 were written at the same time and use the extract data in v2. If
530 and v2 were written at the same time and use the extract data in v2. If
534 there is contradiction we ignore v2 content as we assume an old version
531 there is contradiction we ignore v2 content as we assume an old version
535 of Mercurial has overwritten the mergestate file and left an old v2
532 of Mercurial has overwritten the mergestate file and left an old v2
536 file around.
533 file around.
537
534
538 returns list of record [(TYPE, data), ...]"""
535 returns list of record [(TYPE, data), ...]"""
539 v1records = self._readrecordsv1()
536 v1records = self._readrecordsv1()
540 v2records = self._readrecordsv2()
537 v2records = self._readrecordsv2()
541 if self._v1v2match(v1records, v2records):
538 if self._v1v2match(v1records, v2records):
542 return v2records
539 return v2records
543 else:
540 else:
544 # v1 file is newer than v2 file, use it
541 # v1 file is newer than v2 file, use it
545 # we have to infer the "other" changeset of the merge
542 # we have to infer the "other" changeset of the merge
546 # we cannot do better than that with v1 of the format
543 # we cannot do better than that with v1 of the format
547 mctx = self._repo[None].parents()[-1]
544 mctx = self._repo[None].parents()[-1]
548 v1records.append((RECORD_OTHER, mctx.hex()))
545 v1records.append((RECORD_OTHER, mctx.hex()))
549 # add place holder "other" file node information
546 # add place holder "other" file node information
550 # nobody is using it yet so we do no need to fetch the data
547 # nobody is using it yet so we do no need to fetch the data
551 # if mctx was wrong `mctx[bits[-2]]` may fails.
548 # if mctx was wrong `mctx[bits[-2]]` may fails.
552 for idx, r in enumerate(v1records):
549 for idx, r in enumerate(v1records):
553 if r[0] == RECORD_MERGED:
550 if r[0] == RECORD_MERGED:
554 bits = r[1].split(b'\0')
551 bits = r[1].split(b'\0')
555 bits.insert(-2, b'')
552 bits.insert(-2, b'')
556 v1records[idx] = (r[0], b'\0'.join(bits))
553 v1records[idx] = (r[0], b'\0'.join(bits))
557 return v1records
554 return v1records
558
555
559 def _v1v2match(self, v1records, v2records):
556 def _v1v2match(self, v1records, v2records):
560 oldv2 = set() # old format version of v2 record
557 oldv2 = set() # old format version of v2 record
561 for rec in v2records:
558 for rec in v2records:
562 if rec[0] == RECORD_LOCAL:
559 if rec[0] == RECORD_LOCAL:
563 oldv2.add(rec)
560 oldv2.add(rec)
564 elif rec[0] == RECORD_MERGED:
561 elif rec[0] == RECORD_MERGED:
565 # drop the onode data (not contained in v1)
562 # drop the onode data (not contained in v1)
566 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
563 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
567 for rec in v1records:
564 for rec in v1records:
568 if rec not in oldv2:
565 if rec not in oldv2:
569 return False
566 return False
570 else:
567 else:
571 return True
568 return True
572
569
573 def _readrecordsv1(self):
570 def _readrecordsv1(self):
574 """read on disk merge state for version 1 file
571 """read on disk merge state for version 1 file
575
572
576 returns list of record [(TYPE, data), ...]
573 returns list of record [(TYPE, data), ...]
577
574
578 Note: the "F" data from this file are one entry short
575 Note: the "F" data from this file are one entry short
579 (no "other file node" entry)
576 (no "other file node" entry)
580 """
577 """
581 records = []
578 records = []
582 try:
579 try:
583 f = self._repo.vfs(self.statepathv1)
580 f = self._repo.vfs(self.statepathv1)
584 for i, l in enumerate(f):
581 for i, l in enumerate(f):
585 if i == 0:
582 if i == 0:
586 records.append((RECORD_LOCAL, l[:-1]))
583 records.append((RECORD_LOCAL, l[:-1]))
587 else:
584 else:
588 records.append((RECORD_MERGED, l[:-1]))
585 records.append((RECORD_MERGED, l[:-1]))
589 f.close()
586 f.close()
590 except IOError as err:
587 except IOError as err:
591 if err.errno != errno.ENOENT:
588 if err.errno != errno.ENOENT:
592 raise
589 raise
593 return records
590 return records
594
591
595 def _readrecordsv2(self):
592 def _readrecordsv2(self):
596 """read on disk merge state for version 2 file
593 """read on disk merge state for version 2 file
597
594
598 This format is a list of arbitrary records of the form:
595 This format is a list of arbitrary records of the form:
599
596
600 [type][length][content]
597 [type][length][content]
601
598
602 `type` is a single character, `length` is a 4 byte integer, and
599 `type` is a single character, `length` is a 4 byte integer, and
603 `content` is an arbitrary byte sequence of length `length`.
600 `content` is an arbitrary byte sequence of length `length`.
604
601
605 Mercurial versions prior to 3.7 have a bug where if there are
602 Mercurial versions prior to 3.7 have a bug where if there are
606 unsupported mandatory merge records, attempting to clear out the merge
603 unsupported mandatory merge records, attempting to clear out the merge
607 state with hg update --clean or similar aborts. The 't' record type
604 state with hg update --clean or similar aborts. The 't' record type
608 works around that by writing out what those versions treat as an
605 works around that by writing out what those versions treat as an
609 advisory record, but later versions interpret as special: the first
606 advisory record, but later versions interpret as special: the first
610 character is the 'real' record type and everything onwards is the data.
607 character is the 'real' record type and everything onwards is the data.
611
608
612 Returns list of records [(TYPE, data), ...]."""
609 Returns list of records [(TYPE, data), ...]."""
613 records = []
610 records = []
614 try:
611 try:
615 f = self._repo.vfs(self.statepathv2)
612 f = self._repo.vfs(self.statepathv2)
616 data = f.read()
613 data = f.read()
617 off = 0
614 off = 0
618 end = len(data)
615 end = len(data)
619 while off < end:
616 while off < end:
620 rtype = data[off : off + 1]
617 rtype = data[off : off + 1]
621 off += 1
618 off += 1
622 length = _unpack(b'>I', data[off : (off + 4)])[0]
619 length = _unpack(b'>I', data[off : (off + 4)])[0]
623 off += 4
620 off += 4
624 record = data[off : (off + length)]
621 record = data[off : (off + length)]
625 off += length
622 off += length
626 if rtype == RECORD_OVERRIDE:
623 if rtype == RECORD_OVERRIDE:
627 rtype, record = record[0:1], record[1:]
624 rtype, record = record[0:1], record[1:]
628 records.append((rtype, record))
625 records.append((rtype, record))
629 f.close()
626 f.close()
630 except IOError as err:
627 except IOError as err:
631 if err.errno != errno.ENOENT:
628 if err.errno != errno.ENOENT:
632 raise
629 raise
633 return records
630 return records
634
631
635 def commit(self):
632 def commit(self):
636 if self._dirty:
633 if self._dirty:
637 records = self._makerecords()
634 records = self._makerecords()
638 self._writerecords(records)
635 self._writerecords(records)
639 self._dirty = False
636 self._dirty = False
640
637
641 def _makerecords(self):
638 def _makerecords(self):
642 records = []
639 records = []
643 records.append((RECORD_LOCAL, hex(self._local)))
640 records.append((RECORD_LOCAL, hex(self._local)))
644 records.append((RECORD_OTHER, hex(self._other)))
641 records.append((RECORD_OTHER, hex(self._other)))
645 # Write out state items. In all cases, the value of the state map entry
642 # Write out state items. In all cases, the value of the state map entry
646 # is written as the contents of the record. The record type depends on
643 # is written as the contents of the record. The record type depends on
647 # the type of state that is stored, and capital-letter records are used
644 # the type of state that is stored, and capital-letter records are used
648 # to prevent older versions of Mercurial that do not support the feature
645 # to prevent older versions of Mercurial that do not support the feature
649 # from loading them.
646 # from loading them.
650 for filename, v in pycompat.iteritems(self._state):
647 for filename, v in pycompat.iteritems(self._state):
651 if v[0] in (
648 if v[0] in (
652 MERGE_RECORD_UNRESOLVED_PATH,
649 MERGE_RECORD_UNRESOLVED_PATH,
653 MERGE_RECORD_RESOLVED_PATH,
650 MERGE_RECORD_RESOLVED_PATH,
654 ):
651 ):
655 # Path conflicts. These are stored in 'P' records. The current
652 # Path conflicts. These are stored in 'P' records. The current
656 # resolution state ('pu' or 'pr') is stored within the record.
653 # resolution state ('pu' or 'pr') is stored within the record.
657 records.append(
654 records.append(
658 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
655 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
659 )
656 )
660 elif (
657 elif (
661 v[1] == self._repo.nodeconstants.nullhex
658 v[1] == self._repo.nodeconstants.nullhex
662 or v[6] == self._repo.nodeconstants.nullhex
659 or v[6] == self._repo.nodeconstants.nullhex
663 ):
660 ):
664 # Change/Delete or Delete/Change conflicts. These are stored in
661 # Change/Delete or Delete/Change conflicts. These are stored in
665 # 'C' records. v[1] is the local file, and is nullhex when the
662 # 'C' records. v[1] is the local file, and is nullhex when the
666 # file is deleted locally ('dc'). v[6] is the remote file, and
663 # file is deleted locally ('dc'). v[6] is the remote file, and
667 # is nullhex when the file is deleted remotely ('cd').
664 # is nullhex when the file is deleted remotely ('cd').
668 records.append(
665 records.append(
669 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
666 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
670 )
667 )
671 else:
668 else:
672 # Normal files. These are stored in 'F' records.
669 # Normal files. These are stored in 'F' records.
673 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
670 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
674 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
671 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
675 rawextras = b'\0'.join(
672 rawextras = b'\0'.join(
676 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
673 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
677 )
674 )
678 records.append(
675 records.append(
679 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
676 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
680 )
677 )
681 if self._labels is not None:
678 if self._labels is not None:
682 labels = b'\0'.join(self._labels)
679 labels = b'\0'.join(self._labels)
683 records.append((RECORD_LABELS, labels))
680 records.append((RECORD_LABELS, labels))
684 return records
681 return records
685
682
686 def _writerecords(self, records):
683 def _writerecords(self, records):
687 """Write current state on disk (both v1 and v2)"""
684 """Write current state on disk (both v1 and v2)"""
688 self._writerecordsv1(records)
685 self._writerecordsv1(records)
689 self._writerecordsv2(records)
686 self._writerecordsv2(records)
690
687
691 def _writerecordsv1(self, records):
688 def _writerecordsv1(self, records):
692 """Write current state on disk in a version 1 file"""
689 """Write current state on disk in a version 1 file"""
693 f = self._repo.vfs(self.statepathv1, b'wb')
690 f = self._repo.vfs(self.statepathv1, b'wb')
694 irecords = iter(records)
691 irecords = iter(records)
695 lrecords = next(irecords)
692 lrecords = next(irecords)
696 assert lrecords[0] == RECORD_LOCAL
693 assert lrecords[0] == RECORD_LOCAL
697 f.write(hex(self._local) + b'\n')
694 f.write(hex(self._local) + b'\n')
698 for rtype, data in irecords:
695 for rtype, data in irecords:
699 if rtype == RECORD_MERGED:
696 if rtype == RECORD_MERGED:
700 f.write(b'%s\n' % _droponode(data))
697 f.write(b'%s\n' % _droponode(data))
701 f.close()
698 f.close()
702
699
703 def _writerecordsv2(self, records):
700 def _writerecordsv2(self, records):
704 """Write current state on disk in a version 2 file
701 """Write current state on disk in a version 2 file
705
702
706 See the docstring for _readrecordsv2 for why we use 't'."""
703 See the docstring for _readrecordsv2 for why we use 't'."""
707 # these are the records that all version 2 clients can read
704 # these are the records that all version 2 clients can read
708 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
705 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
709 f = self._repo.vfs(self.statepathv2, b'wb')
706 f = self._repo.vfs(self.statepathv2, b'wb')
710 for key, data in records:
707 for key, data in records:
711 assert len(key) == 1
708 assert len(key) == 1
712 if key not in allowlist:
709 if key not in allowlist:
713 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
710 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
714 format = b'>sI%is' % len(data)
711 format = b'>sI%is' % len(data)
715 f.write(_pack(format, key, len(data), data))
712 f.write(_pack(format, key, len(data), data))
716 f.close()
713 f.close()
717
714
718 def _make_backup(self, fctx, localkey):
715 def _make_backup(self, fctx, localkey):
719 self._repo.vfs.write(b'merge/' + localkey, fctx.data())
716 self._repo.vfs.write(b'merge/' + localkey, fctx.data())
720
717
721 def _restore_backup(self, fctx, localkey, flags):
718 def _restore_backup(self, fctx, localkey, flags):
722 with self._repo.vfs(b'merge/' + localkey) as f:
719 with self._repo.vfs(b'merge/' + localkey) as f:
723 fctx.write(f.read(), flags)
720 fctx.write(f.read(), flags)
724
721
725 def reset(self):
722 def reset(self):
726 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
723 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
727
724
728
725
729 class memmergestate(_mergestate_base):
726 class memmergestate(_mergestate_base):
730 def __init__(self, repo):
727 def __init__(self, repo):
731 super(memmergestate, self).__init__(repo)
728 super(memmergestate, self).__init__(repo)
732 self._backups = {}
729 self._backups = {}
733
730
734 def _make_backup(self, fctx, localkey):
731 def _make_backup(self, fctx, localkey):
735 self._backups[localkey] = fctx.data()
732 self._backups[localkey] = fctx.data()
736
733
737 def _restore_backup(self, fctx, localkey, flags):
734 def _restore_backup(self, fctx, localkey, flags):
738 fctx.write(self._backups[localkey], flags)
735 fctx.write(self._backups[localkey], flags)
739
736
740
737
741 def recordupdates(repo, actions, branchmerge, getfiledata):
738 def recordupdates(repo, actions, branchmerge, getfiledata):
742 """record merge actions to the dirstate"""
739 """record merge actions to the dirstate"""
743 # remove (must come first)
740 # remove (must come first)
744 for f, args, msg in actions.get(ACTION_REMOVE, []):
741 for f, args, msg in actions.get(ACTION_REMOVE, []):
745 if branchmerge:
742 if branchmerge:
746 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=False)
743 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=False)
747 else:
744 else:
748 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
745 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
749
746
750 # forget (must come first)
747 # forget (must come first)
751 for f, args, msg in actions.get(ACTION_FORGET, []):
748 for f, args, msg in actions.get(ACTION_FORGET, []):
752 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
749 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
753
750
754 # resolve path conflicts
751 # resolve path conflicts
755 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
752 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
756 (f0, origf0) = args
753 (f0, origf0) = args
757 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
754 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
758 repo.dirstate.copy(origf0, f)
755 repo.dirstate.copy(origf0, f)
759 if f0 == origf0:
756 if f0 == origf0:
760 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
757 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
761 else:
758 else:
762 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
759 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
763
760
764 # re-add
761 # re-add
765 for f, args, msg in actions.get(ACTION_ADD, []):
762 for f, args, msg in actions.get(ACTION_ADD, []):
766 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
763 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
767
764
768 # re-add/mark as modified
765 # re-add/mark as modified
769 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
766 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
770 if branchmerge:
767 if branchmerge:
771 repo.dirstate.update_file(
768 repo.dirstate.update_file(
772 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
769 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
773 )
770 )
774 else:
771 else:
775 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
772 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
776
773
777 # exec change
774 # exec change
778 for f, args, msg in actions.get(ACTION_EXEC, []):
775 for f, args, msg in actions.get(ACTION_EXEC, []):
779 repo.dirstate.update_file(
776 repo.dirstate.update_file(
780 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
777 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
781 )
778 )
782
779
783 # keep
780 # keep
784 for f, args, msg in actions.get(ACTION_KEEP, []):
781 for f, args, msg in actions.get(ACTION_KEEP, []):
785 pass
782 pass
786
783
787 # keep deleted
784 # keep deleted
788 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
785 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
789 pass
786 pass
790
787
791 # keep new
788 # keep new
792 for f, args, msg in actions.get(ACTION_KEEP_NEW, []):
789 for f, args, msg in actions.get(ACTION_KEEP_NEW, []):
793 pass
790 pass
794
791
795 # get
792 # get
796 for f, args, msg in actions.get(ACTION_GET, []):
793 for f, args, msg in actions.get(ACTION_GET, []):
797 if branchmerge:
794 if branchmerge:
798 # tracked in p1 can be True also but update_file should not care
795 # tracked in p1 can be True also but update_file should not care
799 old_entry = repo.dirstate.get_entry(f)
796 old_entry = repo.dirstate.get_entry(f)
800 p1_tracked = old_entry.any_tracked and not old_entry.added
797 p1_tracked = old_entry.any_tracked and not old_entry.added
801 repo.dirstate.update_file(
798 repo.dirstate.update_file(
802 f,
799 f,
803 p1_tracked=p1_tracked,
800 p1_tracked=p1_tracked,
804 wc_tracked=True,
801 wc_tracked=True,
805 p2_info=True,
802 p2_info=True,
806 )
803 )
807 else:
804 else:
808 parentfiledata = getfiledata[f] if getfiledata else None
805 parentfiledata = getfiledata[f] if getfiledata else None
809 repo.dirstate.update_file(
806 repo.dirstate.update_file(
810 f,
807 f,
811 p1_tracked=True,
808 p1_tracked=True,
812 wc_tracked=True,
809 wc_tracked=True,
813 parentfiledata=parentfiledata,
810 parentfiledata=parentfiledata,
814 )
811 )
815
812
816 # merge
813 # merge
817 for f, args, msg in actions.get(ACTION_MERGE, []):
814 for f, args, msg in actions.get(ACTION_MERGE, []):
818 f1, f2, fa, move, anc = args
815 f1, f2, fa, move, anc = args
819 if branchmerge:
816 if branchmerge:
820 # We've done a branch merge, mark this file as merged
817 # We've done a branch merge, mark this file as merged
821 # so that we properly record the merger later
818 # so that we properly record the merger later
822 p1_tracked = f1 == f
819 p1_tracked = f1 == f
823 repo.dirstate.update_file(
820 repo.dirstate.update_file(
824 f,
821 f,
825 p1_tracked=p1_tracked,
822 p1_tracked=p1_tracked,
826 wc_tracked=True,
823 wc_tracked=True,
827 p2_info=True,
824 p2_info=True,
828 )
825 )
829 if f1 != f2: # copy/rename
826 if f1 != f2: # copy/rename
830 if move:
827 if move:
831 repo.dirstate.update_file(
828 repo.dirstate.update_file(
832 f1, p1_tracked=True, wc_tracked=False
829 f1, p1_tracked=True, wc_tracked=False
833 )
830 )
834 if f1 != f:
831 if f1 != f:
835 repo.dirstate.copy(f1, f)
832 repo.dirstate.copy(f1, f)
836 else:
833 else:
837 repo.dirstate.copy(f2, f)
834 repo.dirstate.copy(f2, f)
838 else:
835 else:
839 # We've update-merged a locally modified file, so
836 # We've update-merged a locally modified file, so
840 # we set the dirstate to emulate a normal checkout
837 # we set the dirstate to emulate a normal checkout
841 # of that file some time in the past. Thus our
838 # of that file some time in the past. Thus our
842 # merge will appear as a normal local file
839 # merge will appear as a normal local file
843 # modification.
840 # modification.
844 if f2 == f: # file not locally copied/moved
841 if f2 == f: # file not locally copied/moved
845 repo.dirstate.update_file(
842 repo.dirstate.update_file(
846 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
843 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
847 )
844 )
848 if move:
845 if move:
849 repo.dirstate.update_file(
846 repo.dirstate.update_file(
850 f1, p1_tracked=False, wc_tracked=False
847 f1, p1_tracked=False, wc_tracked=False
851 )
848 )
852
849
853 # directory rename, move local
850 # directory rename, move local
854 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
851 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
855 f0, flag = args
852 f0, flag = args
856 if branchmerge:
853 if branchmerge:
857 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
854 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
858 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
855 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
859 repo.dirstate.copy(f0, f)
856 repo.dirstate.copy(f0, f)
860 else:
857 else:
861 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=True)
858 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=True)
862 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
859 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
863
860
864 # directory rename, get
861 # directory rename, get
865 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
862 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
866 f0, flag = args
863 f0, flag = args
867 if branchmerge:
864 if branchmerge:
868 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
865 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
869 repo.dirstate.copy(f0, f)
866 repo.dirstate.copy(f0, f)
870 else:
867 else:
871 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=True)
868 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=True)
@@ -1,255 +1,255 b''
1 Test for the full copytracing algorithm
1 Test for the full copytracing algorithm
2 =======================================
2 =======================================
3
3
4
4
5 Initial Setup
5 Initial Setup
6 =============
6 =============
7
7
8 use git diff to see rename
8 use git diff to see rename
9
9
10 $ cat << EOF >> $HGRCPATH
10 $ cat << EOF >> $HGRCPATH
11 > [diff]
11 > [diff]
12 > git=yes
12 > git=yes
13 > EOF
13 > EOF
14
14
15 Setup an history where one side copy and rename a file (and update it) while the other side update it.
15 Setup an history where one side copy and rename a file (and update it) while the other side update it.
16
16
17 $ hg init t
17 $ hg init t
18 $ cd t
18 $ cd t
19
19
20 $ echo 1 > a
20 $ echo 1 > a
21 $ hg ci -qAm "first"
21 $ hg ci -qAm "first"
22
22
23 $ hg cp a b
23 $ hg cp a b
24 $ hg mv a c
24 $ hg mv a c
25 $ echo 2 >> b
25 $ echo 2 >> b
26 $ echo 2 >> c
26 $ echo 2 >> c
27
27
28 $ hg ci -qAm "second"
28 $ hg ci -qAm "second"
29
29
30 $ hg co -C 0
30 $ hg co -C 0
31 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
31 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
32
32
33 $ echo 0 > a
33 $ echo 0 > a
34 $ echo 1 >> a
34 $ echo 1 >> a
35
35
36 $ hg ci -qAm "other"
36 $ hg ci -qAm "other"
37
37
38 $ hg log -G --patch
38 $ hg log -G --patch
39 @ changeset: 2:add3f11052fa
39 @ changeset: 2:add3f11052fa
40 | tag: tip
40 | tag: tip
41 | parent: 0:b8bf91eeebbc
41 | parent: 0:b8bf91eeebbc
42 | user: test
42 | user: test
43 | date: Thu Jan 01 00:00:00 1970 +0000
43 | date: Thu Jan 01 00:00:00 1970 +0000
44 | summary: other
44 | summary: other
45 |
45 |
46 | diff --git a/a b/a
46 | diff --git a/a b/a
47 | --- a/a
47 | --- a/a
48 | +++ b/a
48 | +++ b/a
49 | @@ -1,1 +1,2 @@
49 | @@ -1,1 +1,2 @@
50 | +0
50 | +0
51 | 1
51 | 1
52 |
52 |
53 | o changeset: 1:17c05bb7fcb6
53 | o changeset: 1:17c05bb7fcb6
54 |/ user: test
54 |/ user: test
55 | date: Thu Jan 01 00:00:00 1970 +0000
55 | date: Thu Jan 01 00:00:00 1970 +0000
56 | summary: second
56 | summary: second
57 |
57 |
58 | diff --git a/a b/b
58 | diff --git a/a b/b
59 | rename from a
59 | rename from a
60 | rename to b
60 | rename to b
61 | --- a/a
61 | --- a/a
62 | +++ b/b
62 | +++ b/b
63 | @@ -1,1 +1,2 @@
63 | @@ -1,1 +1,2 @@
64 | 1
64 | 1
65 | +2
65 | +2
66 | diff --git a/a b/c
66 | diff --git a/a b/c
67 | copy from a
67 | copy from a
68 | copy to c
68 | copy to c
69 | --- a/a
69 | --- a/a
70 | +++ b/c
70 | +++ b/c
71 | @@ -1,1 +1,2 @@
71 | @@ -1,1 +1,2 @@
72 | 1
72 | 1
73 | +2
73 | +2
74 |
74 |
75 o changeset: 0:b8bf91eeebbc
75 o changeset: 0:b8bf91eeebbc
76 user: test
76 user: test
77 date: Thu Jan 01 00:00:00 1970 +0000
77 date: Thu Jan 01 00:00:00 1970 +0000
78 summary: first
78 summary: first
79
79
80 diff --git a/a b/a
80 diff --git a/a b/a
81 new file mode 100644
81 new file mode 100644
82 --- /dev/null
82 --- /dev/null
83 +++ b/a
83 +++ b/a
84 @@ -0,0 +1,1 @@
84 @@ -0,0 +1,1 @@
85 +1
85 +1
86
86
87
87
88 Test Simple Merge
88 Test Simple Merge
89 =================
89 =================
90
90
91 $ hg merge --debug
91 $ hg merge --debug
92 unmatched files in other:
92 unmatched files in other:
93 b
93 b
94 c
94 c
95 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
95 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
96 on remote side:
96 on remote side:
97 src: 'a' -> dst: 'b' *
97 src: 'a' -> dst: 'b' *
98 src: 'a' -> dst: 'c' *
98 src: 'a' -> dst: 'c' *
99 checking for directory renames
99 checking for directory renames
100 resolving manifests
100 resolving manifests
101 branchmerge: True, force: False, partial: False
101 branchmerge: True, force: False, partial: False
102 ancestor: b8bf91eeebbc, local: add3f11052fa+, remote: 17c05bb7fcb6
102 ancestor: b8bf91eeebbc, local: add3f11052fa+, remote: 17c05bb7fcb6
103 starting 4 threads for background file closing (?)
103 starting 4 threads for background file closing (?)
104 preserving a for resolve of b
104 preserving a for resolve of b
105 preserving a for resolve of c
105 preserving a for resolve of c
106 removing a
106 removing a
107 b: remote moved from a -> m (premerge)
107 b: remote moved from a -> m
108 picked tool ':merge' for b (binary False symlink False changedelete False)
108 picked tool ':merge' for b (binary False symlink False changedelete False)
109 merging a and b to b
109 merging a and b to b
110 my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc
110 my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc
111 premerge successful
111 premerge successful
112 c: remote moved from a -> m (premerge)
112 c: remote moved from a -> m
113 picked tool ':merge' for c (binary False symlink False changedelete False)
113 picked tool ':merge' for c (binary False symlink False changedelete False)
114 merging a and c to c
114 merging a and c to c
115 my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc
115 my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc
116 premerge successful
116 premerge successful
117 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
117 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
118 (branch merge, don't forget to commit)
118 (branch merge, don't forget to commit)
119
119
120 file b
120 file b
121 $ cat b
121 $ cat b
122 0
122 0
123 1
123 1
124 2
124 2
125
125
126 file c
126 file c
127 $ cat c
127 $ cat c
128 0
128 0
129 1
129 1
130 2
130 2
131
131
132 Test disabling copy tracing
132 Test disabling copy tracing
133 ===========================
133 ===========================
134
134
135 first verify copy metadata was kept
135 first verify copy metadata was kept
136 -----------------------------------
136 -----------------------------------
137
137
138 $ hg up -qC 2
138 $ hg up -qC 2
139 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase=
139 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase=
140 rebasing 2:add3f11052fa tip "other"
140 rebasing 2:add3f11052fa tip "other"
141 merging b and a to b
141 merging b and a to b
142 merging c and a to c
142 merging c and a to c
143
143
144 $ cat b
144 $ cat b
145 0
145 0
146 1
146 1
147 2
147 2
148
148
149 next verify copy metadata is lost when disabled
149 next verify copy metadata is lost when disabled
150 ------------------------------------------------
150 ------------------------------------------------
151
151
152 $ hg strip -r . --config extensions.strip=
152 $ hg strip -r . --config extensions.strip=
153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg
154 saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg
155 $ hg up -qC 2
155 $ hg up -qC 2
156 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.copytrace=off --config ui.interactive=True << EOF
156 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.copytrace=off --config ui.interactive=True << EOF
157 > c
157 > c
158 > EOF
158 > EOF
159 rebasing 2:add3f11052fa tip "other"
159 rebasing 2:add3f11052fa tip "other"
160 file 'a' was deleted in local [dest] but was modified in other [source].
160 file 'a' was deleted in local [dest] but was modified in other [source].
161 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
161 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
162 What do you want to do? c
162 What do you want to do? c
163
163
164 $ cat b
164 $ cat b
165 1
165 1
166 2
166 2
167
167
168 $ cd ..
168 $ cd ..
169
169
170 Verify disabling copy tracing still keeps copies from rebase source
170 Verify disabling copy tracing still keeps copies from rebase source
171 -------------------------------------------------------------------
171 -------------------------------------------------------------------
172
172
173 $ hg init copydisable
173 $ hg init copydisable
174 $ cd copydisable
174 $ cd copydisable
175 $ touch a
175 $ touch a
176 $ hg ci -Aqm 'add a'
176 $ hg ci -Aqm 'add a'
177 $ touch b
177 $ touch b
178 $ hg ci -Aqm 'add b, c'
178 $ hg ci -Aqm 'add b, c'
179 $ hg cp b x
179 $ hg cp b x
180 $ echo x >> x
180 $ echo x >> x
181 $ hg ci -qm 'copy b->x'
181 $ hg ci -qm 'copy b->x'
182 $ hg up -q 1
182 $ hg up -q 1
183 $ touch z
183 $ touch z
184 $ hg ci -Aqm 'add z'
184 $ hg ci -Aqm 'add z'
185 $ hg log -G -T '{rev} {desc}\n'
185 $ hg log -G -T '{rev} {desc}\n'
186 @ 3 add z
186 @ 3 add z
187 |
187 |
188 | o 2 copy b->x
188 | o 2 copy b->x
189 |/
189 |/
190 o 1 add b, c
190 o 1 add b, c
191 |
191 |
192 o 0 add a
192 o 0 add a
193
193
194 $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.copytrace=off
194 $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.copytrace=off
195 rebasing 2:6adcf8c12e7d "copy b->x"
195 rebasing 2:6adcf8c12e7d "copy b->x"
196 saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg
196 saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg
197 $ hg up -q 3
197 $ hg up -q 3
198 $ hg log -f x -T '{rev} {desc}\n'
198 $ hg log -f x -T '{rev} {desc}\n'
199 3 copy b->x
199 3 copy b->x
200 1 add b, c
200 1 add b, c
201
201
202 $ cd ../
202 $ cd ../
203
203
204
204
205 test storage preservation
205 test storage preservation
206 -------------------------
206 -------------------------
207
207
208 Verify rebase do not discard recorded copies data when copy tracing usage is
208 Verify rebase do not discard recorded copies data when copy tracing usage is
209 disabled.
209 disabled.
210
210
211 Setup
211 Setup
212
212
213 $ hg init copydisable3
213 $ hg init copydisable3
214 $ cd copydisable3
214 $ cd copydisable3
215 $ touch a
215 $ touch a
216 $ hg ci -Aqm 'add a'
216 $ hg ci -Aqm 'add a'
217 $ hg cp a b
217 $ hg cp a b
218 $ hg ci -Aqm 'copy a->b'
218 $ hg ci -Aqm 'copy a->b'
219 $ hg mv b c
219 $ hg mv b c
220 $ hg ci -Aqm 'move b->c'
220 $ hg ci -Aqm 'move b->c'
221 $ hg up -q 0
221 $ hg up -q 0
222 $ hg cp a b
222 $ hg cp a b
223 $ echo b >> b
223 $ echo b >> b
224 $ hg ci -Aqm 'copy a->b (2)'
224 $ hg ci -Aqm 'copy a->b (2)'
225 $ hg log -G -T '{rev} {desc}\n'
225 $ hg log -G -T '{rev} {desc}\n'
226 @ 3 copy a->b (2)
226 @ 3 copy a->b (2)
227 |
227 |
228 | o 2 move b->c
228 | o 2 move b->c
229 | |
229 | |
230 | o 1 copy a->b
230 | o 1 copy a->b
231 |/
231 |/
232 o 0 add a
232 o 0 add a
233
233
234
234
235 Actual Test
235 Actual Test
236
236
237 A file is copied on one side and has been moved twice on the other side. the
237 A file is copied on one side and has been moved twice on the other side. the
238 file is copied from `0:a`, so the file history of the `3:b` should trace directly to `0:a`.
238 file is copied from `0:a`, so the file history of the `3:b` should trace directly to `0:a`.
239
239
240 $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.copytrace=off
240 $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.copytrace=off
241 rebasing 3:47e1a9e6273b tip "copy a->b (2)"
241 rebasing 3:47e1a9e6273b tip "copy a->b (2)"
242 saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg
242 saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg
243
243
244 $ hg log -G -f b
244 $ hg log -G -f b
245 @ changeset: 3:76024fb4b05b
245 @ changeset: 3:76024fb4b05b
246 : tag: tip
246 : tag: tip
247 : user: test
247 : user: test
248 : date: Thu Jan 01 00:00:00 1970 +0000
248 : date: Thu Jan 01 00:00:00 1970 +0000
249 : summary: copy a->b (2)
249 : summary: copy a->b (2)
250 :
250 :
251 o changeset: 0:ac82d8b1f7c4
251 o changeset: 0:ac82d8b1f7c4
252 user: test
252 user: test
253 date: Thu Jan 01 00:00:00 1970 +0000
253 date: Thu Jan 01 00:00:00 1970 +0000
254 summary: add a
254 summary: add a
255
255
@@ -1,66 +1,66 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3
3
4 $ echo line 1 > foo
4 $ echo line 1 > foo
5 $ hg ci -qAm 'add foo'
5 $ hg ci -qAm 'add foo'
6
6
7 copy foo to bar and change both files
7 copy foo to bar and change both files
8 $ hg cp foo bar
8 $ hg cp foo bar
9 $ echo line 2-1 >> foo
9 $ echo line 2-1 >> foo
10 $ echo line 2-2 >> bar
10 $ echo line 2-2 >> bar
11 $ hg ci -m 'cp foo bar; change both'
11 $ hg ci -m 'cp foo bar; change both'
12
12
13 in another branch, change foo in a way that doesn't conflict with
13 in another branch, change foo in a way that doesn't conflict with
14 the other changes
14 the other changes
15 $ hg up -qC 0
15 $ hg up -qC 0
16 $ echo line 0 > foo
16 $ echo line 0 > foo
17 $ hg cat foo >> foo
17 $ hg cat foo >> foo
18 $ hg ci -m 'change foo'
18 $ hg ci -m 'change foo'
19 created new head
19 created new head
20
20
21 we get conflicts that shouldn't be there
21 we get conflicts that shouldn't be there
22 $ hg merge -P
22 $ hg merge -P
23 changeset: 1:484bf6903104
23 changeset: 1:484bf6903104
24 user: test
24 user: test
25 date: Thu Jan 01 00:00:00 1970 +0000
25 date: Thu Jan 01 00:00:00 1970 +0000
26 summary: cp foo bar; change both
26 summary: cp foo bar; change both
27
27
28 $ hg merge --debug
28 $ hg merge --debug
29 unmatched files in other:
29 unmatched files in other:
30 bar
30 bar
31 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
31 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
32 on remote side:
32 on remote side:
33 src: 'foo' -> dst: 'bar' *
33 src: 'foo' -> dst: 'bar' *
34 checking for directory renames
34 checking for directory renames
35 resolving manifests
35 resolving manifests
36 branchmerge: True, force: False, partial: False
36 branchmerge: True, force: False, partial: False
37 ancestor: e6dc8efe11cc, local: 6a0df1dad128+, remote: 484bf6903104
37 ancestor: e6dc8efe11cc, local: 6a0df1dad128+, remote: 484bf6903104
38 starting 4 threads for background file closing (?)
38 starting 4 threads for background file closing (?)
39 preserving foo for resolve of bar
39 preserving foo for resolve of bar
40 preserving foo for resolve of foo
40 preserving foo for resolve of foo
41 bar: remote copied from foo -> m (premerge)
41 bar: remote copied from foo -> m
42 picked tool ':merge' for bar (binary False symlink False changedelete False)
42 picked tool ':merge' for bar (binary False symlink False changedelete False)
43 merging foo and bar to bar
43 merging foo and bar to bar
44 my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc
44 my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc
45 premerge successful
45 premerge successful
46 foo: versions differ -> m (premerge)
46 foo: versions differ -> m
47 picked tool ':merge' for foo (binary False symlink False changedelete False)
47 picked tool ':merge' for foo (binary False symlink False changedelete False)
48 merging foo
48 merging foo
49 my foo@6a0df1dad128+ other foo@484bf6903104 ancestor foo@e6dc8efe11cc
49 my foo@6a0df1dad128+ other foo@484bf6903104 ancestor foo@e6dc8efe11cc
50 premerge successful
50 premerge successful
51 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
51 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
52 (branch merge, don't forget to commit)
52 (branch merge, don't forget to commit)
53
53
54 contents of foo
54 contents of foo
55 $ cat foo
55 $ cat foo
56 line 0
56 line 0
57 line 1
57 line 1
58 line 2-1
58 line 2-1
59
59
60 contents of bar
60 contents of bar
61 $ cat bar
61 $ cat bar
62 line 0
62 line 0
63 line 1
63 line 1
64 line 2-2
64 line 2-2
65
65
66 $ cd ..
66 $ cd ..
@@ -1,926 +1,925 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extdiff]
2 > [extdiff]
3 > # for portability:
3 > # for portability:
4 > pdiff = sh "$RUNTESTDIR/pdiff"
4 > pdiff = sh "$RUNTESTDIR/pdiff"
5 > EOF
5 > EOF
6
6
7 Create a repo with some stuff in it:
7 Create a repo with some stuff in it:
8
8
9 $ hg init a
9 $ hg init a
10 $ cd a
10 $ cd a
11 $ echo a > a
11 $ echo a > a
12 $ echo a > d
12 $ echo a > d
13 $ echo a > e
13 $ echo a > e
14 $ hg ci -qAm0
14 $ hg ci -qAm0
15 $ echo b > a
15 $ echo b > a
16 $ hg ci -m1 -u bar
16 $ hg ci -m1 -u bar
17 $ hg mv a b
17 $ hg mv a b
18 $ hg ci -m2
18 $ hg ci -m2
19 $ hg cp b c
19 $ hg cp b c
20 $ hg ci -m3 -u baz
20 $ hg ci -m3 -u baz
21 $ echo b > d
21 $ echo b > d
22 $ echo f > e
22 $ echo f > e
23 $ hg ci -m4
23 $ hg ci -m4
24 $ hg up -q 3
24 $ hg up -q 3
25 $ echo b > e
25 $ echo b > e
26 $ hg branch -q stable
26 $ hg branch -q stable
27 $ hg ci -m5
27 $ hg ci -m5
28 $ hg merge -q default --tool internal:local # for conflicts in e, choose 5 and ignore 4
28 $ hg merge -q default --tool internal:local # for conflicts in e, choose 5 and ignore 4
29 $ hg branch -q default
29 $ hg branch -q default
30 $ hg ci -m6
30 $ hg ci -m6
31 $ hg phase --public 3
31 $ hg phase --public 3
32 $ hg phase --force --secret 6
32 $ hg phase --force --secret 6
33
33
34 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
34 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
35 @ test@6.secret: 6
35 @ test@6.secret: 6
36 |\
36 |\
37 | o test@5.draft: 5
37 | o test@5.draft: 5
38 | |
38 | |
39 o | test@4.draft: 4
39 o | test@4.draft: 4
40 |/
40 |/
41 o baz@3.public: 3
41 o baz@3.public: 3
42 |
42 |
43 o test@2.public: 2
43 o test@2.public: 2
44 |
44 |
45 o bar@1.public: 1
45 o bar@1.public: 1
46 |
46 |
47 o test@0.public: 0
47 o test@0.public: 0
48
48
49 Test --base for grafting the merge of 4 from the perspective of 5, thus only getting the change to d
49 Test --base for grafting the merge of 4 from the perspective of 5, thus only getting the change to d
50
50
51 $ hg up -cqr 3
51 $ hg up -cqr 3
52 $ hg graft -r 6 --base 5
52 $ hg graft -r 6 --base 5
53 grafting 6:25a2b029d3ae "6" (tip)
53 grafting 6:25a2b029d3ae "6" (tip)
54 merging e
54 merging e
55 $ hg st --change .
55 $ hg st --change .
56 M d
56 M d
57
57
58 $ hg -q strip . --config extensions.strip=
58 $ hg -q strip . --config extensions.strip=
59
59
60 Test --base for collapsing changesets 2 and 3, thus getting both b and c
60 Test --base for collapsing changesets 2 and 3, thus getting both b and c
61
61
62 $ hg up -cqr 0
62 $ hg up -cqr 0
63 $ hg graft -r 3 --base 1
63 $ hg graft -r 3 --base 1
64 grafting 3:4c60f11aa304 "3"
64 grafting 3:4c60f11aa304 "3"
65 merging a and b to b
65 merging a and b to b
66 merging a and c to c
66 merging a and c to c
67 $ hg st --change .
67 $ hg st --change .
68 A b
68 A b
69 A c
69 A c
70 R a
70 R a
71
71
72 $ hg -q strip . --config extensions.strip=
72 $ hg -q strip . --config extensions.strip=
73
73
74 Specifying child as --base revision fails safely (perhaps slightly confusing, but consistent)
74 Specifying child as --base revision fails safely (perhaps slightly confusing, but consistent)
75
75
76 $ hg graft -r 2 --base 3
76 $ hg graft -r 2 --base 3
77 grafting 2:5c095ad7e90f "2"
77 grafting 2:5c095ad7e90f "2"
78 note: possible conflict - c was deleted and renamed to:
78 note: possible conflict - c was deleted and renamed to:
79 a
79 a
80 note: graft of 2:5c095ad7e90f created no changes to commit
80 note: graft of 2:5c095ad7e90f created no changes to commit
81
81
82 Can't continue without starting:
82 Can't continue without starting:
83
83
84 $ hg -q up -cr tip
84 $ hg -q up -cr tip
85 $ hg rm -q e
85 $ hg rm -q e
86 $ hg graft --continue
86 $ hg graft --continue
87 abort: no graft in progress
87 abort: no graft in progress
88 [20]
88 [20]
89 $ hg revert -r . -q e
89 $ hg revert -r . -q e
90
90
91 Need to specify a rev:
91 Need to specify a rev:
92
92
93 $ hg graft
93 $ hg graft
94 abort: no revisions specified
94 abort: no revisions specified
95 [10]
95 [10]
96
96
97 Can't graft ancestor:
97 Can't graft ancestor:
98
98
99 $ hg graft 1 2
99 $ hg graft 1 2
100 skipping ancestor revision 1:5d205f8b35b6
100 skipping ancestor revision 1:5d205f8b35b6
101 skipping ancestor revision 2:5c095ad7e90f
101 skipping ancestor revision 2:5c095ad7e90f
102 [255]
102 [255]
103
103
104 Specify revisions with -r:
104 Specify revisions with -r:
105
105
106 $ hg graft -r 1 -r 2
106 $ hg graft -r 1 -r 2
107 skipping ancestor revision 1:5d205f8b35b6
107 skipping ancestor revision 1:5d205f8b35b6
108 skipping ancestor revision 2:5c095ad7e90f
108 skipping ancestor revision 2:5c095ad7e90f
109 [255]
109 [255]
110
110
111 $ hg graft -r 1 2
111 $ hg graft -r 1 2
112 warning: inconsistent use of --rev might give unexpected revision ordering!
112 warning: inconsistent use of --rev might give unexpected revision ordering!
113 skipping ancestor revision 2:5c095ad7e90f
113 skipping ancestor revision 2:5c095ad7e90f
114 skipping ancestor revision 1:5d205f8b35b6
114 skipping ancestor revision 1:5d205f8b35b6
115 [255]
115 [255]
116
116
117 Conflicting date/user options:
117 Conflicting date/user options:
118
118
119 $ hg up -q 0
119 $ hg up -q 0
120 $ hg graft -U --user foo 2
120 $ hg graft -U --user foo 2
121 abort: cannot specify both --user and --currentuser
121 abort: cannot specify both --user and --currentuser
122 [10]
122 [10]
123 $ hg graft -D --date '0 0' 2
123 $ hg graft -D --date '0 0' 2
124 abort: cannot specify both --date and --currentdate
124 abort: cannot specify both --date and --currentdate
125 [10]
125 [10]
126
126
127 Can't graft with dirty wd:
127 Can't graft with dirty wd:
128
128
129 $ hg up -q 0
129 $ hg up -q 0
130 $ echo foo > a
130 $ echo foo > a
131 $ hg graft 1
131 $ hg graft 1
132 abort: uncommitted changes
132 abort: uncommitted changes
133 [20]
133 [20]
134 $ hg revert a
134 $ hg revert a
135
135
136 Graft a rename:
136 Graft a rename:
137 (this also tests that editor is invoked if '--edit' is specified)
137 (this also tests that editor is invoked if '--edit' is specified)
138
138
139 $ hg status --rev "2^1" --rev 2
139 $ hg status --rev "2^1" --rev 2
140 A b
140 A b
141 R a
141 R a
142 $ HGEDITOR=cat hg graft 2 -u foo --edit
142 $ HGEDITOR=cat hg graft 2 -u foo --edit
143 grafting 2:5c095ad7e90f "2"
143 grafting 2:5c095ad7e90f "2"
144 merging a and b to b
144 merging a and b to b
145 2
145 2
146
146
147
147
148 HG: Enter commit message. Lines beginning with 'HG:' are removed.
148 HG: Enter commit message. Lines beginning with 'HG:' are removed.
149 HG: Leave message empty to abort commit.
149 HG: Leave message empty to abort commit.
150 HG: --
150 HG: --
151 HG: user: foo
151 HG: user: foo
152 HG: branch 'default'
152 HG: branch 'default'
153 HG: added b
153 HG: added b
154 HG: removed a
154 HG: removed a
155 $ hg export tip --git
155 $ hg export tip --git
156 # HG changeset patch
156 # HG changeset patch
157 # User foo
157 # User foo
158 # Date 0 0
158 # Date 0 0
159 # Thu Jan 01 00:00:00 1970 +0000
159 # Thu Jan 01 00:00:00 1970 +0000
160 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
160 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
161 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
161 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
162 2
162 2
163
163
164 diff --git a/a b/b
164 diff --git a/a b/b
165 rename from a
165 rename from a
166 rename to b
166 rename to b
167
167
168 Look for extra:source
168 Look for extra:source
169
169
170 $ hg log --debug -r tip
170 $ hg log --debug -r tip
171 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
171 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
172 tag: tip
172 tag: tip
173 phase: draft
173 phase: draft
174 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
174 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
175 parent: -1:0000000000000000000000000000000000000000
175 parent: -1:0000000000000000000000000000000000000000
176 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
176 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
177 user: foo
177 user: foo
178 date: Thu Jan 01 00:00:00 1970 +0000
178 date: Thu Jan 01 00:00:00 1970 +0000
179 files+: b
179 files+: b
180 files-: a
180 files-: a
181 extra: branch=default
181 extra: branch=default
182 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
182 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
183 description:
183 description:
184 2
184 2
185
185
186
186
187
187
188 Graft out of order, skipping a merge and a duplicate
188 Graft out of order, skipping a merge and a duplicate
189 (this also tests that editor is not invoked if '--edit' is not specified)
189 (this also tests that editor is not invoked if '--edit' is not specified)
190
190
191 $ hg graft 1 5 4 3 'merge()' 2 -n
191 $ hg graft 1 5 4 3 'merge()' 2 -n
192 skipping ungraftable merge revision 6
192 skipping ungraftable merge revision 6
193 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
193 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
194 grafting 1:5d205f8b35b6 "1"
194 grafting 1:5d205f8b35b6 "1"
195 grafting 5:97f8bfe72746 "5"
195 grafting 5:97f8bfe72746 "5"
196 grafting 4:9c233e8e184d "4"
196 grafting 4:9c233e8e184d "4"
197 grafting 3:4c60f11aa304 "3"
197 grafting 3:4c60f11aa304 "3"
198
198
199 $ HGEDITOR=cat hg graft 1 5 'merge()' 2 --debug
199 $ HGEDITOR=cat hg graft 1 5 'merge()' 2 --debug
200 skipping ungraftable merge revision 6
200 skipping ungraftable merge revision 6
201 scanning for duplicate grafts
201 scanning for duplicate grafts
202 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
202 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
203 grafting 1:5d205f8b35b6 "1"
203 grafting 1:5d205f8b35b6 "1"
204 unmatched files in local:
204 unmatched files in local:
205 b
205 b
206 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
206 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
207 on local side:
207 on local side:
208 src: 'a' -> dst: 'b' *
208 src: 'a' -> dst: 'b' *
209 checking for directory renames
209 checking for directory renames
210 resolving manifests
210 resolving manifests
211 branchmerge: True, force: True, partial: False
211 branchmerge: True, force: True, partial: False
212 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
212 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
213 starting 4 threads for background file closing (?)
213 starting 4 threads for background file closing (?)
214 preserving b for resolve of b
214 preserving b for resolve of b
215 b: local copied/moved from a -> m (premerge)
215 b: local copied/moved from a -> m
216 picked tool ':merge' for b (binary False symlink False changedelete False)
216 picked tool ':merge' for b (binary False symlink False changedelete False)
217 merging b and a to b
217 merging b and a to b
218 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
218 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
219 premerge successful
219 premerge successful
220 committing files:
220 committing files:
221 b
221 b
222 committing manifest
222 committing manifest
223 committing changelog
223 committing changelog
224 updating the branch cache
224 updating the branch cache
225 grafting 5:97f8bfe72746 "5"
225 grafting 5:97f8bfe72746 "5"
226 resolving manifests
226 resolving manifests
227 branchmerge: True, force: True, partial: False
227 branchmerge: True, force: True, partial: False
228 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
228 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
229 e: remote is newer -> g
229 e: remote is newer -> g
230 getting e
230 getting e
231 committing files:
231 committing files:
232 e
232 e
233 committing manifest
233 committing manifest
234 committing changelog
234 committing changelog
235 updating the branch cache
235 updating the branch cache
236 $ HGEDITOR=cat hg graft 4 3 --log --debug
236 $ HGEDITOR=cat hg graft 4 3 --log --debug
237 scanning for duplicate grafts
237 scanning for duplicate grafts
238 grafting 4:9c233e8e184d "4"
238 grafting 4:9c233e8e184d "4"
239 resolving manifests
239 resolving manifests
240 branchmerge: True, force: True, partial: False
240 branchmerge: True, force: True, partial: False
241 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
241 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
242 d: remote is newer -> g
242 d: remote is newer -> g
243 getting d
243 getting d
244 preserving e for resolve of e
244 preserving e for resolve of e
245 e: versions differ -> m (premerge)
245 e: versions differ -> m
246 picked tool ':merge' for e (binary False symlink False changedelete False)
246 picked tool ':merge' for e (binary False symlink False changedelete False)
247 merging e
247 merging e
248 my e@1905859650ec+ other e@9c233e8e184d ancestor e@4c60f11aa304
248 my e@1905859650ec+ other e@9c233e8e184d ancestor e@4c60f11aa304
249 e: versions differ -> m (merge)
250 picked tool ':merge' for e (binary False symlink False changedelete False)
249 picked tool ':merge' for e (binary False symlink False changedelete False)
251 my e@1905859650ec+ other e@9c233e8e184d ancestor e@4c60f11aa304
250 my e@1905859650ec+ other e@9c233e8e184d ancestor e@4c60f11aa304
252 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
251 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
253 abort: unresolved conflicts, can't continue
252 abort: unresolved conflicts, can't continue
254 (use 'hg resolve' and 'hg graft --continue')
253 (use 'hg resolve' and 'hg graft --continue')
255 [1]
254 [1]
256
255
257 Summary should mention graft:
256 Summary should mention graft:
258
257
259 $ hg summary |grep graft
258 $ hg summary |grep graft
260 commit: 2 modified, 2 unknown, 1 unresolved (graft in progress)
259 commit: 2 modified, 2 unknown, 1 unresolved (graft in progress)
261
260
262 Using status to get more context
261 Using status to get more context
263
262
264 $ hg status --verbose
263 $ hg status --verbose
265 M d
264 M d
266 M e
265 M e
267 ? a.orig
266 ? a.orig
268 ? e.orig
267 ? e.orig
269 # The repository is in an unfinished *graft* state.
268 # The repository is in an unfinished *graft* state.
270
269
271 # Unresolved merge conflicts:
270 # Unresolved merge conflicts:
272 #
271 #
273 # e
272 # e
274 #
273 #
275 # To mark files as resolved: hg resolve --mark FILE
274 # To mark files as resolved: hg resolve --mark FILE
276
275
277 # To continue: hg graft --continue
276 # To continue: hg graft --continue
278 # To abort: hg graft --abort
277 # To abort: hg graft --abort
279 # To stop: hg graft --stop
278 # To stop: hg graft --stop
280
279
281
280
282 Commit while interrupted should fail:
281 Commit while interrupted should fail:
283
282
284 $ hg ci -m 'commit interrupted graft'
283 $ hg ci -m 'commit interrupted graft'
285 abort: graft in progress
284 abort: graft in progress
286 (use 'hg graft --continue' or 'hg graft --stop' to stop)
285 (use 'hg graft --continue' or 'hg graft --stop' to stop)
287 [20]
286 [20]
288
287
289 Abort the graft and try committing:
288 Abort the graft and try committing:
290
289
291 $ hg up -C .
290 $ hg up -C .
292 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 $ echo c >> e
292 $ echo c >> e
294 $ hg ci -mtest
293 $ hg ci -mtest
295
294
296 $ hg strip . --config extensions.strip=
295 $ hg strip . --config extensions.strip=
297 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
298 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
297 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
299
298
300 Graft again:
299 Graft again:
301
300
302 $ hg graft 1 5 4 3 'merge()' 2
301 $ hg graft 1 5 4 3 'merge()' 2
303 skipping ungraftable merge revision 6
302 skipping ungraftable merge revision 6
304 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
303 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
305 skipping revision 1:5d205f8b35b6 (already grafted to 8:6b9e5368ca4e)
304 skipping revision 1:5d205f8b35b6 (already grafted to 8:6b9e5368ca4e)
306 skipping revision 5:97f8bfe72746 (already grafted to 9:1905859650ec)
305 skipping revision 5:97f8bfe72746 (already grafted to 9:1905859650ec)
307 grafting 4:9c233e8e184d "4"
306 grafting 4:9c233e8e184d "4"
308 merging e
307 merging e
309 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
308 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
310 abort: unresolved conflicts, can't continue
309 abort: unresolved conflicts, can't continue
311 (use 'hg resolve' and 'hg graft --continue')
310 (use 'hg resolve' and 'hg graft --continue')
312 [1]
311 [1]
313
312
314 Continue without resolve should fail:
313 Continue without resolve should fail:
315
314
316 $ hg graft -c
315 $ hg graft -c
317 grafting 4:9c233e8e184d "4"
316 grafting 4:9c233e8e184d "4"
318 abort: unresolved merge conflicts (see 'hg help resolve')
317 abort: unresolved merge conflicts (see 'hg help resolve')
319 [20]
318 [20]
320
319
321 Fix up:
320 Fix up:
322
321
323 $ echo b > e
322 $ echo b > e
324 $ hg resolve -m e
323 $ hg resolve -m e
325 (no more unresolved files)
324 (no more unresolved files)
326 continue: hg graft --continue
325 continue: hg graft --continue
327
326
328 Continue with a revision should fail:
327 Continue with a revision should fail:
329
328
330 $ hg graft -c 6
329 $ hg graft -c 6
331 abort: can't specify --continue and revisions
330 abort: can't specify --continue and revisions
332 [10]
331 [10]
333
332
334 $ hg graft -c -r 6
333 $ hg graft -c -r 6
335 abort: can't specify --continue and revisions
334 abort: can't specify --continue and revisions
336 [10]
335 [10]
337
336
338 Continue for real, clobber usernames
337 Continue for real, clobber usernames
339
338
340 $ hg graft -c -U
339 $ hg graft -c -U
341 grafting 4:9c233e8e184d "4"
340 grafting 4:9c233e8e184d "4"
342 grafting 3:4c60f11aa304 "3"
341 grafting 3:4c60f11aa304 "3"
343
342
344 Compare with original:
343 Compare with original:
345
344
346 $ hg diff -r 6
345 $ hg diff -r 6
347 $ hg status --rev 0:. -C
346 $ hg status --rev 0:. -C
348 M d
347 M d
349 M e
348 M e
350 A b
349 A b
351 a
350 a
352 A c
351 A c
353 a
352 a
354 R a
353 R a
355
354
356 View graph:
355 View graph:
357
356
358 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
357 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
359 @ test@11.draft: 3
358 @ test@11.draft: 3
360 |
359 |
361 o test@10.draft: 4
360 o test@10.draft: 4
362 |
361 |
363 o test@9.draft: 5
362 o test@9.draft: 5
364 |
363 |
365 o bar@8.draft: 1
364 o bar@8.draft: 1
366 |
365 |
367 o foo@7.draft: 2
366 o foo@7.draft: 2
368 |
367 |
369 | o test@6.secret: 6
368 | o test@6.secret: 6
370 | |\
369 | |\
371 | | o test@5.draft: 5
370 | | o test@5.draft: 5
372 | | |
371 | | |
373 | o | test@4.draft: 4
372 | o | test@4.draft: 4
374 | |/
373 | |/
375 | o baz@3.public: 3
374 | o baz@3.public: 3
376 | |
375 | |
377 | o test@2.public: 2
376 | o test@2.public: 2
378 | |
377 | |
379 | o bar@1.public: 1
378 | o bar@1.public: 1
380 |/
379 |/
381 o test@0.public: 0
380 o test@0.public: 0
382
381
383 Graft again onto another branch should preserve the original source
382 Graft again onto another branch should preserve the original source
384 $ hg up -q 0
383 $ hg up -q 0
385 $ echo 'g'>g
384 $ echo 'g'>g
386 $ hg add g
385 $ hg add g
387 $ hg ci -m 7
386 $ hg ci -m 7
388 created new head
387 created new head
389 $ hg graft 7
388 $ hg graft 7
390 grafting 7:ef0ef43d49e7 "2"
389 grafting 7:ef0ef43d49e7 "2"
391
390
392 $ hg log -r 7 --template '{rev}:{node}\n'
391 $ hg log -r 7 --template '{rev}:{node}\n'
393 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
392 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
394 $ hg log -r 2 --template '{rev}:{node}\n'
393 $ hg log -r 2 --template '{rev}:{node}\n'
395 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
394 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
396
395
397 $ hg log --debug -r tip
396 $ hg log --debug -r tip
398 changeset: 13:7a4785234d87ec1aa420ed6b11afe40fa73e12a9
397 changeset: 13:7a4785234d87ec1aa420ed6b11afe40fa73e12a9
399 tag: tip
398 tag: tip
400 phase: draft
399 phase: draft
401 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
400 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
402 parent: -1:0000000000000000000000000000000000000000
401 parent: -1:0000000000000000000000000000000000000000
403 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
402 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
404 user: foo
403 user: foo
405 date: Thu Jan 01 00:00:00 1970 +0000
404 date: Thu Jan 01 00:00:00 1970 +0000
406 files+: b
405 files+: b
407 files-: a
406 files-: a
408 extra: branch=default
407 extra: branch=default
409 extra: intermediate-source=ef0ef43d49e79e81ddafdc7997401ba0041efc82
408 extra: intermediate-source=ef0ef43d49e79e81ddafdc7997401ba0041efc82
410 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
409 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
411 description:
410 description:
412 2
411 2
413
412
414
413
415 Disallow grafting an already grafted cset onto its original branch
414 Disallow grafting an already grafted cset onto its original branch
416 $ hg up -q 6
415 $ hg up -q 6
417 $ hg graft 7
416 $ hg graft 7
418 skipping already grafted revision 7:ef0ef43d49e7 (was grafted from 2:5c095ad7e90f)
417 skipping already grafted revision 7:ef0ef43d49e7 (was grafted from 2:5c095ad7e90f)
419 [255]
418 [255]
420
419
421 $ hg pdiff --config extensions.extdiff= --patch -r 2 -r 13
420 $ hg pdiff --config extensions.extdiff= --patch -r 2 -r 13
422 --- */hg-5c095ad7e90f.patch * (glob)
421 --- */hg-5c095ad7e90f.patch * (glob)
423 +++ */hg-7a4785234d87.patch * (glob)
422 +++ */hg-7a4785234d87.patch * (glob)
424 @@ -1,18 +1,18 @@
423 @@ -1,18 +1,18 @@
425 # HG changeset patch
424 # HG changeset patch
426 -# User test
425 -# User test
427 +# User foo
426 +# User foo
428 # Date 0 0
427 # Date 0 0
429 # Thu Jan 01 00:00:00 1970 +0000
428 # Thu Jan 01 00:00:00 1970 +0000
430 -# Node ID 5c095ad7e90f871700f02dd1fa5012cb4498a2d4
429 -# Node ID 5c095ad7e90f871700f02dd1fa5012cb4498a2d4
431 -# Parent 5d205f8b35b66bc36375c9534ffd3237730e8f04
430 -# Parent 5d205f8b35b66bc36375c9534ffd3237730e8f04
432 +# Node ID 7a4785234d87ec1aa420ed6b11afe40fa73e12a9
431 +# Node ID 7a4785234d87ec1aa420ed6b11afe40fa73e12a9
433 +# Parent b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
432 +# Parent b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
434 2
433 2
435
434
436 -diff -r 5d205f8b35b6 -r 5c095ad7e90f a
435 -diff -r 5d205f8b35b6 -r 5c095ad7e90f a
437 +diff -r b592ea63bb0c -r 7a4785234d87 a
436 +diff -r b592ea63bb0c -r 7a4785234d87 a
438 --- a/a Thu Jan 01 00:00:00 1970 +0000
437 --- a/a Thu Jan 01 00:00:00 1970 +0000
439 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
438 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
440 @@ -1,1 +0,0 @@
439 @@ -1,1 +0,0 @@
441 --b
440 --b
442 -diff -r 5d205f8b35b6 -r 5c095ad7e90f b
441 -diff -r 5d205f8b35b6 -r 5c095ad7e90f b
443 +-a
442 +-a
444 +diff -r b592ea63bb0c -r 7a4785234d87 b
443 +diff -r b592ea63bb0c -r 7a4785234d87 b
445 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
444 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
446 +++ b/b Thu Jan 01 00:00:00 1970 +0000
445 +++ b/b Thu Jan 01 00:00:00 1970 +0000
447 @@ -0,0 +1,1 @@
446 @@ -0,0 +1,1 @@
448 -+b
447 -+b
449 ++a
448 ++a
450 [1]
449 [1]
451
450
452 $ hg pdiff --config extensions.extdiff= --patch -r 2 -r 13 -X .
451 $ hg pdiff --config extensions.extdiff= --patch -r 2 -r 13 -X .
453 --- */hg-5c095ad7e90f.patch * (glob)
452 --- */hg-5c095ad7e90f.patch * (glob)
454 +++ */hg-7a4785234d87.patch * (glob)
453 +++ */hg-7a4785234d87.patch * (glob)
455 @@ -1,8 +1,8 @@
454 @@ -1,8 +1,8 @@
456 # HG changeset patch
455 # HG changeset patch
457 -# User test
456 -# User test
458 +# User foo
457 +# User foo
459 # Date 0 0
458 # Date 0 0
460 # Thu Jan 01 00:00:00 1970 +0000
459 # Thu Jan 01 00:00:00 1970 +0000
461 -# Node ID 5c095ad7e90f871700f02dd1fa5012cb4498a2d4
460 -# Node ID 5c095ad7e90f871700f02dd1fa5012cb4498a2d4
462 -# Parent 5d205f8b35b66bc36375c9534ffd3237730e8f04
461 -# Parent 5d205f8b35b66bc36375c9534ffd3237730e8f04
463 +# Node ID 7a4785234d87ec1aa420ed6b11afe40fa73e12a9
462 +# Node ID 7a4785234d87ec1aa420ed6b11afe40fa73e12a9
464 +# Parent b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
463 +# Parent b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
465 2
464 2
466
465
467 [1]
466 [1]
468
467
469 Disallow grafting already grafted csets with the same origin onto each other
468 Disallow grafting already grafted csets with the same origin onto each other
470 $ hg up -q 13
469 $ hg up -q 13
471 $ hg graft 2
470 $ hg graft 2
472 skipping revision 2:5c095ad7e90f (already grafted to 13:7a4785234d87)
471 skipping revision 2:5c095ad7e90f (already grafted to 13:7a4785234d87)
473 [255]
472 [255]
474 $ hg graft 7
473 $ hg graft 7
475 skipping already grafted revision 7:ef0ef43d49e7 (13:7a4785234d87 also has origin 2:5c095ad7e90f)
474 skipping already grafted revision 7:ef0ef43d49e7 (13:7a4785234d87 also has origin 2:5c095ad7e90f)
476 [255]
475 [255]
477
476
478 $ hg up -q 7
477 $ hg up -q 7
479 $ hg graft 2
478 $ hg graft 2
480 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
479 skipping revision 2:5c095ad7e90f (already grafted to 7:ef0ef43d49e7)
481 [255]
480 [255]
482 $ hg graft tip
481 $ hg graft tip
483 skipping already grafted revision 13:7a4785234d87 (7:ef0ef43d49e7 also has origin 2:5c095ad7e90f)
482 skipping already grafted revision 13:7a4785234d87 (7:ef0ef43d49e7 also has origin 2:5c095ad7e90f)
484 [255]
483 [255]
485
484
486 Graft with --log
485 Graft with --log
487
486
488 $ hg up -Cq 1
487 $ hg up -Cq 1
489 $ hg graft 3 --log -u foo
488 $ hg graft 3 --log -u foo
490 grafting 3:4c60f11aa304 "3"
489 grafting 3:4c60f11aa304 "3"
491 $ hg log --template '{rev}:{node|short} {parents} {desc}\n' -r tip
490 $ hg log --template '{rev}:{node|short} {parents} {desc}\n' -r tip
492 14:0c921c65ef1e 1:5d205f8b35b6 3
491 14:0c921c65ef1e 1:5d205f8b35b6 3
493 (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
492 (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
494
493
495 Resolve conflicted graft
494 Resolve conflicted graft
496 $ hg up -q 0
495 $ hg up -q 0
497 $ echo b > a
496 $ echo b > a
498 $ hg ci -m 8
497 $ hg ci -m 8
499 created new head
498 created new head
500 $ echo c > a
499 $ echo c > a
501 $ hg ci -m 9
500 $ hg ci -m 9
502 $ hg graft 1 --tool internal:fail
501 $ hg graft 1 --tool internal:fail
503 grafting 1:5d205f8b35b6 "1"
502 grafting 1:5d205f8b35b6 "1"
504 abort: unresolved conflicts, can't continue
503 abort: unresolved conflicts, can't continue
505 (use 'hg resolve' and 'hg graft --continue')
504 (use 'hg resolve' and 'hg graft --continue')
506 [1]
505 [1]
507 $ hg resolve --all
506 $ hg resolve --all
508 merging a
507 merging a
509 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
508 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
510 [1]
509 [1]
511 $ cat a
510 $ cat a
512 <<<<<<< local: aaa4406d4f0a - test: 9
511 <<<<<<< local: aaa4406d4f0a - test: 9
513 c
512 c
514 =======
513 =======
515 b
514 b
516 >>>>>>> graft: 5d205f8b35b6 - bar: 1
515 >>>>>>> graft: 5d205f8b35b6 - bar: 1
517 $ echo b > a
516 $ echo b > a
518 $ hg resolve -m a
517 $ hg resolve -m a
519 (no more unresolved files)
518 (no more unresolved files)
520 continue: hg graft --continue
519 continue: hg graft --continue
521 $ hg graft -c
520 $ hg graft -c
522 grafting 1:5d205f8b35b6 "1"
521 grafting 1:5d205f8b35b6 "1"
523 $ hg export tip --git
522 $ hg export tip --git
524 # HG changeset patch
523 # HG changeset patch
525 # User bar
524 # User bar
526 # Date 0 0
525 # Date 0 0
527 # Thu Jan 01 00:00:00 1970 +0000
526 # Thu Jan 01 00:00:00 1970 +0000
528 # Node ID f67661df0c4804d301f064f332b57e7d5ddaf2be
527 # Node ID f67661df0c4804d301f064f332b57e7d5ddaf2be
529 # Parent aaa4406d4f0ae9befd6e58c82ec63706460cbca6
528 # Parent aaa4406d4f0ae9befd6e58c82ec63706460cbca6
530 1
529 1
531
530
532 diff --git a/a b/a
531 diff --git a/a b/a
533 --- a/a
532 --- a/a
534 +++ b/a
533 +++ b/a
535 @@ -1,1 +1,1 @@
534 @@ -1,1 +1,1 @@
536 -c
535 -c
537 +b
536 +b
538
537
539 Resolve conflicted graft with rename
538 Resolve conflicted graft with rename
540 $ echo c > a
539 $ echo c > a
541 $ hg ci -m 10
540 $ hg ci -m 10
542 $ hg graft 2 --tool internal:fail
541 $ hg graft 2 --tool internal:fail
543 grafting 2:5c095ad7e90f "2"
542 grafting 2:5c095ad7e90f "2"
544 abort: unresolved conflicts, can't continue
543 abort: unresolved conflicts, can't continue
545 (use 'hg resolve' and 'hg graft --continue')
544 (use 'hg resolve' and 'hg graft --continue')
546 [1]
545 [1]
547 $ hg resolve --all
546 $ hg resolve --all
548 merging a and b to b
547 merging a and b to b
549 (no more unresolved files)
548 (no more unresolved files)
550 continue: hg graft --continue
549 continue: hg graft --continue
551 $ hg graft -c
550 $ hg graft -c
552 grafting 2:5c095ad7e90f "2"
551 grafting 2:5c095ad7e90f "2"
553 $ hg export tip --git
552 $ hg export tip --git
554 # HG changeset patch
553 # HG changeset patch
555 # User test
554 # User test
556 # Date 0 0
555 # Date 0 0
557 # Thu Jan 01 00:00:00 1970 +0000
556 # Thu Jan 01 00:00:00 1970 +0000
558 # Node ID 9627f653b421c61fc1ea4c4e366745070fa3d2bc
557 # Node ID 9627f653b421c61fc1ea4c4e366745070fa3d2bc
559 # Parent ee295f490a40b97f3d18dd4c4f1c8936c233b612
558 # Parent ee295f490a40b97f3d18dd4c4f1c8936c233b612
560 2
559 2
561
560
562 diff --git a/a b/b
561 diff --git a/a b/b
563 rename from a
562 rename from a
564 rename to b
563 rename to b
565
564
566 Test simple origin(), with and without args
565 Test simple origin(), with and without args
567 $ hg log -r 'origin()'
566 $ hg log -r 'origin()'
568 changeset: 1:5d205f8b35b6
567 changeset: 1:5d205f8b35b6
569 user: bar
568 user: bar
570 date: Thu Jan 01 00:00:00 1970 +0000
569 date: Thu Jan 01 00:00:00 1970 +0000
571 summary: 1
570 summary: 1
572
571
573 changeset: 2:5c095ad7e90f
572 changeset: 2:5c095ad7e90f
574 user: test
573 user: test
575 date: Thu Jan 01 00:00:00 1970 +0000
574 date: Thu Jan 01 00:00:00 1970 +0000
576 summary: 2
575 summary: 2
577
576
578 changeset: 3:4c60f11aa304
577 changeset: 3:4c60f11aa304
579 user: baz
578 user: baz
580 date: Thu Jan 01 00:00:00 1970 +0000
579 date: Thu Jan 01 00:00:00 1970 +0000
581 summary: 3
580 summary: 3
582
581
583 changeset: 4:9c233e8e184d
582 changeset: 4:9c233e8e184d
584 user: test
583 user: test
585 date: Thu Jan 01 00:00:00 1970 +0000
584 date: Thu Jan 01 00:00:00 1970 +0000
586 summary: 4
585 summary: 4
587
586
588 changeset: 5:97f8bfe72746
587 changeset: 5:97f8bfe72746
589 branch: stable
588 branch: stable
590 parent: 3:4c60f11aa304
589 parent: 3:4c60f11aa304
591 user: test
590 user: test
592 date: Thu Jan 01 00:00:00 1970 +0000
591 date: Thu Jan 01 00:00:00 1970 +0000
593 summary: 5
592 summary: 5
594
593
595 $ hg log -r 'origin(7)'
594 $ hg log -r 'origin(7)'
596 changeset: 2:5c095ad7e90f
595 changeset: 2:5c095ad7e90f
597 user: test
596 user: test
598 date: Thu Jan 01 00:00:00 1970 +0000
597 date: Thu Jan 01 00:00:00 1970 +0000
599 summary: 2
598 summary: 2
600
599
601 Now transplant a graft to test following through copies
600 Now transplant a graft to test following through copies
602 $ hg up -q 0
601 $ hg up -q 0
603 $ hg branch -q dev
602 $ hg branch -q dev
604 $ hg ci -qm "dev branch"
603 $ hg ci -qm "dev branch"
605 $ hg --config extensions.transplant= transplant -q 7
604 $ hg --config extensions.transplant= transplant -q 7
606 $ hg log -r 'origin(.)'
605 $ hg log -r 'origin(.)'
607 changeset: 2:5c095ad7e90f
606 changeset: 2:5c095ad7e90f
608 user: test
607 user: test
609 date: Thu Jan 01 00:00:00 1970 +0000
608 date: Thu Jan 01 00:00:00 1970 +0000
610 summary: 2
609 summary: 2
611
610
612 Test that the graft and transplant markers in extra are converted, allowing
611 Test that the graft and transplant markers in extra are converted, allowing
613 origin() to still work. Note that these recheck the immediately preceeding two
612 origin() to still work. Note that these recheck the immediately preceeding two
614 tests.
613 tests.
615 $ hg --quiet --config extensions.convert= --config convert.hg.saverev=True convert . ../converted
614 $ hg --quiet --config extensions.convert= --config convert.hg.saverev=True convert . ../converted
616
615
617 The graft case
616 The graft case
618 $ hg -R ../converted log -r 7 --template "{rev}: {node}\n{join(extras, '\n')}\n"
617 $ hg -R ../converted log -r 7 --template "{rev}: {node}\n{join(extras, '\n')}\n"
619 7: 7ae846e9111fc8f57745634250c7b9ac0a60689b
618 7: 7ae846e9111fc8f57745634250c7b9ac0a60689b
620 branch=default
619 branch=default
621 convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
620 convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
622 source=e0213322b2c1a5d5d236c74e79666441bee67a7d
621 source=e0213322b2c1a5d5d236c74e79666441bee67a7d
623 $ hg -R ../converted log -r 'origin(7)'
622 $ hg -R ../converted log -r 'origin(7)'
624 changeset: 2:e0213322b2c1
623 changeset: 2:e0213322b2c1
625 user: test
624 user: test
626 date: Thu Jan 01 00:00:00 1970 +0000
625 date: Thu Jan 01 00:00:00 1970 +0000
627 summary: 2
626 summary: 2
628
627
629 Test that template correctly expands more than one 'extra' (issue4362), and that
628 Test that template correctly expands more than one 'extra' (issue4362), and that
630 'intermediate-source' is converted.
629 'intermediate-source' is converted.
631 $ hg -R ../converted log -r 13 --template "{extras % ' Extra: {extra}\n'}"
630 $ hg -R ../converted log -r 13 --template "{extras % ' Extra: {extra}\n'}"
632 Extra: branch=default
631 Extra: branch=default
633 Extra: convert_revision=7a4785234d87ec1aa420ed6b11afe40fa73e12a9
632 Extra: convert_revision=7a4785234d87ec1aa420ed6b11afe40fa73e12a9
634 Extra: intermediate-source=7ae846e9111fc8f57745634250c7b9ac0a60689b
633 Extra: intermediate-source=7ae846e9111fc8f57745634250c7b9ac0a60689b
635 Extra: source=e0213322b2c1a5d5d236c74e79666441bee67a7d
634 Extra: source=e0213322b2c1a5d5d236c74e79666441bee67a7d
636
635
637 The transplant case
636 The transplant case
638 $ hg -R ../converted log -r tip --template "{rev}: {node}\n{join(extras, '\n')}\n"
637 $ hg -R ../converted log -r tip --template "{rev}: {node}\n{join(extras, '\n')}\n"
639 21: fbb6c5cc81002f2b4b49c9d731404688bcae5ade
638 21: fbb6c5cc81002f2b4b49c9d731404688bcae5ade
640 branch=dev
639 branch=dev
641 convert_revision=7e61b508e709a11d28194a5359bc3532d910af21
640 convert_revision=7e61b508e709a11d28194a5359bc3532d910af21
642 transplant_source=z\xe8F\xe9\x11\x1f\xc8\xf5wEcBP\xc7\xb9\xac\n`h\x9b
641 transplant_source=z\xe8F\xe9\x11\x1f\xc8\xf5wEcBP\xc7\xb9\xac\n`h\x9b
643 $ hg -R ../converted log -r 'origin(tip)'
642 $ hg -R ../converted log -r 'origin(tip)'
644 changeset: 2:e0213322b2c1
643 changeset: 2:e0213322b2c1
645 user: test
644 user: test
646 date: Thu Jan 01 00:00:00 1970 +0000
645 date: Thu Jan 01 00:00:00 1970 +0000
647 summary: 2
646 summary: 2
648
647
649
648
650 Test simple destination
649 Test simple destination
651 $ hg log -r 'destination()'
650 $ hg log -r 'destination()'
652 changeset: 7:ef0ef43d49e7
651 changeset: 7:ef0ef43d49e7
653 parent: 0:68795b066622
652 parent: 0:68795b066622
654 user: foo
653 user: foo
655 date: Thu Jan 01 00:00:00 1970 +0000
654 date: Thu Jan 01 00:00:00 1970 +0000
656 summary: 2
655 summary: 2
657
656
658 changeset: 8:6b9e5368ca4e
657 changeset: 8:6b9e5368ca4e
659 user: bar
658 user: bar
660 date: Thu Jan 01 00:00:00 1970 +0000
659 date: Thu Jan 01 00:00:00 1970 +0000
661 summary: 1
660 summary: 1
662
661
663 changeset: 9:1905859650ec
662 changeset: 9:1905859650ec
664 user: test
663 user: test
665 date: Thu Jan 01 00:00:00 1970 +0000
664 date: Thu Jan 01 00:00:00 1970 +0000
666 summary: 5
665 summary: 5
667
666
668 changeset: 10:52dc0b4c6907
667 changeset: 10:52dc0b4c6907
669 user: test
668 user: test
670 date: Thu Jan 01 00:00:00 1970 +0000
669 date: Thu Jan 01 00:00:00 1970 +0000
671 summary: 4
670 summary: 4
672
671
673 changeset: 11:882b35362a6b
672 changeset: 11:882b35362a6b
674 user: test
673 user: test
675 date: Thu Jan 01 00:00:00 1970 +0000
674 date: Thu Jan 01 00:00:00 1970 +0000
676 summary: 3
675 summary: 3
677
676
678 changeset: 13:7a4785234d87
677 changeset: 13:7a4785234d87
679 user: foo
678 user: foo
680 date: Thu Jan 01 00:00:00 1970 +0000
679 date: Thu Jan 01 00:00:00 1970 +0000
681 summary: 2
680 summary: 2
682
681
683 changeset: 14:0c921c65ef1e
682 changeset: 14:0c921c65ef1e
684 parent: 1:5d205f8b35b6
683 parent: 1:5d205f8b35b6
685 user: foo
684 user: foo
686 date: Thu Jan 01 00:00:00 1970 +0000
685 date: Thu Jan 01 00:00:00 1970 +0000
687 summary: 3
686 summary: 3
688
687
689 changeset: 17:f67661df0c48
688 changeset: 17:f67661df0c48
690 user: bar
689 user: bar
691 date: Thu Jan 01 00:00:00 1970 +0000
690 date: Thu Jan 01 00:00:00 1970 +0000
692 summary: 1
691 summary: 1
693
692
694 changeset: 19:9627f653b421
693 changeset: 19:9627f653b421
695 user: test
694 user: test
696 date: Thu Jan 01 00:00:00 1970 +0000
695 date: Thu Jan 01 00:00:00 1970 +0000
697 summary: 2
696 summary: 2
698
697
699 changeset: 21:7e61b508e709
698 changeset: 21:7e61b508e709
700 branch: dev
699 branch: dev
701 tag: tip
700 tag: tip
702 user: foo
701 user: foo
703 date: Thu Jan 01 00:00:00 1970 +0000
702 date: Thu Jan 01 00:00:00 1970 +0000
704 summary: 2
703 summary: 2
705
704
706 $ hg log -r 'destination(2)'
705 $ hg log -r 'destination(2)'
707 changeset: 7:ef0ef43d49e7
706 changeset: 7:ef0ef43d49e7
708 parent: 0:68795b066622
707 parent: 0:68795b066622
709 user: foo
708 user: foo
710 date: Thu Jan 01 00:00:00 1970 +0000
709 date: Thu Jan 01 00:00:00 1970 +0000
711 summary: 2
710 summary: 2
712
711
713 changeset: 13:7a4785234d87
712 changeset: 13:7a4785234d87
714 user: foo
713 user: foo
715 date: Thu Jan 01 00:00:00 1970 +0000
714 date: Thu Jan 01 00:00:00 1970 +0000
716 summary: 2
715 summary: 2
717
716
718 changeset: 19:9627f653b421
717 changeset: 19:9627f653b421
719 user: test
718 user: test
720 date: Thu Jan 01 00:00:00 1970 +0000
719 date: Thu Jan 01 00:00:00 1970 +0000
721 summary: 2
720 summary: 2
722
721
723 changeset: 21:7e61b508e709
722 changeset: 21:7e61b508e709
724 branch: dev
723 branch: dev
725 tag: tip
724 tag: tip
726 user: foo
725 user: foo
727 date: Thu Jan 01 00:00:00 1970 +0000
726 date: Thu Jan 01 00:00:00 1970 +0000
728 summary: 2
727 summary: 2
729
728
730 Transplants of grafts can find a destination...
729 Transplants of grafts can find a destination...
731 $ hg log -r 'destination(7)'
730 $ hg log -r 'destination(7)'
732 changeset: 21:7e61b508e709
731 changeset: 21:7e61b508e709
733 branch: dev
732 branch: dev
734 tag: tip
733 tag: tip
735 user: foo
734 user: foo
736 date: Thu Jan 01 00:00:00 1970 +0000
735 date: Thu Jan 01 00:00:00 1970 +0000
737 summary: 2
736 summary: 2
738
737
739 ... grafts of grafts unfortunately can't
738 ... grafts of grafts unfortunately can't
740 $ hg graft -q 13 --debug
739 $ hg graft -q 13 --debug
741 scanning for duplicate grafts
740 scanning for duplicate grafts
742 grafting 13:7a4785234d87 "2"
741 grafting 13:7a4785234d87 "2"
743 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
742 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
744 on local side:
743 on local side:
745 src: 'a' -> dst: 'b' *
744 src: 'a' -> dst: 'b' *
746 on remote side:
745 on remote side:
747 src: 'a' -> dst: 'b' *
746 src: 'a' -> dst: 'b' *
748 checking for directory renames
747 checking for directory renames
749 resolving manifests
748 resolving manifests
750 branchmerge: True, force: True, partial: False
749 branchmerge: True, force: True, partial: False
751 ancestor: b592ea63bb0c, local: 7e61b508e709+, remote: 7a4785234d87
750 ancestor: b592ea63bb0c, local: 7e61b508e709+, remote: 7a4785234d87
752 starting 4 threads for background file closing (?)
751 starting 4 threads for background file closing (?)
753 nothing to commit, clearing merge state
752 nothing to commit, clearing merge state
754 note: graft of 13:7a4785234d87 created no changes to commit
753 note: graft of 13:7a4785234d87 created no changes to commit
755 $ hg log -r 'destination(13)'
754 $ hg log -r 'destination(13)'
756 All copies of a cset
755 All copies of a cset
757 $ hg log -r 'origin(13) or destination(origin(13))'
756 $ hg log -r 'origin(13) or destination(origin(13))'
758 changeset: 2:5c095ad7e90f
757 changeset: 2:5c095ad7e90f
759 user: test
758 user: test
760 date: Thu Jan 01 00:00:00 1970 +0000
759 date: Thu Jan 01 00:00:00 1970 +0000
761 summary: 2
760 summary: 2
762
761
763 changeset: 7:ef0ef43d49e7
762 changeset: 7:ef0ef43d49e7
764 parent: 0:68795b066622
763 parent: 0:68795b066622
765 user: foo
764 user: foo
766 date: Thu Jan 01 00:00:00 1970 +0000
765 date: Thu Jan 01 00:00:00 1970 +0000
767 summary: 2
766 summary: 2
768
767
769 changeset: 13:7a4785234d87
768 changeset: 13:7a4785234d87
770 user: foo
769 user: foo
771 date: Thu Jan 01 00:00:00 1970 +0000
770 date: Thu Jan 01 00:00:00 1970 +0000
772 summary: 2
771 summary: 2
773
772
774 changeset: 19:9627f653b421
773 changeset: 19:9627f653b421
775 user: test
774 user: test
776 date: Thu Jan 01 00:00:00 1970 +0000
775 date: Thu Jan 01 00:00:00 1970 +0000
777 summary: 2
776 summary: 2
778
777
779 changeset: 21:7e61b508e709
778 changeset: 21:7e61b508e709
780 branch: dev
779 branch: dev
781 tag: tip
780 tag: tip
782 user: foo
781 user: foo
783 date: Thu Jan 01 00:00:00 1970 +0000
782 date: Thu Jan 01 00:00:00 1970 +0000
784 summary: 2
783 summary: 2
785
784
786
785
787 graft skips ancestors
786 graft skips ancestors
788
787
789 $ hg graft 21 3
788 $ hg graft 21 3
790 skipping ancestor revision 21:7e61b508e709
789 skipping ancestor revision 21:7e61b508e709
791 grafting 3:4c60f11aa304 "3"
790 grafting 3:4c60f11aa304 "3"
792 merging b and c to c
791 merging b and c to c
793
792
794 graft with --force (still doesn't graft merges)
793 graft with --force (still doesn't graft merges)
795
794
796 $ hg graft 19 0 6
795 $ hg graft 19 0 6
797 skipping ungraftable merge revision 6
796 skipping ungraftable merge revision 6
798 skipping ancestor revision 0:68795b066622
797 skipping ancestor revision 0:68795b066622
799 grafting 19:9627f653b421 "2"
798 grafting 19:9627f653b421 "2"
800 merging b
799 merging b
801 note: graft of 19:9627f653b421 created no changes to commit
800 note: graft of 19:9627f653b421 created no changes to commit
802 $ hg graft 19 0 6 --force
801 $ hg graft 19 0 6 --force
803 skipping ungraftable merge revision 6
802 skipping ungraftable merge revision 6
804 grafting 19:9627f653b421 "2"
803 grafting 19:9627f653b421 "2"
805 merging b
804 merging b
806 note: graft of 19:9627f653b421 created no changes to commit
805 note: graft of 19:9627f653b421 created no changes to commit
807 grafting 0:68795b066622 "0"
806 grafting 0:68795b066622 "0"
808
807
809 graft --force after backout. Do the backout with graft too, to make
808 graft --force after backout. Do the backout with graft too, to make
810 sure we support issue6248.
809 sure we support issue6248.
811
810
812 $ echo abc > a
811 $ echo abc > a
813 $ hg ci -m 24
812 $ hg ci -m 24
814 $ hg graft --base . -r ".^" --no-commit
813 $ hg graft --base . -r ".^" --no-commit
815 grafting 23:b1cac6de36a9 "0"
814 grafting 23:b1cac6de36a9 "0"
816 $ hg commit -m 'Backed out changeset 2e7ea477be26'
815 $ hg commit -m 'Backed out changeset 2e7ea477be26'
817 $ hg graft 24
816 $ hg graft 24
818 skipping ancestor revision 24:2e7ea477be26
817 skipping ancestor revision 24:2e7ea477be26
819 [255]
818 [255]
820 $ hg graft 24 --force
819 $ hg graft 24 --force
821 grafting 24:2e7ea477be26 "24"
820 grafting 24:2e7ea477be26 "24"
822 merging a
821 merging a
823 $ cat a
822 $ cat a
824 abc
823 abc
825
824
826 graft --continue after --force
825 graft --continue after --force
827
826
828 $ echo def > a
827 $ echo def > a
829 $ hg ci -m 27
828 $ hg ci -m 27
830 $ hg graft 24 --force --tool internal:fail
829 $ hg graft 24 --force --tool internal:fail
831 grafting 24:2e7ea477be26 "24"
830 grafting 24:2e7ea477be26 "24"
832 abort: unresolved conflicts, can't continue
831 abort: unresolved conflicts, can't continue
833 (use 'hg resolve' and 'hg graft --continue')
832 (use 'hg resolve' and 'hg graft --continue')
834 [1]
833 [1]
835 $ hg resolve --all
834 $ hg resolve --all
836 merging a
835 merging a
837 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
836 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
838 [1]
837 [1]
839 $ echo abc > a
838 $ echo abc > a
840 $ hg resolve -m a
839 $ hg resolve -m a
841 (no more unresolved files)
840 (no more unresolved files)
842 continue: hg graft --continue
841 continue: hg graft --continue
843 $ hg graft -c
842 $ hg graft -c
844 grafting 24:2e7ea477be26 "24"
843 grafting 24:2e7ea477be26 "24"
845 $ cat a
844 $ cat a
846 abc
845 abc
847
846
848 graft --continue after --base with conflits
847 graft --continue after --base with conflits
849
848
850 $ echo base > d
849 $ echo base > d
851 $ hg ci -m _
850 $ hg ci -m _
852 $ hg graft -r 6
851 $ hg graft -r 6
853 skipping ungraftable merge revision 6
852 skipping ungraftable merge revision 6
854 [255]
853 [255]
855 $ hg graft -r 6 --base 5
854 $ hg graft -r 6 --base 5
856 grafting 6:25a2b029d3ae "6"
855 grafting 6:25a2b029d3ae "6"
857 merging d
856 merging d
857 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
858 merging e
858 merging e
859 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
860 abort: unresolved conflicts, can't continue
859 abort: unresolved conflicts, can't continue
861 (use 'hg resolve' and 'hg graft --continue')
860 (use 'hg resolve' and 'hg graft --continue')
862 [1]
861 [1]
863 $ echo a > d && hg resolve -qm
862 $ echo a > d && hg resolve -qm
864 continue: hg graft --continue
863 continue: hg graft --continue
865 $ hg graft --continue
864 $ hg graft --continue
866 grafting 6:25a2b029d3ae "6"
865 grafting 6:25a2b029d3ae "6"
867
866
868 Continue testing same origin policy, using revision numbers from test above
867 Continue testing same origin policy, using revision numbers from test above
869 but do some destructive editing of the repo:
868 but do some destructive editing of the repo:
870
869
871 $ hg up -qC 7
870 $ hg up -qC 7
872 $ hg tag -l -r 13 tmp
871 $ hg tag -l -r 13 tmp
873 $ hg --config extensions.strip= strip 2
872 $ hg --config extensions.strip= strip 2
874 saved backup bundle to $TESTTMP/a/.hg/strip-backup/5c095ad7e90f-d323a1e4-backup.hg
873 saved backup bundle to $TESTTMP/a/.hg/strip-backup/5c095ad7e90f-d323a1e4-backup.hg
875 $ hg graft tmp
874 $ hg graft tmp
876 skipping already grafted revision 8:7a4785234d87 (2:ef0ef43d49e7 also has unknown origin 5c095ad7e90f)
875 skipping already grafted revision 8:7a4785234d87 (2:ef0ef43d49e7 also has unknown origin 5c095ad7e90f)
877 [255]
876 [255]
878
877
879 Empty graft
878 Empty graft
880
879
881 $ hg up -qr 22
880 $ hg up -qr 22
882 $ hg tag -f something
881 $ hg tag -f something
883 $ hg graft -qr 23
882 $ hg graft -qr 23
884 $ hg graft -f 23
883 $ hg graft -f 23
885 grafting 23:72d9c7c75bcc "24"
884 grafting 23:72d9c7c75bcc "24"
886 note: graft of 23:72d9c7c75bcc created no changes to commit
885 note: graft of 23:72d9c7c75bcc created no changes to commit
887
886
888 $ cd ..
887 $ cd ..
889
888
890 Graft to duplicate a commit
889 Graft to duplicate a commit
891
890
892 $ hg init graftsibling
891 $ hg init graftsibling
893 $ cd graftsibling
892 $ cd graftsibling
894 $ touch a
893 $ touch a
895 $ hg commit -qAm a
894 $ hg commit -qAm a
896 $ touch b
895 $ touch b
897 $ hg commit -qAm b
896 $ hg commit -qAm b
898 $ hg log -G -T '{rev}\n'
897 $ hg log -G -T '{rev}\n'
899 @ 1
898 @ 1
900 |
899 |
901 o 0
900 o 0
902
901
903 $ hg up -q 0
902 $ hg up -q 0
904 $ hg graft -r 1
903 $ hg graft -r 1
905 grafting 1:0e067c57feba "b" (tip)
904 grafting 1:0e067c57feba "b" (tip)
906 $ hg log -G -T '{rev}\n'
905 $ hg log -G -T '{rev}\n'
907 @ 2
906 @ 2
908 |
907 |
909 | o 1
908 | o 1
910 |/
909 |/
911 o 0
910 o 0
912
911
913 Graft to duplicate a commit twice
912 Graft to duplicate a commit twice
914
913
915 $ hg up -q 0
914 $ hg up -q 0
916 $ hg graft -r 2
915 $ hg graft -r 2
917 grafting 2:044ec77f6389 "b" (tip)
916 grafting 2:044ec77f6389 "b" (tip)
918 $ hg log -G -T '{rev}\n'
917 $ hg log -G -T '{rev}\n'
919 @ 3
918 @ 3
920 |
919 |
921 | o 2
920 | o 2
922 |/
921 |/
923 | o 1
922 | o 1
924 |/
923 |/
925 o 0
924 o 0
926
925
@@ -1,99 +1,99 b''
1 https://bz.mercurial-scm.org/672
1 https://bz.mercurial-scm.org/672
2
2
3 # 0-2-4
3 # 0-2-4
4 # \ \ \
4 # \ \ \
5 # 1-3-5
5 # 1-3-5
6 #
6 #
7 # rename in #1, content change in #4.
7 # rename in #1, content change in #4.
8
8
9 $ hg init
9 $ hg init
10
10
11 $ touch 1
11 $ touch 1
12 $ touch 2
12 $ touch 2
13 $ hg commit -Am init # 0
13 $ hg commit -Am init # 0
14 adding 1
14 adding 1
15 adding 2
15 adding 2
16
16
17 $ hg rename 1 1a
17 $ hg rename 1 1a
18 $ hg commit -m rename # 1
18 $ hg commit -m rename # 1
19
19
20 $ hg co -C 0
20 $ hg co -C 0
21 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
21 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
22
22
23 $ echo unrelated >> 2
23 $ echo unrelated >> 2
24 $ hg ci -m unrelated1 # 2
24 $ hg ci -m unrelated1 # 2
25 created new head
25 created new head
26
26
27 $ hg merge --debug 1
27 $ hg merge --debug 1
28 unmatched files in other:
28 unmatched files in other:
29 1a
29 1a
30 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
30 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
31 on remote side:
31 on remote side:
32 src: '1' -> dst: '1a'
32 src: '1' -> dst: '1a'
33 checking for directory renames
33 checking for directory renames
34 resolving manifests
34 resolving manifests
35 branchmerge: True, force: False, partial: False
35 branchmerge: True, force: False, partial: False
36 ancestor: 81f4b099af3d, local: c64f439569a9+, remote: c12dcd37c90a
36 ancestor: 81f4b099af3d, local: c64f439569a9+, remote: c12dcd37c90a
37 1: other deleted -> r
37 1: other deleted -> r
38 removing 1
38 removing 1
39 1a: remote created -> g
39 1a: remote created -> g
40 getting 1a
40 getting 1a
41 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
41 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
42 (branch merge, don't forget to commit)
42 (branch merge, don't forget to commit)
43
43
44 $ hg ci -m merge1 # 3
44 $ hg ci -m merge1 # 3
45
45
46 $ hg co -C 2
46 $ hg co -C 2
47 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
47 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
48
48
49 $ echo hello >> 1
49 $ echo hello >> 1
50 $ hg ci -m unrelated2 # 4
50 $ hg ci -m unrelated2 # 4
51 created new head
51 created new head
52
52
53 $ hg co -C 3
53 $ hg co -C 3
54 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
54 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
55
55
56 $ hg merge -y --debug 4
56 $ hg merge -y --debug 4
57 unmatched files in local:
57 unmatched files in local:
58 1a
58 1a
59 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
59 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
60 on local side:
60 on local side:
61 src: '1' -> dst: '1a' *
61 src: '1' -> dst: '1a' *
62 checking for directory renames
62 checking for directory renames
63 resolving manifests
63 resolving manifests
64 branchmerge: True, force: False, partial: False
64 branchmerge: True, force: False, partial: False
65 ancestor: c64f439569a9, local: f4a9cff3cd0b+, remote: 746e9549ea96
65 ancestor: c64f439569a9, local: f4a9cff3cd0b+, remote: 746e9549ea96
66 starting 4 threads for background file closing (?)
66 starting 4 threads for background file closing (?)
67 preserving 1a for resolve of 1a
67 preserving 1a for resolve of 1a
68 1a: local copied/moved from 1 -> m (premerge)
68 1a: local copied/moved from 1 -> m
69 picked tool ':merge' for 1a (binary False symlink False changedelete False)
69 picked tool ':merge' for 1a (binary False symlink False changedelete False)
70 merging 1a and 1 to 1a
70 merging 1a and 1 to 1a
71 my 1a@f4a9cff3cd0b+ other 1@746e9549ea96 ancestor 1@c64f439569a9
71 my 1a@f4a9cff3cd0b+ other 1@746e9549ea96 ancestor 1@c64f439569a9
72 premerge successful
72 premerge successful
73 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
73 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
74 (branch merge, don't forget to commit)
74 (branch merge, don't forget to commit)
75
75
76 $ hg co -C 4
76 $ hg co -C 4
77 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
77 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
78
78
79 $ hg merge -y --debug 3
79 $ hg merge -y --debug 3
80 unmatched files in other:
80 unmatched files in other:
81 1a
81 1a
82 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
82 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
83 on remote side:
83 on remote side:
84 src: '1' -> dst: '1a' *
84 src: '1' -> dst: '1a' *
85 checking for directory renames
85 checking for directory renames
86 resolving manifests
86 resolving manifests
87 branchmerge: True, force: False, partial: False
87 branchmerge: True, force: False, partial: False
88 ancestor: c64f439569a9, local: 746e9549ea96+, remote: f4a9cff3cd0b
88 ancestor: c64f439569a9, local: 746e9549ea96+, remote: f4a9cff3cd0b
89 starting 4 threads for background file closing (?)
89 starting 4 threads for background file closing (?)
90 preserving 1 for resolve of 1a
90 preserving 1 for resolve of 1a
91 removing 1
91 removing 1
92 1a: remote moved from 1 -> m (premerge)
92 1a: remote moved from 1 -> m
93 picked tool ':merge' for 1a (binary False symlink False changedelete False)
93 picked tool ':merge' for 1a (binary False symlink False changedelete False)
94 merging 1 and 1a to 1a
94 merging 1 and 1a to 1a
95 my 1a@746e9549ea96+ other 1a@f4a9cff3cd0b ancestor 1@c64f439569a9
95 my 1a@746e9549ea96+ other 1a@f4a9cff3cd0b ancestor 1@c64f439569a9
96 premerge successful
96 premerge successful
97 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
97 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
98 (branch merge, don't forget to commit)
98 (branch merge, don't forget to commit)
99
99
@@ -1,183 +1,183 b''
1 Check that renames are correctly saved by a commit after a merge
1 Check that renames are correctly saved by a commit after a merge
2
2
3 Test with the merge on 3 having the rename on the local parent
3 Test with the merge on 3 having the rename on the local parent
4
4
5 $ hg init a
5 $ hg init a
6 $ cd a
6 $ cd a
7
7
8 $ echo line1 > foo
8 $ echo line1 > foo
9 $ hg add foo
9 $ hg add foo
10 $ hg ci -m '0: add foo'
10 $ hg ci -m '0: add foo'
11
11
12 $ echo line2 >> foo
12 $ echo line2 >> foo
13 $ hg ci -m '1: change foo'
13 $ hg ci -m '1: change foo'
14
14
15 $ hg up -C 0
15 $ hg up -C 0
16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17
17
18 $ hg mv foo bar
18 $ hg mv foo bar
19 $ rm bar
19 $ rm bar
20 $ echo line0 > bar
20 $ echo line0 > bar
21 $ echo line1 >> bar
21 $ echo line1 >> bar
22 $ hg ci -m '2: mv foo bar; change bar'
22 $ hg ci -m '2: mv foo bar; change bar'
23 created new head
23 created new head
24
24
25 $ hg merge 1
25 $ hg merge 1
26 merging bar and foo to bar
26 merging bar and foo to bar
27 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
27 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
28 (branch merge, don't forget to commit)
28 (branch merge, don't forget to commit)
29
29
30 $ cat bar
30 $ cat bar
31 line0
31 line0
32 line1
32 line1
33 line2
33 line2
34
34
35 $ hg ci -m '3: merge with local rename'
35 $ hg ci -m '3: merge with local rename'
36
36
37 $ hg debugindex bar
37 $ hg debugindex bar
38 rev linkrev nodeid p1 p2
38 rev linkrev nodeid p1 p2
39 0 2 d35118874825 000000000000 000000000000
39 0 2 d35118874825 000000000000 000000000000
40 1 3 5345f5ab8abd 000000000000 d35118874825
40 1 3 5345f5ab8abd 000000000000 d35118874825
41
41
42 $ hg debugrename bar
42 $ hg debugrename bar
43 bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2
43 bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2
44
44
45 $ hg debugindex foo
45 $ hg debugindex foo
46 rev linkrev nodeid p1 p2
46 rev linkrev nodeid p1 p2
47 0 0 690b295714ae 000000000000 000000000000
47 0 0 690b295714ae 000000000000 000000000000
48 1 1 9e25c27b8757 690b295714ae 000000000000
48 1 1 9e25c27b8757 690b295714ae 000000000000
49
49
50
50
51 Revert the content change from rev 2:
51 Revert the content change from rev 2:
52
52
53 $ hg up -C 2
53 $ hg up -C 2
54 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 $ rm bar
55 $ rm bar
56 $ echo line1 > bar
56 $ echo line1 > bar
57 $ hg ci -m '4: revert content change from rev 2'
57 $ hg ci -m '4: revert content change from rev 2'
58 created new head
58 created new head
59
59
60 $ hg log --template '{rev}:{node|short} {parents}\n'
60 $ hg log --template '{rev}:{node|short} {parents}\n'
61 4:2263c1be0967 2:0f2ff26688b9
61 4:2263c1be0967 2:0f2ff26688b9
62 3:0555950ead28 2:0f2ff26688b9 1:5cd961e4045d
62 3:0555950ead28 2:0f2ff26688b9 1:5cd961e4045d
63 2:0f2ff26688b9 0:2665aaee66e9
63 2:0f2ff26688b9 0:2665aaee66e9
64 1:5cd961e4045d
64 1:5cd961e4045d
65 0:2665aaee66e9
65 0:2665aaee66e9
66
66
67 This should use bar@rev2 as the ancestor:
67 This should use bar@rev2 as the ancestor:
68
68
69 $ hg --debug merge 3
69 $ hg --debug merge 3
70 resolving manifests
70 resolving manifests
71 branchmerge: True, force: False, partial: False
71 branchmerge: True, force: False, partial: False
72 ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28
72 ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28
73 starting 4 threads for background file closing (?)
73 starting 4 threads for background file closing (?)
74 preserving bar for resolve of bar
74 preserving bar for resolve of bar
75 bar: versions differ -> m (premerge)
75 bar: versions differ -> m
76 picked tool ':merge' for bar (binary False symlink False changedelete False)
76 picked tool ':merge' for bar (binary False symlink False changedelete False)
77 merging bar
77 merging bar
78 my bar@2263c1be0967+ other bar@0555950ead28 ancestor bar@0f2ff26688b9
78 my bar@2263c1be0967+ other bar@0555950ead28 ancestor bar@0f2ff26688b9
79 premerge successful
79 premerge successful
80 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
80 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
81 (branch merge, don't forget to commit)
81 (branch merge, don't forget to commit)
82
82
83 $ cat bar
83 $ cat bar
84 line1
84 line1
85 line2
85 line2
86
86
87 $ hg ci -m '5: merge'
87 $ hg ci -m '5: merge'
88
88
89 $ hg debugindex bar
89 $ hg debugindex bar
90 rev linkrev nodeid p1 p2
90 rev linkrev nodeid p1 p2
91 0 2 d35118874825 000000000000 000000000000
91 0 2 d35118874825 000000000000 000000000000
92 1 3 5345f5ab8abd 000000000000 d35118874825
92 1 3 5345f5ab8abd 000000000000 d35118874825
93 2 4 ff4b45017382 d35118874825 000000000000
93 2 4 ff4b45017382 d35118874825 000000000000
94 3 5 3701b4893544 ff4b45017382 5345f5ab8abd
94 3 5 3701b4893544 ff4b45017382 5345f5ab8abd
95
95
96
96
97 Same thing, but with the merge on 3 having the rename
97 Same thing, but with the merge on 3 having the rename
98 on the remote parent:
98 on the remote parent:
99
99
100 $ cd ..
100 $ cd ..
101 $ hg clone -U -r 1 -r 2 a b
101 $ hg clone -U -r 1 -r 2 a b
102 adding changesets
102 adding changesets
103 adding manifests
103 adding manifests
104 adding file changes
104 adding file changes
105 added 3 changesets with 3 changes to 2 files (+1 heads)
105 added 3 changesets with 3 changes to 2 files (+1 heads)
106 new changesets 2665aaee66e9:0f2ff26688b9
106 new changesets 2665aaee66e9:0f2ff26688b9
107 $ cd b
107 $ cd b
108
108
109 $ hg up -C 1
109 $ hg up -C 1
110 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
111
111
112 $ hg merge 2
112 $ hg merge 2
113 merging foo and bar to bar
113 merging foo and bar to bar
114 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
114 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
115 (branch merge, don't forget to commit)
115 (branch merge, don't forget to commit)
116
116
117 $ cat bar
117 $ cat bar
118 line0
118 line0
119 line1
119 line1
120 line2
120 line2
121
121
122 $ hg ci -m '3: merge with remote rename'
122 $ hg ci -m '3: merge with remote rename'
123
123
124 $ hg debugindex bar
124 $ hg debugindex bar
125 rev linkrev nodeid p1 p2
125 rev linkrev nodeid p1 p2
126 0 2 d35118874825 000000000000 000000000000
126 0 2 d35118874825 000000000000 000000000000
127 1 3 5345f5ab8abd 000000000000 d35118874825
127 1 3 5345f5ab8abd 000000000000 d35118874825
128
128
129 $ hg debugrename bar
129 $ hg debugrename bar
130 bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2
130 bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2
131
131
132 $ hg debugindex foo
132 $ hg debugindex foo
133 rev linkrev nodeid p1 p2
133 rev linkrev nodeid p1 p2
134 0 0 690b295714ae 000000000000 000000000000
134 0 0 690b295714ae 000000000000 000000000000
135 1 1 9e25c27b8757 690b295714ae 000000000000
135 1 1 9e25c27b8757 690b295714ae 000000000000
136
136
137
137
138 Revert the content change from rev 2:
138 Revert the content change from rev 2:
139
139
140 $ hg up -C 2
140 $ hg up -C 2
141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 $ rm bar
142 $ rm bar
143 $ echo line1 > bar
143 $ echo line1 > bar
144 $ hg ci -m '4: revert content change from rev 2'
144 $ hg ci -m '4: revert content change from rev 2'
145 created new head
145 created new head
146
146
147 $ hg log --template '{rev}:{node|short} {parents}\n'
147 $ hg log --template '{rev}:{node|short} {parents}\n'
148 4:2263c1be0967 2:0f2ff26688b9
148 4:2263c1be0967 2:0f2ff26688b9
149 3:3ffa6b9e35f0 1:5cd961e4045d 2:0f2ff26688b9
149 3:3ffa6b9e35f0 1:5cd961e4045d 2:0f2ff26688b9
150 2:0f2ff26688b9 0:2665aaee66e9
150 2:0f2ff26688b9 0:2665aaee66e9
151 1:5cd961e4045d
151 1:5cd961e4045d
152 0:2665aaee66e9
152 0:2665aaee66e9
153
153
154 This should use bar@rev2 as the ancestor:
154 This should use bar@rev2 as the ancestor:
155
155
156 $ hg --debug merge 3
156 $ hg --debug merge 3
157 resolving manifests
157 resolving manifests
158 branchmerge: True, force: False, partial: False
158 branchmerge: True, force: False, partial: False
159 ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0
159 ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0
160 starting 4 threads for background file closing (?)
160 starting 4 threads for background file closing (?)
161 preserving bar for resolve of bar
161 preserving bar for resolve of bar
162 bar: versions differ -> m (premerge)
162 bar: versions differ -> m
163 picked tool ':merge' for bar (binary False symlink False changedelete False)
163 picked tool ':merge' for bar (binary False symlink False changedelete False)
164 merging bar
164 merging bar
165 my bar@2263c1be0967+ other bar@3ffa6b9e35f0 ancestor bar@0f2ff26688b9
165 my bar@2263c1be0967+ other bar@3ffa6b9e35f0 ancestor bar@0f2ff26688b9
166 premerge successful
166 premerge successful
167 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
167 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
168 (branch merge, don't forget to commit)
168 (branch merge, don't forget to commit)
169
169
170 $ cat bar
170 $ cat bar
171 line1
171 line1
172 line2
172 line2
173
173
174 $ hg ci -m '5: merge'
174 $ hg ci -m '5: merge'
175
175
176 $ hg debugindex bar
176 $ hg debugindex bar
177 rev linkrev nodeid p1 p2
177 rev linkrev nodeid p1 p2
178 0 2 d35118874825 000000000000 000000000000
178 0 2 d35118874825 000000000000 000000000000
179 1 3 5345f5ab8abd 000000000000 d35118874825
179 1 3 5345f5ab8abd 000000000000 d35118874825
180 2 4 ff4b45017382 d35118874825 000000000000
180 2 4 ff4b45017382 d35118874825 000000000000
181 3 5 3701b4893544 ff4b45017382 5345f5ab8abd
181 3 5 3701b4893544 ff4b45017382 5345f5ab8abd
182
182
183 $ cd ..
183 $ cd ..
@@ -1,939 +1,938 b''
1 #testcases old newfilenode
1 #testcases old newfilenode
2
2
3 #if newfilenode
3 #if newfilenode
4 Enable the config option
4 Enable the config option
5 ------------------------
5 ------------------------
6
6
7 $ cat >> $HGRCPATH <<EOF
7 $ cat >> $HGRCPATH <<EOF
8 > [experimental]
8 > [experimental]
9 > merge-track-salvaged = True
9 > merge-track-salvaged = True
10 > EOF
10 > EOF
11 #endif
11 #endif
12
12
13 Criss cross merging
13 Criss cross merging
14
14
15 $ hg init criss-cross
15 $ hg init criss-cross
16 $ cd criss-cross
16 $ cd criss-cross
17 $ echo '0 base' > f1
17 $ echo '0 base' > f1
18 $ echo '0 base' > f2
18 $ echo '0 base' > f2
19 $ hg ci -Aqm '0 base'
19 $ hg ci -Aqm '0 base'
20
20
21 $ echo '1 first change' > f1
21 $ echo '1 first change' > f1
22 $ hg ci -m '1 first change f1'
22 $ hg ci -m '1 first change f1'
23
23
24 $ hg up -qr0
24 $ hg up -qr0
25 $ echo '2 first change' > f2
25 $ echo '2 first change' > f2
26 $ hg ci -qm '2 first change f2'
26 $ hg ci -qm '2 first change f2'
27
27
28 $ hg merge -qr 1
28 $ hg merge -qr 1
29 $ hg ci -m '3 merge'
29 $ hg ci -m '3 merge'
30
30
31 $ hg up -qr2
31 $ hg up -qr2
32 $ hg merge -qr1
32 $ hg merge -qr1
33 $ hg ci -qm '4 merge'
33 $ hg ci -qm '4 merge'
34
34
35 $ echo '5 second change' > f1
35 $ echo '5 second change' > f1
36 $ hg ci -m '5 second change f1'
36 $ hg ci -m '5 second change f1'
37
37
38 $ hg up -r3
38 $ hg up -r3
39 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
39 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 $ echo '6 second change' > f2
40 $ echo '6 second change' > f2
41 $ hg ci -m '6 second change f2'
41 $ hg ci -m '6 second change f2'
42
42
43 $ hg log -G
43 $ hg log -G
44 @ changeset: 6:3b08d01b0ab5
44 @ changeset: 6:3b08d01b0ab5
45 | tag: tip
45 | tag: tip
46 | parent: 3:cf89f02107e5
46 | parent: 3:cf89f02107e5
47 | user: test
47 | user: test
48 | date: Thu Jan 01 00:00:00 1970 +0000
48 | date: Thu Jan 01 00:00:00 1970 +0000
49 | summary: 6 second change f2
49 | summary: 6 second change f2
50 |
50 |
51 | o changeset: 5:adfe50279922
51 | o changeset: 5:adfe50279922
52 | | user: test
52 | | user: test
53 | | date: Thu Jan 01 00:00:00 1970 +0000
53 | | date: Thu Jan 01 00:00:00 1970 +0000
54 | | summary: 5 second change f1
54 | | summary: 5 second change f1
55 | |
55 | |
56 | o changeset: 4:7d3e55501ae6
56 | o changeset: 4:7d3e55501ae6
57 | |\ parent: 2:40663881a6dd
57 | |\ parent: 2:40663881a6dd
58 | | | parent: 1:0f6b37dbe527
58 | | | parent: 1:0f6b37dbe527
59 | | | user: test
59 | | | user: test
60 | | | date: Thu Jan 01 00:00:00 1970 +0000
60 | | | date: Thu Jan 01 00:00:00 1970 +0000
61 | | | summary: 4 merge
61 | | | summary: 4 merge
62 | | |
62 | | |
63 o---+ changeset: 3:cf89f02107e5
63 o---+ changeset: 3:cf89f02107e5
64 | | | parent: 2:40663881a6dd
64 | | | parent: 2:40663881a6dd
65 |/ / parent: 1:0f6b37dbe527
65 |/ / parent: 1:0f6b37dbe527
66 | | user: test
66 | | user: test
67 | | date: Thu Jan 01 00:00:00 1970 +0000
67 | | date: Thu Jan 01 00:00:00 1970 +0000
68 | | summary: 3 merge
68 | | summary: 3 merge
69 | |
69 | |
70 | o changeset: 2:40663881a6dd
70 | o changeset: 2:40663881a6dd
71 | | parent: 0:40494bf2444c
71 | | parent: 0:40494bf2444c
72 | | user: test
72 | | user: test
73 | | date: Thu Jan 01 00:00:00 1970 +0000
73 | | date: Thu Jan 01 00:00:00 1970 +0000
74 | | summary: 2 first change f2
74 | | summary: 2 first change f2
75 | |
75 | |
76 o | changeset: 1:0f6b37dbe527
76 o | changeset: 1:0f6b37dbe527
77 |/ user: test
77 |/ user: test
78 | date: Thu Jan 01 00:00:00 1970 +0000
78 | date: Thu Jan 01 00:00:00 1970 +0000
79 | summary: 1 first change f1
79 | summary: 1 first change f1
80 |
80 |
81 o changeset: 0:40494bf2444c
81 o changeset: 0:40494bf2444c
82 user: test
82 user: test
83 date: Thu Jan 01 00:00:00 1970 +0000
83 date: Thu Jan 01 00:00:00 1970 +0000
84 summary: 0 base
84 summary: 0 base
85
85
86
86
87 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor='!'
87 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor='!'
88 note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
88 note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
89 alternatively, use --config merge.preferancestor=40663881a6dd
89 alternatively, use --config merge.preferancestor=40663881a6dd
90 resolving manifests
90 resolving manifests
91 branchmerge: True, force: False, partial: False
91 branchmerge: True, force: False, partial: False
92 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
92 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
93 f1: remote is newer -> g
93 f1: remote is newer -> g
94 getting f1
94 getting f1
95 preserving f2 for resolve of f2
95 preserving f2 for resolve of f2
96 f2: versions differ -> m (premerge)
96 f2: versions differ -> m
97 picked tool ':dump' for f2 (binary False symlink False changedelete False)
97 picked tool ':dump' for f2 (binary False symlink False changedelete False)
98 merging f2
98 merging f2
99 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527
99 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527
100 f2: versions differ -> m (merge)
101 picked tool ':dump' for f2 (binary False symlink False changedelete False)
100 picked tool ':dump' for f2 (binary False symlink False changedelete False)
102 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527
101 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527
103 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
102 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
104 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
103 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
105 [1]
104 [1]
106
105
107 $ f --dump *
106 $ f --dump *
108 f1:
107 f1:
109 >>>
108 >>>
110 5 second change
109 5 second change
111 <<<
110 <<<
112 f2:
111 f2:
113 >>>
112 >>>
114 6 second change
113 6 second change
115 <<<
114 <<<
116 f2.base:
115 f2.base:
117 >>>
116 >>>
118 0 base
117 0 base
119 <<<
118 <<<
120 f2.local:
119 f2.local:
121 >>>
120 >>>
122 6 second change
121 6 second change
123 <<<
122 <<<
124 f2.orig:
123 f2.orig:
125 >>>
124 >>>
126 6 second change
125 6 second change
127 <<<
126 <<<
128 f2.other:
127 f2.other:
129 >>>
128 >>>
130 2 first change
129 2 first change
131 <<<
130 <<<
132
131
133 $ hg up -qC .
132 $ hg up -qC .
134 $ hg merge -v --tool internal:dump 5 --config merge.preferancestor="null 40663881 3b08d"
133 $ hg merge -v --tool internal:dump 5 --config merge.preferancestor="null 40663881 3b08d"
135 note: using 40663881a6dd as ancestor of 3b08d01b0ab5 and adfe50279922
134 note: using 40663881a6dd as ancestor of 3b08d01b0ab5 and adfe50279922
136 alternatively, use --config merge.preferancestor=0f6b37dbe527
135 alternatively, use --config merge.preferancestor=0f6b37dbe527
137 resolving manifests
136 resolving manifests
138 merging f1
137 merging f1
139 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
138 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
140 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
139 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
141 [1]
140 [1]
142
141
143 Redo merge with merge.preferancestor="*" to enable bid merge
142 Redo merge with merge.preferancestor="*" to enable bid merge
144
143
145 $ rm f*
144 $ rm f*
146 $ hg up -qC .
145 $ hg up -qC .
147 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor="*"
146 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor="*"
148 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
147 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
149
148
150 calculating bids for ancestor 0f6b37dbe527
149 calculating bids for ancestor 0f6b37dbe527
151 resolving manifests
150 resolving manifests
152 branchmerge: True, force: False, partial: False
151 branchmerge: True, force: False, partial: False
153 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
152 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
154 f1: remote is newer -> g
153 f1: remote is newer -> g
155 f2: versions differ -> m
154 f2: versions differ -> m
156
155
157 calculating bids for ancestor 40663881a6dd
156 calculating bids for ancestor 40663881a6dd
158 resolving manifests
157 resolving manifests
159 branchmerge: True, force: False, partial: False
158 branchmerge: True, force: False, partial: False
160 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
159 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
161 f1: versions differ -> m
160 f1: versions differ -> m
162 f2: remote unchanged -> k
161 f2: remote unchanged -> k
163
162
164 auction for merging merge bids (2 ancestors)
163 auction for merging merge bids (2 ancestors)
165 list of bids for f1:
164 list of bids for f1:
166 remote is newer -> g
165 remote is newer -> g
167 versions differ -> m
166 versions differ -> m
168 f1: picking 'get' action
167 f1: picking 'get' action
169 list of bids for f2:
168 list of bids for f2:
170 remote unchanged -> k
169 remote unchanged -> k
171 versions differ -> m
170 versions differ -> m
172 f2: picking 'keep' action
171 f2: picking 'keep' action
173 end of auction
172 end of auction
174
173
175 f1: remote is newer -> g
174 f1: remote is newer -> g
176 getting f1
175 getting f1
177 f2: remote unchanged -> k
176 f2: remote unchanged -> k
178 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
177 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 (branch merge, don't forget to commit)
178 (branch merge, don't forget to commit)
180
179
181 $ f --dump *
180 $ f --dump *
182 f1:
181 f1:
183 >>>
182 >>>
184 5 second change
183 5 second change
185 <<<
184 <<<
186 f2:
185 f2:
187 >>>
186 >>>
188 6 second change
187 6 second change
189 <<<
188 <<<
190
189
191
190
192 The other way around:
191 The other way around:
193
192
194 $ hg up -C -r5
193 $ hg up -C -r5
195 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
196 $ hg merge -v --debug --config merge.preferancestor="*"
195 $ hg merge -v --debug --config merge.preferancestor="*"
197 note: merging adfe50279922+ and 3b08d01b0ab5 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
196 note: merging adfe50279922+ and 3b08d01b0ab5 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
198
197
199 calculating bids for ancestor 0f6b37dbe527
198 calculating bids for ancestor 0f6b37dbe527
200 resolving manifests
199 resolving manifests
201 branchmerge: True, force: False, partial: False
200 branchmerge: True, force: False, partial: False
202 ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5
201 ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5
203 f1: remote unchanged -> k
202 f1: remote unchanged -> k
204 f2: versions differ -> m
203 f2: versions differ -> m
205
204
206 calculating bids for ancestor 40663881a6dd
205 calculating bids for ancestor 40663881a6dd
207 resolving manifests
206 resolving manifests
208 branchmerge: True, force: False, partial: False
207 branchmerge: True, force: False, partial: False
209 ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5
208 ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5
210 f1: versions differ -> m
209 f1: versions differ -> m
211 f2: remote is newer -> g
210 f2: remote is newer -> g
212
211
213 auction for merging merge bids (2 ancestors)
212 auction for merging merge bids (2 ancestors)
214 list of bids for f1:
213 list of bids for f1:
215 remote unchanged -> k
214 remote unchanged -> k
216 versions differ -> m
215 versions differ -> m
217 f1: picking 'keep' action
216 f1: picking 'keep' action
218 list of bids for f2:
217 list of bids for f2:
219 remote is newer -> g
218 remote is newer -> g
220 versions differ -> m
219 versions differ -> m
221 f2: picking 'get' action
220 f2: picking 'get' action
222 end of auction
221 end of auction
223
222
224 f2: remote is newer -> g
223 f2: remote is newer -> g
225 getting f2
224 getting f2
226 f1: remote unchanged -> k
225 f1: remote unchanged -> k
227 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 (branch merge, don't forget to commit)
227 (branch merge, don't forget to commit)
229
228
230 $ f --dump *
229 $ f --dump *
231 f1:
230 f1:
232 >>>
231 >>>
233 5 second change
232 5 second change
234 <<<
233 <<<
235 f2:
234 f2:
236 >>>
235 >>>
237 6 second change
236 6 second change
238 <<<
237 <<<
239
238
240 Verify how the output looks and and how verbose it is:
239 Verify how the output looks and and how verbose it is:
241
240
242 $ hg up -qC
241 $ hg up -qC
243 $ hg merge
242 $ hg merge
244 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 (branch merge, don't forget to commit)
244 (branch merge, don't forget to commit)
246
245
247 $ hg up -qC tip
246 $ hg up -qC tip
248 $ hg merge -v
247 $ hg merge -v
249 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
248 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
250
249
251 calculating bids for ancestor 0f6b37dbe527
250 calculating bids for ancestor 0f6b37dbe527
252 resolving manifests
251 resolving manifests
253
252
254 calculating bids for ancestor 40663881a6dd
253 calculating bids for ancestor 40663881a6dd
255 resolving manifests
254 resolving manifests
256
255
257 auction for merging merge bids (2 ancestors)
256 auction for merging merge bids (2 ancestors)
258 f1: picking 'get' action
257 f1: picking 'get' action
259 f2: picking 'keep' action
258 f2: picking 'keep' action
260 end of auction
259 end of auction
261
260
262 getting f1
261 getting f1
263 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 (branch merge, don't forget to commit)
263 (branch merge, don't forget to commit)
265
264
266 $ hg up -qC
265 $ hg up -qC
267 $ hg merge -v --debug --config merge.preferancestor="*"
266 $ hg merge -v --debug --config merge.preferancestor="*"
268 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
267 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
269
268
270 calculating bids for ancestor 0f6b37dbe527
269 calculating bids for ancestor 0f6b37dbe527
271 resolving manifests
270 resolving manifests
272 branchmerge: True, force: False, partial: False
271 branchmerge: True, force: False, partial: False
273 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
272 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
274 f1: remote is newer -> g
273 f1: remote is newer -> g
275 f2: versions differ -> m
274 f2: versions differ -> m
276
275
277 calculating bids for ancestor 40663881a6dd
276 calculating bids for ancestor 40663881a6dd
278 resolving manifests
277 resolving manifests
279 branchmerge: True, force: False, partial: False
278 branchmerge: True, force: False, partial: False
280 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
279 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
281 f1: versions differ -> m
280 f1: versions differ -> m
282 f2: remote unchanged -> k
281 f2: remote unchanged -> k
283
282
284 auction for merging merge bids (2 ancestors)
283 auction for merging merge bids (2 ancestors)
285 list of bids for f1:
284 list of bids for f1:
286 remote is newer -> g
285 remote is newer -> g
287 versions differ -> m
286 versions differ -> m
288 f1: picking 'get' action
287 f1: picking 'get' action
289 list of bids for f2:
288 list of bids for f2:
290 remote unchanged -> k
289 remote unchanged -> k
291 versions differ -> m
290 versions differ -> m
292 f2: picking 'keep' action
291 f2: picking 'keep' action
293 end of auction
292 end of auction
294
293
295 f1: remote is newer -> g
294 f1: remote is newer -> g
296 getting f1
295 getting f1
297 f2: remote unchanged -> k
296 f2: remote unchanged -> k
298 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
299 (branch merge, don't forget to commit)
298 (branch merge, don't forget to commit)
300
299
301 Test the greatest common ancestor returning multiple changesets
300 Test the greatest common ancestor returning multiple changesets
302
301
303 $ hg log -r 'heads(commonancestors(head()))'
302 $ hg log -r 'heads(commonancestors(head()))'
304 changeset: 1:0f6b37dbe527
303 changeset: 1:0f6b37dbe527
305 user: test
304 user: test
306 date: Thu Jan 01 00:00:00 1970 +0000
305 date: Thu Jan 01 00:00:00 1970 +0000
307 summary: 1 first change f1
306 summary: 1 first change f1
308
307
309 changeset: 2:40663881a6dd
308 changeset: 2:40663881a6dd
310 parent: 0:40494bf2444c
309 parent: 0:40494bf2444c
311 user: test
310 user: test
312 date: Thu Jan 01 00:00:00 1970 +0000
311 date: Thu Jan 01 00:00:00 1970 +0000
313 summary: 2 first change f2
312 summary: 2 first change f2
314
313
315
314
316 $ cd ..
315 $ cd ..
317
316
318 http://stackoverflow.com/questions/9350005/how-do-i-specify-a-merge-base-to-use-in-a-hg-merge/9430810
317 http://stackoverflow.com/questions/9350005/how-do-i-specify-a-merge-base-to-use-in-a-hg-merge/9430810
319
318
320 $ hg init ancestor-merging
319 $ hg init ancestor-merging
321 $ cd ancestor-merging
320 $ cd ancestor-merging
322 $ echo a > x
321 $ echo a > x
323 $ hg commit -A -m a x
322 $ hg commit -A -m a x
324 $ hg update -q 0
323 $ hg update -q 0
325 $ echo b >> x
324 $ echo b >> x
326 $ hg commit -m b
325 $ hg commit -m b
327 $ hg update -q 0
326 $ hg update -q 0
328 $ echo c >> x
327 $ echo c >> x
329 $ hg commit -qm c
328 $ hg commit -qm c
330 $ hg update -q 1
329 $ hg update -q 1
331 $ hg merge -q --tool internal:local 2
330 $ hg merge -q --tool internal:local 2
332 $ echo c >> x
331 $ echo c >> x
333 $ hg commit -m bc
332 $ hg commit -m bc
334 $ hg update -q 2
333 $ hg update -q 2
335 $ hg merge -q --tool internal:local 1
334 $ hg merge -q --tool internal:local 1
336 $ echo b >> x
335 $ echo b >> x
337 $ hg commit -qm cb
336 $ hg commit -qm cb
338
337
339 $ hg merge --config merge.preferancestor='!'
338 $ hg merge --config merge.preferancestor='!'
340 note: using 70008a2163f6 as ancestor of 0d355fdef312 and 4b8b546a3eef
339 note: using 70008a2163f6 as ancestor of 0d355fdef312 and 4b8b546a3eef
341 alternatively, use --config merge.preferancestor=b211bbc6eb3c
340 alternatively, use --config merge.preferancestor=b211bbc6eb3c
342 merging x
341 merging x
343 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
342 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
344 (branch merge, don't forget to commit)
343 (branch merge, don't forget to commit)
345 $ cat x
344 $ cat x
346 a
345 a
347 c
346 c
348 b
347 b
349 c
348 c
350
349
351 $ hg up -qC .
350 $ hg up -qC .
352
351
353 $ hg merge --config merge.preferancestor=b211bbc6eb3c
352 $ hg merge --config merge.preferancestor=b211bbc6eb3c
354 note: using b211bbc6eb3c as ancestor of 0d355fdef312 and 4b8b546a3eef
353 note: using b211bbc6eb3c as ancestor of 0d355fdef312 and 4b8b546a3eef
355 alternatively, use --config merge.preferancestor=70008a2163f6
354 alternatively, use --config merge.preferancestor=70008a2163f6
356 merging x
355 merging x
357 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
356 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
358 (branch merge, don't forget to commit)
357 (branch merge, don't forget to commit)
359 $ cat x
358 $ cat x
360 a
359 a
361 b
360 b
362 c
361 c
363 b
362 b
364
363
365 $ hg up -qC .
364 $ hg up -qC .
366
365
367 $ hg merge -v --config merge.preferancestor="*"
366 $ hg merge -v --config merge.preferancestor="*"
368 note: merging 0d355fdef312+ and 4b8b546a3eef using bids from ancestors 70008a2163f6 and b211bbc6eb3c
367 note: merging 0d355fdef312+ and 4b8b546a3eef using bids from ancestors 70008a2163f6 and b211bbc6eb3c
369
368
370 calculating bids for ancestor 70008a2163f6
369 calculating bids for ancestor 70008a2163f6
371 resolving manifests
370 resolving manifests
372
371
373 calculating bids for ancestor b211bbc6eb3c
372 calculating bids for ancestor b211bbc6eb3c
374 resolving manifests
373 resolving manifests
375
374
376 auction for merging merge bids (2 ancestors)
375 auction for merging merge bids (2 ancestors)
377 x: multiple bids for merge action:
376 x: multiple bids for merge action:
378 versions differ -> m
377 versions differ -> m
379 versions differ -> m
378 versions differ -> m
380 x: ambiguous merge - picked m action
379 x: ambiguous merge - picked m action
381 end of auction
380 end of auction
382
381
383 merging x
382 merging x
384 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
383 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
385 (branch merge, don't forget to commit)
384 (branch merge, don't forget to commit)
386 $ cat x
385 $ cat x
387 a
386 a
388 c
387 c
389 b
388 b
390 c
389 c
391
390
392 Verify that the old context ancestor works with / despite preferancestor:
391 Verify that the old context ancestor works with / despite preferancestor:
393
392
394 $ hg log -r 'ancestor(head())' --config merge.preferancestor=1 -T '{rev}\n'
393 $ hg log -r 'ancestor(head())' --config merge.preferancestor=1 -T '{rev}\n'
395 1
394 1
396 $ hg log -r 'ancestor(head())' --config merge.preferancestor=2 -T '{rev}\n'
395 $ hg log -r 'ancestor(head())' --config merge.preferancestor=2 -T '{rev}\n'
397 2
396 2
398 $ hg log -r 'ancestor(head())' --config merge.preferancestor=3 -T '{rev}\n'
397 $ hg log -r 'ancestor(head())' --config merge.preferancestor=3 -T '{rev}\n'
399 1
398 1
400 $ hg log -r 'ancestor(head())' --config merge.preferancestor='1337 * - 2' -T '{rev}\n'
399 $ hg log -r 'ancestor(head())' --config merge.preferancestor='1337 * - 2' -T '{rev}\n'
401 2
400 2
402
401
403 $ cd ..
402 $ cd ..
404
403
405 $ hg init issue5020
404 $ hg init issue5020
406 $ cd issue5020
405 $ cd issue5020
407
406
408 $ echo a > noop
407 $ echo a > noop
409 $ hg ci -qAm initial
408 $ hg ci -qAm initial
410
409
411 $ echo b > noop
410 $ echo b > noop
412 $ hg ci -qAm 'uninteresting change'
411 $ hg ci -qAm 'uninteresting change'
413
412
414 $ hg up -q 0
413 $ hg up -q 0
415 $ mkdir d1
414 $ mkdir d1
416 $ echo a > d1/a
415 $ echo a > d1/a
417 $ echo b > d1/b
416 $ echo b > d1/b
418 $ hg ci -qAm 'add d1/a and d1/b'
417 $ hg ci -qAm 'add d1/a and d1/b'
419
418
420 $ hg merge -q 1
419 $ hg merge -q 1
421 $ hg rm d1/a
420 $ hg rm d1/a
422 $ hg mv -q d1 d2
421 $ hg mv -q d1 d2
423 $ hg ci -qm 'merge while removing d1/a and moving d1/b to d2/b'
422 $ hg ci -qm 'merge while removing d1/a and moving d1/b to d2/b'
424
423
425 $ hg up -q 1
424 $ hg up -q 1
426 $ hg merge -q 2
425 $ hg merge -q 2
427 $ hg ci -qm 'merge (no changes while merging)'
426 $ hg ci -qm 'merge (no changes while merging)'
428 $ hg log -G -T '{rev}:{node|short} {desc}'
427 $ hg log -G -T '{rev}:{node|short} {desc}'
429 @ 4:c0ef19750a22 merge (no changes while merging)
428 @ 4:c0ef19750a22 merge (no changes while merging)
430 |\
429 |\
431 +---o 3:6ca01f7342b9 merge while removing d1/a and moving d1/b to d2/b
430 +---o 3:6ca01f7342b9 merge while removing d1/a and moving d1/b to d2/b
432 | |/
431 | |/
433 | o 2:154e6000f54e add d1/a and d1/b
432 | o 2:154e6000f54e add d1/a and d1/b
434 | |
433 | |
435 o | 1:11b5b303e36c uninteresting change
434 o | 1:11b5b303e36c uninteresting change
436 |/
435 |/
437 o 0:7b54db1ebf33 initial
436 o 0:7b54db1ebf33 initial
438
437
439 $ hg merge 3 --debug
438 $ hg merge 3 --debug
440 note: merging c0ef19750a22+ and 6ca01f7342b9 using bids from ancestors 11b5b303e36c and 154e6000f54e
439 note: merging c0ef19750a22+ and 6ca01f7342b9 using bids from ancestors 11b5b303e36c and 154e6000f54e
441
440
442 calculating bids for ancestor 11b5b303e36c
441 calculating bids for ancestor 11b5b303e36c
443 resolving manifests
442 resolving manifests
444 branchmerge: True, force: False, partial: False
443 branchmerge: True, force: False, partial: False
445 ancestor: 11b5b303e36c, local: c0ef19750a22+, remote: 6ca01f7342b9
444 ancestor: 11b5b303e36c, local: c0ef19750a22+, remote: 6ca01f7342b9
446 d1/a: ancestor missing, remote missing -> kn
445 d1/a: ancestor missing, remote missing -> kn
447 d1/b: ancestor missing, remote missing -> kn
446 d1/b: ancestor missing, remote missing -> kn
448 d2/b: remote created -> g
447 d2/b: remote created -> g
449
448
450 calculating bids for ancestor 154e6000f54e
449 calculating bids for ancestor 154e6000f54e
451 unmatched files in other:
450 unmatched files in other:
452 d2/b
451 d2/b
453 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
452 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
454 on remote side:
453 on remote side:
455 src: 'd1/b' -> dst: 'd2/b'
454 src: 'd1/b' -> dst: 'd2/b'
456 checking for directory renames
455 checking for directory renames
457 discovered dir src: 'd1/' -> dst: 'd2/'
456 discovered dir src: 'd1/' -> dst: 'd2/'
458 resolving manifests
457 resolving manifests
459 branchmerge: True, force: False, partial: False
458 branchmerge: True, force: False, partial: False
460 ancestor: 154e6000f54e, local: c0ef19750a22+, remote: 6ca01f7342b9
459 ancestor: 154e6000f54e, local: c0ef19750a22+, remote: 6ca01f7342b9
461 d1/a: other deleted -> r
460 d1/a: other deleted -> r
462 d1/b: other deleted -> r
461 d1/b: other deleted -> r
463 d2/b: remote created -> g
462 d2/b: remote created -> g
464
463
465 auction for merging merge bids (2 ancestors)
464 auction for merging merge bids (2 ancestors)
466 list of bids for d1/a:
465 list of bids for d1/a:
467 ancestor missing, remote missing -> kn
466 ancestor missing, remote missing -> kn
468 other deleted -> r
467 other deleted -> r
469 d1/a: picking 'keep new' action
468 d1/a: picking 'keep new' action
470 list of bids for d1/b:
469 list of bids for d1/b:
471 ancestor missing, remote missing -> kn
470 ancestor missing, remote missing -> kn
472 other deleted -> r
471 other deleted -> r
473 d1/b: picking 'keep new' action
472 d1/b: picking 'keep new' action
474 list of bids for d2/b:
473 list of bids for d2/b:
475 remote created -> g
474 remote created -> g
476 remote created -> g
475 remote created -> g
477 d2/b: consensus for g
476 d2/b: consensus for g
478 end of auction
477 end of auction
479
478
480 d2/b: remote created -> g
479 d2/b: remote created -> g
481 getting d2/b
480 getting d2/b
482 d1/a: ancestor missing, remote missing -> kn
481 d1/a: ancestor missing, remote missing -> kn
483 d1/b: ancestor missing, remote missing -> kn
482 d1/b: ancestor missing, remote missing -> kn
484 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
485 (branch merge, don't forget to commit)
484 (branch merge, don't forget to commit)
486
485
487
486
488 Check that removal reversion does not go unotified
487 Check that removal reversion does not go unotified
489 ==================================================
488 ==================================================
490
489
491 On a merge, a file can be removed and user can revert that removal. This means
490 On a merge, a file can be removed and user can revert that removal. This means
492 user has made an explicit choice of keeping the file or reverting the removal
491 user has made an explicit choice of keeping the file or reverting the removal
493 even though the merge algo wanted to remove it.
492 even though the merge algo wanted to remove it.
494 Based on this, when we do criss cross merges, merge algorithm should not again
493 Based on this, when we do criss cross merges, merge algorithm should not again
495 choose to remove the file as in one of the merges, user made an explicit choice
494 choose to remove the file as in one of the merges, user made an explicit choice
496 to revert the removal.
495 to revert the removal.
497 Following test cases demonstrate how merge algo does not take in account
496 Following test cases demonstrate how merge algo does not take in account
498 explicit choices made by users to revert the removal and on criss-cross merging
497 explicit choices made by users to revert the removal and on criss-cross merging
499 removes the file again.
498 removes the file again.
500
499
501 "Simple" case where the filenode changes
500 "Simple" case where the filenode changes
502 ----------------------------------------
501 ----------------------------------------
503
502
504 $ cd ..
503 $ cd ..
505 $ hg init criss-cross-merge-reversal-with-update
504 $ hg init criss-cross-merge-reversal-with-update
506 $ cd criss-cross-merge-reversal-with-update
505 $ cd criss-cross-merge-reversal-with-update
507 $ echo the-file > the-file
506 $ echo the-file > the-file
508 $ echo other-file > other-file
507 $ echo other-file > other-file
509 $ hg add the-file other-file
508 $ hg add the-file other-file
510 $ hg ci -m 'root-commit'
509 $ hg ci -m 'root-commit'
511 $ echo foo >> the-file
510 $ echo foo >> the-file
512 $ echo bar >> other-file
511 $ echo bar >> other-file
513 $ hg ci -m 'updating-both-file'
512 $ hg ci -m 'updating-both-file'
514 $ hg up 'desc("root-commit")'
513 $ hg up 'desc("root-commit")'
515 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
514 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
516 $ hg rm the-file
515 $ hg rm the-file
517 $ hg ci -m 'delete-the-file'
516 $ hg ci -m 'delete-the-file'
518 created new head
517 created new head
519 $ hg log -G -T '{node|short} {desc}\n'
518 $ hg log -G -T '{node|short} {desc}\n'
520 @ 7801bc9b9899 delete-the-file
519 @ 7801bc9b9899 delete-the-file
521 |
520 |
522 | o 9b610631ab29 updating-both-file
521 | o 9b610631ab29 updating-both-file
523 |/
522 |/
524 o 955800955977 root-commit
523 o 955800955977 root-commit
525
524
526
525
527 Do all the merge combination (from the deleted or the update side × keeping and deleting the file
526 Do all the merge combination (from the deleted or the update side × keeping and deleting the file
528
527
529 $ hg update 'desc("delete-the-file")'
528 $ hg update 'desc("delete-the-file")'
530 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
529 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
531 $ hg merge 'desc("updating-both-file")' -t :local
530 $ hg merge 'desc("updating-both-file")' -t :local
532 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
531 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
533 (branch merge, don't forget to commit)
532 (branch merge, don't forget to commit)
534 $ hg debugmergestate
533 $ hg debugmergestate
535 local (working copy): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
534 local (working copy): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
536 other (merge rev): 9b610631ab29024c5f44af7d2c19658ef8f8f071
535 other (merge rev): 9b610631ab29024c5f44af7d2c19658ef8f8f071
537 file: the-file (state "r")
536 file: the-file (state "r")
538 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
537 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
539 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
538 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
540 other path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
539 other path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
541 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
540 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
542 extra: merge-removal-candidate = yes
541 extra: merge-removal-candidate = yes
543 extra: merged = yes
542 extra: merged = yes
544 extra: other-file (filenode-source = other)
543 extra: other-file (filenode-source = other)
545 $ hg ci -m "merge-deleting-the-file-from-deleted"
544 $ hg ci -m "merge-deleting-the-file-from-deleted"
546 $ hg manifest
545 $ hg manifest
547 other-file
546 other-file
548 $ hg debugrevlogindex the-file
547 $ hg debugrevlogindex the-file
549 rev linkrev nodeid p1 p2
548 rev linkrev nodeid p1 p2
550 0 0 4b69178b9bda 000000000000 000000000000
549 0 0 4b69178b9bda 000000000000 000000000000
551 1 1 59e363a07dc8 4b69178b9bda 000000000000
550 1 1 59e363a07dc8 4b69178b9bda 000000000000
552
551
553 $ hg update 'desc("updating-both-file")'
552 $ hg update 'desc("updating-both-file")'
554 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
553 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
555 $ hg merge 'desc("delete-the-file")' -t :other
554 $ hg merge 'desc("delete-the-file")' -t :other
556 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
555 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
557 (branch merge, don't forget to commit)
556 (branch merge, don't forget to commit)
558 $ hg debugmergestate
557 $ hg debugmergestate
559 local (working copy): 9b610631ab29024c5f44af7d2c19658ef8f8f071
558 local (working copy): 9b610631ab29024c5f44af7d2c19658ef8f8f071
560 other (merge rev): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
559 other (merge rev): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
561 file: the-file (state "r")
560 file: the-file (state "r")
562 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
561 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
563 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
562 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
564 other path: the-file (node 0000000000000000000000000000000000000000)
563 other path: the-file (node 0000000000000000000000000000000000000000)
565 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
564 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
566 extra: merge-removal-candidate = yes
565 extra: merge-removal-candidate = yes
567 extra: merged = yes
566 extra: merged = yes
568 $ hg ci -m "merge-deleting-the-file-from-updated"
567 $ hg ci -m "merge-deleting-the-file-from-updated"
569 created new head
568 created new head
570 $ hg manifest
569 $ hg manifest
571 other-file
570 other-file
572 $ hg debugrevlogindex the-file
571 $ hg debugrevlogindex the-file
573 rev linkrev nodeid p1 p2
572 rev linkrev nodeid p1 p2
574 0 0 4b69178b9bda 000000000000 000000000000
573 0 0 4b69178b9bda 000000000000 000000000000
575 1 1 59e363a07dc8 4b69178b9bda 000000000000
574 1 1 59e363a07dc8 4b69178b9bda 000000000000
576
575
577 $ hg update 'desc("delete-the-file")'
576 $ hg update 'desc("delete-the-file")'
578 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
577 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
579 $ hg merge 'desc("updating-both-file")' -t :other
578 $ hg merge 'desc("updating-both-file")' -t :other
580 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
579 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
581 (branch merge, don't forget to commit)
580 (branch merge, don't forget to commit)
582 $ hg debugmergestate
581 $ hg debugmergestate
583 local (working copy): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
582 local (working copy): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
584 other (merge rev): 9b610631ab29024c5f44af7d2c19658ef8f8f071
583 other (merge rev): 9b610631ab29024c5f44af7d2c19658ef8f8f071
585 file: the-file (state "r")
584 file: the-file (state "r")
586 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
585 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
587 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
586 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
588 other path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
587 other path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
589 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
588 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
590 extra: merge-removal-candidate = yes
589 extra: merge-removal-candidate = yes
591 extra: merged = yes
590 extra: merged = yes
592 extra: other-file (filenode-source = other)
591 extra: other-file (filenode-source = other)
593 $ hg ci -m "merge-keeping-the-file-from-deleted"
592 $ hg ci -m "merge-keeping-the-file-from-deleted"
594 created new head
593 created new head
595 $ hg manifest
594 $ hg manifest
596 other-file
595 other-file
597 the-file
596 the-file
598
597
599 $ hg debugrevlogindex the-file
598 $ hg debugrevlogindex the-file
600 rev linkrev nodeid p1 p2
599 rev linkrev nodeid p1 p2
601 0 0 4b69178b9bda 000000000000 000000000000
600 0 0 4b69178b9bda 000000000000 000000000000
602 1 1 59e363a07dc8 4b69178b9bda 000000000000
601 1 1 59e363a07dc8 4b69178b9bda 000000000000
603 2 5 885af55420b3 59e363a07dc8 000000000000 (newfilenode !)
602 2 5 885af55420b3 59e363a07dc8 000000000000 (newfilenode !)
604
603
605 $ hg update 'desc("updating-both-file")'
604 $ hg update 'desc("updating-both-file")'
606 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
605 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
607 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
606 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
608 $ hg merge 'desc("delete-the-file")' -t :local
607 $ hg merge 'desc("delete-the-file")' -t :local
609 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
608 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
610 (branch merge, don't forget to commit)
609 (branch merge, don't forget to commit)
611 $ hg debugmergestate
610 $ hg debugmergestate
612 local (working copy): 9b610631ab29024c5f44af7d2c19658ef8f8f071
611 local (working copy): 9b610631ab29024c5f44af7d2c19658ef8f8f071
613 other (merge rev): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
612 other (merge rev): 7801bc9b9899de5e304bd162cafde9b78e10ab9b
614 file: the-file (state "r")
613 file: the-file (state "r")
615 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
614 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
616 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
615 ancestor path: the-file (node 4b69178b9bdae28b651393b46e631427a72f217a)
617 other path: the-file (node 0000000000000000000000000000000000000000)
616 other path: the-file (node 0000000000000000000000000000000000000000)
618 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
617 extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589
619 extra: merge-removal-candidate = yes
618 extra: merge-removal-candidate = yes
620 extra: merged = yes
619 extra: merged = yes
621 $ hg ci -m "merge-keeping-the-file-from-updated"
620 $ hg ci -m "merge-keeping-the-file-from-updated"
622 created new head
621 created new head
623 $ hg manifest
622 $ hg manifest
624 other-file
623 other-file
625 the-file
624 the-file
626
625
627 XXX: This should create a new filenode because user explicitly decided to keep
626 XXX: This should create a new filenode because user explicitly decided to keep
628 the file. If we reuse the same filenode, future merges (criss-cross ones mostly)
627 the file. If we reuse the same filenode, future merges (criss-cross ones mostly)
629 will think that file remain unchanged and user explicit choice will not be taken
628 will think that file remain unchanged and user explicit choice will not be taken
630 in consideration.
629 in consideration.
631 $ hg debugrevlogindex the-file
630 $ hg debugrevlogindex the-file
632 rev linkrev nodeid p1 p2
631 rev linkrev nodeid p1 p2
633 0 0 4b69178b9bda 000000000000 000000000000
632 0 0 4b69178b9bda 000000000000 000000000000
634 1 1 59e363a07dc8 4b69178b9bda 000000000000
633 1 1 59e363a07dc8 4b69178b9bda 000000000000
635 2 5 885af55420b3 59e363a07dc8 000000000000 (newfilenode !)
634 2 5 885af55420b3 59e363a07dc8 000000000000 (newfilenode !)
636
635
637 $ hg log -G -T '{node|short} {desc}\n'
636 $ hg log -G -T '{node|short} {desc}\n'
638 @ 5e3eccec60d8 merge-keeping-the-file-from-updated
637 @ 5e3eccec60d8 merge-keeping-the-file-from-updated
639 |\
638 |\
640 +---o 38a4c3e7cac8 merge-keeping-the-file-from-deleted (newfilenode !)
639 +---o 38a4c3e7cac8 merge-keeping-the-file-from-deleted (newfilenode !)
641 +---o e9b708131723 merge-keeping-the-file-from-deleted (old !)
640 +---o e9b708131723 merge-keeping-the-file-from-deleted (old !)
642 | |/
641 | |/
643 +---o a4e0e44229dc merge-deleting-the-file-from-updated
642 +---o a4e0e44229dc merge-deleting-the-file-from-updated
644 | |/
643 | |/
645 +---o adfd88e5d7d3 merge-deleting-the-file-from-deleted
644 +---o adfd88e5d7d3 merge-deleting-the-file-from-deleted
646 | |/
645 | |/
647 | o 7801bc9b9899 delete-the-file
646 | o 7801bc9b9899 delete-the-file
648 | |
647 | |
649 o | 9b610631ab29 updating-both-file
648 o | 9b610631ab29 updating-both-file
650 |/
649 |/
651 o 955800955977 root-commit
650 o 955800955977 root-commit
652
651
653
652
654 There the resulting merge together (leading to criss cross situation). Check
653 There the resulting merge together (leading to criss cross situation). Check
655 the conflict is properly detected.
654 the conflict is properly detected.
656
655
657 (merging two deletion together → no conflict)
656 (merging two deletion together → no conflict)
658
657
659 $ hg update --clean 'desc("merge-deleting-the-file-from-deleted")'
658 $ hg update --clean 'desc("merge-deleting-the-file-from-deleted")'
660 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
659 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
661 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
660 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
662 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
661 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
663 (branch merge, don't forget to commit)
662 (branch merge, don't forget to commit)
664 $ ls -1
663 $ ls -1
665 other-file
664 other-file
666 $ hg debugmergestate
665 $ hg debugmergestate
667 no merge state found
666 no merge state found
668
667
669 (merging a deletion with keeping conflict)
668 (merging a deletion with keeping conflict)
670
669
671 $ hg update --clean 'desc("merge-deleting-the-file-from-deleted")'
670 $ hg update --clean 'desc("merge-deleting-the-file-from-deleted")'
672 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
671 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
673
672
674 #if newfilenode
673 #if newfilenode
675 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
674 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
676 file 'the-file' was deleted in local [working copy] but was modified in other [merge rev].
675 file 'the-file' was deleted in local [working copy] but was modified in other [merge rev].
677 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
676 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
678 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
677 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
679 What do you want to do? u
678 What do you want to do? u
680 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
679 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
681 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
680 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
682 [1]
681 [1]
683 #else
682 #else
684 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
683 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
685 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
684 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
686 (branch merge, don't forget to commit)
685 (branch merge, don't forget to commit)
687 #endif
686 #endif
688 $ ls -1
687 $ ls -1
689 other-file
688 other-file
690 the-file (newfilenode !)
689 the-file (newfilenode !)
691
690
692 #if newfilenode
691 #if newfilenode
693 $ hg debugmergestate
692 $ hg debugmergestate
694 local (working copy): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
693 local (working copy): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
695 other (merge rev): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
694 other (merge rev): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
696 file: the-file (state "u")
695 file: the-file (state "u")
697 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
696 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
698 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
697 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
699 other path: the-file (node 885af55420b35d7bf3bbd6f546615295bfe6544a)
698 other path: the-file (node 885af55420b35d7bf3bbd6f546615295bfe6544a)
700 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
699 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
701 extra: merge-removal-candidate = yes
700 extra: merge-removal-candidate = yes
702 extra: merged = yes
701 extra: merged = yes
703 #else
702 #else
704 $ hg debugmergestate
703 $ hg debugmergestate
705 local (working copy): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
704 local (working copy): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
706 other (merge rev): e9b7081317232edce73f7ad5ae0b7807ff5c326a
705 other (merge rev): e9b7081317232edce73f7ad5ae0b7807ff5c326a
707 extra: the-file (merge-removal-candidate = yes)
706 extra: the-file (merge-removal-candidate = yes)
708 #endif
707 #endif
709
708
710 (merging a deletion with keeping → conflict)
709 (merging a deletion with keeping → conflict)
711 BROKEN: this should result in conflict
710 BROKEN: this should result in conflict
712
711
713 $ hg update --clean 'desc("merge-deleting-the-file-from-deleted")'
712 $ hg update --clean 'desc("merge-deleting-the-file-from-deleted")'
714 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (newfilenode !)
713 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (newfilenode !)
715 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
714 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
716 $ hg merge 'desc("merge-keeping-the-file-from-updated")'
715 $ hg merge 'desc("merge-keeping-the-file-from-updated")'
717 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
716 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
718 (branch merge, don't forget to commit)
717 (branch merge, don't forget to commit)
719 $ ls -1
718 $ ls -1
720 other-file
719 other-file
721 $ hg debugmergestate
720 $ hg debugmergestate
722 local (working copy): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
721 local (working copy): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
723 other (merge rev): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
722 other (merge rev): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
724 extra: the-file (merge-removal-candidate = yes)
723 extra: the-file (merge-removal-candidate = yes)
725
724
726 (merging two deletion together no conflict)
725 (merging two deletion together no conflict)
727
726
728 $ hg update --clean 'desc("merge-deleting-the-file-from-updated")'
727 $ hg update --clean 'desc("merge-deleting-the-file-from-updated")'
729 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
728 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
730 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
729 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
731 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
730 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
732 (branch merge, don't forget to commit)
731 (branch merge, don't forget to commit)
733 $ ls -1
732 $ ls -1
734 other-file
733 other-file
735 $ hg debugmergestate
734 $ hg debugmergestate
736 no merge state found
735 no merge state found
737
736
738 (merging a deletion with keeping → conflict)
737 (merging a deletion with keeping → conflict)
739
738
740 $ hg update --clean 'desc("merge-deleting-the-file-from-updated")'
739 $ hg update --clean 'desc("merge-deleting-the-file-from-updated")'
741 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
740 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
742
741
743 #if newfilenode
742 #if newfilenode
744 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
743 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
745 file 'the-file' was deleted in local [working copy] but was modified in other [merge rev].
744 file 'the-file' was deleted in local [working copy] but was modified in other [merge rev].
746 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
745 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
747 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
746 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
748 What do you want to do? u
747 What do you want to do? u
749 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
748 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
750 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
749 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
751 [1]
750 [1]
752 #else
751 #else
753 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
752 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
754 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
753 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
755 (branch merge, don't forget to commit)
754 (branch merge, don't forget to commit)
756 #endif
755 #endif
757
756
758 $ ls -1
757 $ ls -1
759 other-file
758 other-file
760 the-file (newfilenode !)
759 the-file (newfilenode !)
761 #if newfilenode
760 #if newfilenode
762 $ hg debugmergestate
761 $ hg debugmergestate
763 local (working copy): a4e0e44229dc130be2915b92c957c093f8c7ee3e
762 local (working copy): a4e0e44229dc130be2915b92c957c093f8c7ee3e
764 other (merge rev): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
763 other (merge rev): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
765 file: the-file (state "u")
764 file: the-file (state "u")
766 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
765 local path: the-file (hash 0000000000000000000000000000000000000000, flags "")
767 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
766 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
768 other path: the-file (node 885af55420b35d7bf3bbd6f546615295bfe6544a)
767 other path: the-file (node 885af55420b35d7bf3bbd6f546615295bfe6544a)
769 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
768 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
770 extra: merge-removal-candidate = yes
769 extra: merge-removal-candidate = yes
771 extra: merged = yes
770 extra: merged = yes
772 #else
771 #else
773 $ hg debugmergestate
772 $ hg debugmergestate
774 local (working copy): a4e0e44229dc130be2915b92c957c093f8c7ee3e
773 local (working copy): a4e0e44229dc130be2915b92c957c093f8c7ee3e
775 other (merge rev): e9b7081317232edce73f7ad5ae0b7807ff5c326a
774 other (merge rev): e9b7081317232edce73f7ad5ae0b7807ff5c326a
776 extra: the-file (merge-removal-candidate = yes)
775 extra: the-file (merge-removal-candidate = yes)
777 #endif
776 #endif
778
777
779 (merging a deletion with keeping conflict)
778 (merging a deletion with keeping conflict)
780 BROKEN: this should result in conflict
779 BROKEN: this should result in conflict
781
780
782 $ hg update --clean 'desc("merge-deleting-the-file-from-updated")'
781 $ hg update --clean 'desc("merge-deleting-the-file-from-updated")'
783 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (newfilenode !)
782 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (newfilenode !)
784 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
783 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
785 $ hg merge 'desc("merge-keeping-the-file-from-updated")'
784 $ hg merge 'desc("merge-keeping-the-file-from-updated")'
786 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
785 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
787 (branch merge, don't forget to commit)
786 (branch merge, don't forget to commit)
788 $ ls -1
787 $ ls -1
789 other-file
788 other-file
790 $ hg debugmergestate
789 $ hg debugmergestate
791 local (working copy): a4e0e44229dc130be2915b92c957c093f8c7ee3e
790 local (working copy): a4e0e44229dc130be2915b92c957c093f8c7ee3e
792 other (merge rev): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
791 other (merge rev): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
793 extra: the-file (merge-removal-candidate = yes)
792 extra: the-file (merge-removal-candidate = yes)
794
793
795 (merging two "keeping" together → no conflict)
794 (merging two "keeping" together → no conflict)
796
795
797 $ hg update --clean 'desc("merge-keeping-the-file-from-updated")'
796 $ hg update --clean 'desc("merge-keeping-the-file-from-updated")'
798 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
797 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
799 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
798 $ hg merge 'desc("merge-keeping-the-file-from-deleted")'
800 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
799 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
801 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
800 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
802 (branch merge, don't forget to commit)
801 (branch merge, don't forget to commit)
803 $ ls -1
802 $ ls -1
804 other-file
803 other-file
805 the-file
804 the-file
806 #if newfilenode
805 #if newfilenode
807 $ hg debugmergestate
806 $ hg debugmergestate
808 local (working copy): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
807 local (working copy): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
809 other (merge rev): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
808 other (merge rev): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
810 extra: the-file (filenode-source = other)
809 extra: the-file (filenode-source = other)
811 #else
810 #else
812 $ hg debugmergestate
811 $ hg debugmergestate
813 no merge state found
812 no merge state found
814 #endif
813 #endif
815
814
816 (merging a deletion with keeping conflict)
815 (merging a deletion with keeping conflict)
817 BROKEN: this should result in conflict
816 BROKEN: this should result in conflict
818
817
819 $ hg update --clean 'desc("merge-keeping-the-file-from-updated")'
818 $ hg update --clean 'desc("merge-keeping-the-file-from-updated")'
820 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
819 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
821 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
820 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
822 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
821 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
823 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
822 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
824 (branch merge, don't forget to commit)
823 (branch merge, don't forget to commit)
825 $ ls -1
824 $ ls -1
826 other-file
825 other-file
827 the-file
826 the-file
828 $ hg debugmergestate
827 $ hg debugmergestate
829 local (working copy): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
828 local (working copy): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
830 other (merge rev): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
829 other (merge rev): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
831 extra: the-file (merge-removal-candidate = yes)
830 extra: the-file (merge-removal-candidate = yes)
832
831
833 (merging a deletion with keeping → conflict)
832 (merging a deletion with keeping → conflict)
834 BROKEN: this should result in conflict
833 BROKEN: this should result in conflict
835
834
836 $ hg update --clean 'desc("merge-keeping-the-file-from-updated")'
835 $ hg update --clean 'desc("merge-keeping-the-file-from-updated")'
837 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
836 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
838 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
837 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
839 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
838 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
840 (branch merge, don't forget to commit)
839 (branch merge, don't forget to commit)
841 $ ls -1
840 $ ls -1
842 other-file
841 other-file
843 the-file
842 the-file
844 $ hg debugmergestate
843 $ hg debugmergestate
845 local (working copy): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
844 local (working copy): 5e3eccec60d88f94a7ba57c351f32cb24c15fe0c
846 other (merge rev): a4e0e44229dc130be2915b92c957c093f8c7ee3e
845 other (merge rev): a4e0e44229dc130be2915b92c957c093f8c7ee3e
847 extra: the-file (merge-removal-candidate = yes)
846 extra: the-file (merge-removal-candidate = yes)
848
847
849 (merging two "keeping" together no conflict)
848 (merging two "keeping" together no conflict)
850
849
851 $ hg update --clean 'desc("merge-keeping-the-file-from-deleted")'
850 $ hg update --clean 'desc("merge-keeping-the-file-from-deleted")'
852 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
851 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (newfilenode !)
853 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
852 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (old !)
854 $ hg merge 'desc("merge-keeping-the-file-from-updated")'
853 $ hg merge 'desc("merge-keeping-the-file-from-updated")'
855 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
854 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
856 (branch merge, don't forget to commit)
855 (branch merge, don't forget to commit)
857 $ ls -1
856 $ ls -1
858 other-file
857 other-file
859 the-file
858 the-file
860 $ hg debugmergestate
859 $ hg debugmergestate
861 no merge state found
860 no merge state found
862
861
863 (merging a deletion with keeping → conflict)
862 (merging a deletion with keeping → conflict)
864
863
865 $ hg update --clean 'desc("merge-keeping-the-file-from-deleted")'
864 $ hg update --clean 'desc("merge-keeping-the-file-from-deleted")'
866 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
865 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
867 #if newfilenode
866 #if newfilenode
868 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
867 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
869 file 'the-file' was deleted in other [merge rev] but was modified in local [working copy].
868 file 'the-file' was deleted in other [merge rev] but was modified in local [working copy].
870 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
869 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
871 What do you want to do? u
870 What do you want to do? u
872 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
871 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
873 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
872 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
874 [1]
873 [1]
875 #else
874 #else
876 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
875 $ hg merge 'desc("merge-deleting-the-file-from-deleted")'
877 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
876 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
878 (branch merge, don't forget to commit)
877 (branch merge, don't forget to commit)
879 #endif
878 #endif
880 $ ls -1
879 $ ls -1
881 other-file
880 other-file
882 the-file
881 the-file
883
882
884 #if newfilenode
883 #if newfilenode
885 $ hg debugmergestate
884 $ hg debugmergestate
886 local (working copy): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78 (newfilenode !)
885 local (working copy): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78 (newfilenode !)
887 local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a (old !)
886 local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a (old !)
888 other (merge rev): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
887 other (merge rev): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
889 file: the-file (state "u")
888 file: the-file (state "u")
890 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
889 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
891 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
890 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
892 other path: the-file (node 0000000000000000000000000000000000000000)
891 other path: the-file (node 0000000000000000000000000000000000000000)
893 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
892 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
894 extra: merge-removal-candidate = yes
893 extra: merge-removal-candidate = yes
895 extra: merged = yes
894 extra: merged = yes
896 #else
895 #else
897 $ hg debugmergestate
896 $ hg debugmergestate
898 local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a
897 local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a
899 other (merge rev): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
898 other (merge rev): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f
900 extra: the-file (merge-removal-candidate = yes)
899 extra: the-file (merge-removal-candidate = yes)
901 #endif
900 #endif
902
901
903 (merging a deletion with keeping conflict)
902 (merging a deletion with keeping conflict)
904
903
905 $ hg update --clean 'desc("merge-keeping-the-file-from-deleted")'
904 $ hg update --clean 'desc("merge-keeping-the-file-from-deleted")'
906 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
905 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
907 #if newfilenode
906 #if newfilenode
908 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
907 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
909 file 'the-file' was deleted in other [merge rev] but was modified in local [working copy].
908 file 'the-file' was deleted in other [merge rev] but was modified in local [working copy].
910 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
909 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
911 What do you want to do? u
910 What do you want to do? u
912 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
911 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
913 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
912 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
914 [1]
913 [1]
915 #else
914 #else
916 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
915 $ hg merge 'desc("merge-deleting-the-file-from-updated")'
917 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
916 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
918 (branch merge, don't forget to commit)
917 (branch merge, don't forget to commit)
919 #endif
918 #endif
920 $ ls -1
919 $ ls -1
921 other-file
920 other-file
922 the-file
921 the-file
923 #if newfilenode
922 #if newfilenode
924 $ hg debugmergestate
923 $ hg debugmergestate
925 local (working copy): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
924 local (working copy): 38a4c3e7cac8c294ecb0a7a85a05464e9836ca78
926 other (merge rev): a4e0e44229dc130be2915b92c957c093f8c7ee3e
925 other (merge rev): a4e0e44229dc130be2915b92c957c093f8c7ee3e
927 file: the-file (state "u")
926 file: the-file (state "u")
928 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
927 local path: the-file (hash 6d2e02da5a9fe0691363dc6b573845fa271eaa35, flags "")
929 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
928 ancestor path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460)
930 other path: the-file (node 0000000000000000000000000000000000000000)
929 other path: the-file (node 0000000000000000000000000000000000000000)
931 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
930 extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071
932 extra: merge-removal-candidate = yes
931 extra: merge-removal-candidate = yes
933 extra: merged = yes
932 extra: merged = yes
934 #else
933 #else
935 $ hg debugmergestate
934 $ hg debugmergestate
936 local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a
935 local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a
937 other (merge rev): a4e0e44229dc130be2915b92c957c093f8c7ee3e
936 other (merge rev): a4e0e44229dc130be2915b92c957c093f8c7ee3e
938 extra: the-file (merge-removal-candidate = yes)
937 extra: the-file (merge-removal-candidate = yes)
939 #endif
938 #endif
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now