##// END OF EJS Templates
log: pass getcopies() function instead of getrenamed() to displayer (API)...
Martin von Zweigbergk -
r42704:a68350a7 default
parent child Browse files
Show More
@@ -1,6272 +1,6272
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from . import (
25 from . import (
26 archival,
26 archival,
27 bookmarks,
27 bookmarks,
28 bundle2,
28 bundle2,
29 changegroup,
29 changegroup,
30 cmdutil,
30 cmdutil,
31 copies,
31 copies,
32 debugcommands as debugcommandsmod,
32 debugcommands as debugcommandsmod,
33 destutil,
33 destutil,
34 dirstateguard,
34 dirstateguard,
35 discovery,
35 discovery,
36 encoding,
36 encoding,
37 error,
37 error,
38 exchange,
38 exchange,
39 extensions,
39 extensions,
40 filemerge,
40 filemerge,
41 formatter,
41 formatter,
42 graphmod,
42 graphmod,
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 narrowspec,
48 narrowspec,
49 obsolete,
49 obsolete,
50 obsutil,
50 obsutil,
51 patch,
51 patch,
52 phases,
52 phases,
53 pycompat,
53 pycompat,
54 rcutil,
54 rcutil,
55 registrar,
55 registrar,
56 repair,
56 repair,
57 revsetlang,
57 revsetlang,
58 rewriteutil,
58 rewriteutil,
59 scmutil,
59 scmutil,
60 server,
60 server,
61 state as statemod,
61 state as statemod,
62 streamclone,
62 streamclone,
63 tags as tagsmod,
63 tags as tagsmod,
64 ui as uimod,
64 ui as uimod,
65 util,
65 util,
66 verify as verifymod,
66 verify as verifymod,
67 wireprotoserver,
67 wireprotoserver,
68 )
68 )
69 from .utils import (
69 from .utils import (
70 dateutil,
70 dateutil,
71 stringutil,
71 stringutil,
72 )
72 )
73
73
74 table = {}
74 table = {}
75 table.update(debugcommandsmod.command._table)
75 table.update(debugcommandsmod.command._table)
76
76
77 command = registrar.command(table)
77 command = registrar.command(table)
78 INTENT_READONLY = registrar.INTENT_READONLY
78 INTENT_READONLY = registrar.INTENT_READONLY
79
79
80 # common command options
80 # common command options
81
81
82 globalopts = [
82 globalopts = [
83 ('R', 'repository', '',
83 ('R', 'repository', '',
84 _('repository root directory or name of overlay bundle file'),
84 _('repository root directory or name of overlay bundle file'),
85 _('REPO')),
85 _('REPO')),
86 ('', 'cwd', '',
86 ('', 'cwd', '',
87 _('change working directory'), _('DIR')),
87 _('change working directory'), _('DIR')),
88 ('y', 'noninteractive', None,
88 ('y', 'noninteractive', None,
89 _('do not prompt, automatically pick the first choice for all prompts')),
89 _('do not prompt, automatically pick the first choice for all prompts')),
90 ('q', 'quiet', None, _('suppress output')),
90 ('q', 'quiet', None, _('suppress output')),
91 ('v', 'verbose', None, _('enable additional output')),
91 ('v', 'verbose', None, _('enable additional output')),
92 ('', 'color', '',
92 ('', 'color', '',
93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 # and should not be translated
94 # and should not be translated
95 _("when to colorize (boolean, always, auto, never, or debug)"),
95 _("when to colorize (boolean, always, auto, never, or debug)"),
96 _('TYPE')),
96 _('TYPE')),
97 ('', 'config', [],
97 ('', 'config', [],
98 _('set/override config option (use \'section.name=value\')'),
98 _('set/override config option (use \'section.name=value\')'),
99 _('CONFIG')),
99 _('CONFIG')),
100 ('', 'debug', None, _('enable debugging output')),
100 ('', 'debug', None, _('enable debugging output')),
101 ('', 'debugger', None, _('start debugger')),
101 ('', 'debugger', None, _('start debugger')),
102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 _('ENCODE')),
103 _('ENCODE')),
104 ('', 'encodingmode', encoding.encodingmode,
104 ('', 'encodingmode', encoding.encodingmode,
105 _('set the charset encoding mode'), _('MODE')),
105 _('set the charset encoding mode'), _('MODE')),
106 ('', 'traceback', None, _('always print a traceback on exception')),
106 ('', 'traceback', None, _('always print a traceback on exception')),
107 ('', 'time', None, _('time how long the command takes')),
107 ('', 'time', None, _('time how long the command takes')),
108 ('', 'profile', None, _('print command execution profile')),
108 ('', 'profile', None, _('print command execution profile')),
109 ('', 'version', None, _('output version information and exit')),
109 ('', 'version', None, _('output version information and exit')),
110 ('h', 'help', None, _('display help and exit')),
110 ('h', 'help', None, _('display help and exit')),
111 ('', 'hidden', False, _('consider hidden changesets')),
111 ('', 'hidden', False, _('consider hidden changesets')),
112 ('', 'pager', 'auto',
112 ('', 'pager', 'auto',
113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 ]
114 ]
115
115
116 dryrunopts = cmdutil.dryrunopts
116 dryrunopts = cmdutil.dryrunopts
117 remoteopts = cmdutil.remoteopts
117 remoteopts = cmdutil.remoteopts
118 walkopts = cmdutil.walkopts
118 walkopts = cmdutil.walkopts
119 commitopts = cmdutil.commitopts
119 commitopts = cmdutil.commitopts
120 commitopts2 = cmdutil.commitopts2
120 commitopts2 = cmdutil.commitopts2
121 formatteropts = cmdutil.formatteropts
121 formatteropts = cmdutil.formatteropts
122 templateopts = cmdutil.templateopts
122 templateopts = cmdutil.templateopts
123 logopts = cmdutil.logopts
123 logopts = cmdutil.logopts
124 diffopts = cmdutil.diffopts
124 diffopts = cmdutil.diffopts
125 diffwsopts = cmdutil.diffwsopts
125 diffwsopts = cmdutil.diffwsopts
126 diffopts2 = cmdutil.diffopts2
126 diffopts2 = cmdutil.diffopts2
127 mergetoolopts = cmdutil.mergetoolopts
127 mergetoolopts = cmdutil.mergetoolopts
128 similarityopts = cmdutil.similarityopts
128 similarityopts = cmdutil.similarityopts
129 subrepoopts = cmdutil.subrepoopts
129 subrepoopts = cmdutil.subrepoopts
130 debugrevlogopts = cmdutil.debugrevlogopts
130 debugrevlogopts = cmdutil.debugrevlogopts
131
131
132 # Commands start here, listed alphabetically
132 # Commands start here, listed alphabetically
133
133
134 @command('add',
134 @command('add',
135 walkopts + subrepoopts + dryrunopts,
135 walkopts + subrepoopts + dryrunopts,
136 _('[OPTION]... [FILE]...'),
136 _('[OPTION]... [FILE]...'),
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 helpbasic=True, inferrepo=True)
138 helpbasic=True, inferrepo=True)
139 def add(ui, repo, *pats, **opts):
139 def add(ui, repo, *pats, **opts):
140 """add the specified files on the next commit
140 """add the specified files on the next commit
141
141
142 Schedule files to be version controlled and added to the
142 Schedule files to be version controlled and added to the
143 repository.
143 repository.
144
144
145 The files will be added to the repository at the next commit. To
145 The files will be added to the repository at the next commit. To
146 undo an add before that, see :hg:`forget`.
146 undo an add before that, see :hg:`forget`.
147
147
148 If no names are given, add all files to the repository (except
148 If no names are given, add all files to the repository (except
149 files matching ``.hgignore``).
149 files matching ``.hgignore``).
150
150
151 .. container:: verbose
151 .. container:: verbose
152
152
153 Examples:
153 Examples:
154
154
155 - New (unknown) files are added
155 - New (unknown) files are added
156 automatically by :hg:`add`::
156 automatically by :hg:`add`::
157
157
158 $ ls
158 $ ls
159 foo.c
159 foo.c
160 $ hg status
160 $ hg status
161 ? foo.c
161 ? foo.c
162 $ hg add
162 $ hg add
163 adding foo.c
163 adding foo.c
164 $ hg status
164 $ hg status
165 A foo.c
165 A foo.c
166
166
167 - Specific files to be added can be specified::
167 - Specific files to be added can be specified::
168
168
169 $ ls
169 $ ls
170 bar.c foo.c
170 bar.c foo.c
171 $ hg status
171 $ hg status
172 ? bar.c
172 ? bar.c
173 ? foo.c
173 ? foo.c
174 $ hg add bar.c
174 $ hg add bar.c
175 $ hg status
175 $ hg status
176 A bar.c
176 A bar.c
177 ? foo.c
177 ? foo.c
178
178
179 Returns 0 if all files are successfully added.
179 Returns 0 if all files are successfully added.
180 """
180 """
181
181
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
185 return rejected and 1 or 0
185 return rejected and 1 or 0
186
186
187 @command('addremove',
187 @command('addremove',
188 similarityopts + subrepoopts + walkopts + dryrunopts,
188 similarityopts + subrepoopts + walkopts + dryrunopts,
189 _('[OPTION]... [FILE]...'),
189 _('[OPTION]... [FILE]...'),
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
191 inferrepo=True)
191 inferrepo=True)
192 def addremove(ui, repo, *pats, **opts):
192 def addremove(ui, repo, *pats, **opts):
193 """add all new files, delete all missing files
193 """add all new files, delete all missing files
194
194
195 Add all new files and remove all missing files from the
195 Add all new files and remove all missing files from the
196 repository.
196 repository.
197
197
198 Unless names are given, new files are ignored if they match any of
198 Unless names are given, new files are ignored if they match any of
199 the patterns in ``.hgignore``. As with add, these changes take
199 the patterns in ``.hgignore``. As with add, these changes take
200 effect at the next commit.
200 effect at the next commit.
201
201
202 Use the -s/--similarity option to detect renamed files. This
202 Use the -s/--similarity option to detect renamed files. This
203 option takes a percentage between 0 (disabled) and 100 (files must
203 option takes a percentage between 0 (disabled) and 100 (files must
204 be identical) as its parameter. With a parameter greater than 0,
204 be identical) as its parameter. With a parameter greater than 0,
205 this compares every removed file with every added file and records
205 this compares every removed file with every added file and records
206 those similar enough as renames. Detecting renamed files this way
206 those similar enough as renames. Detecting renamed files this way
207 can be expensive. After using this option, :hg:`status -C` can be
207 can be expensive. After using this option, :hg:`status -C` can be
208 used to check which files were identified as moved or renamed. If
208 used to check which files were identified as moved or renamed. If
209 not specified, -s/--similarity defaults to 100 and only renames of
209 not specified, -s/--similarity defaults to 100 and only renames of
210 identical files are detected.
210 identical files are detected.
211
211
212 .. container:: verbose
212 .. container:: verbose
213
213
214 Examples:
214 Examples:
215
215
216 - A number of files (bar.c and foo.c) are new,
216 - A number of files (bar.c and foo.c) are new,
217 while foobar.c has been removed (without using :hg:`remove`)
217 while foobar.c has been removed (without using :hg:`remove`)
218 from the repository::
218 from the repository::
219
219
220 $ ls
220 $ ls
221 bar.c foo.c
221 bar.c foo.c
222 $ hg status
222 $ hg status
223 ! foobar.c
223 ! foobar.c
224 ? bar.c
224 ? bar.c
225 ? foo.c
225 ? foo.c
226 $ hg addremove
226 $ hg addremove
227 adding bar.c
227 adding bar.c
228 adding foo.c
228 adding foo.c
229 removing foobar.c
229 removing foobar.c
230 $ hg status
230 $ hg status
231 A bar.c
231 A bar.c
232 A foo.c
232 A foo.c
233 R foobar.c
233 R foobar.c
234
234
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
236 Afterwards, it was edited slightly::
236 Afterwards, it was edited slightly::
237
237
238 $ ls
238 $ ls
239 foo.c
239 foo.c
240 $ hg status
240 $ hg status
241 ! foobar.c
241 ! foobar.c
242 ? foo.c
242 ? foo.c
243 $ hg addremove --similarity 90
243 $ hg addremove --similarity 90
244 removing foobar.c
244 removing foobar.c
245 adding foo.c
245 adding foo.c
246 recording removal of foobar.c as rename to foo.c (94% similar)
246 recording removal of foobar.c as rename to foo.c (94% similar)
247 $ hg status -C
247 $ hg status -C
248 A foo.c
248 A foo.c
249 foobar.c
249 foobar.c
250 R foobar.c
250 R foobar.c
251
251
252 Returns 0 if all files are successfully added.
252 Returns 0 if all files are successfully added.
253 """
253 """
254 opts = pycompat.byteskwargs(opts)
254 opts = pycompat.byteskwargs(opts)
255 if not opts.get('similarity'):
255 if not opts.get('similarity'):
256 opts['similarity'] = '100'
256 opts['similarity'] = '100'
257 matcher = scmutil.match(repo[None], pats, opts)
257 matcher = scmutil.match(repo[None], pats, opts)
258 relative = scmutil.anypats(pats, opts)
258 relative = scmutil.anypats(pats, opts)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
261
261
262 @command('annotate|blame',
262 @command('annotate|blame',
263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
264 ('', 'follow', None,
264 ('', 'follow', None,
265 _('follow copies/renames and list the filename (DEPRECATED)')),
265 _('follow copies/renames and list the filename (DEPRECATED)')),
266 ('', 'no-follow', None, _("don't follow copies and renames")),
266 ('', 'no-follow', None, _("don't follow copies and renames")),
267 ('a', 'text', None, _('treat all files as text')),
267 ('a', 'text', None, _('treat all files as text')),
268 ('u', 'user', None, _('list the author (long with -v)')),
268 ('u', 'user', None, _('list the author (long with -v)')),
269 ('f', 'file', None, _('list the filename')),
269 ('f', 'file', None, _('list the filename')),
270 ('d', 'date', None, _('list the date (short with -q)')),
270 ('d', 'date', None, _('list the date (short with -q)')),
271 ('n', 'number', None, _('list the revision number (default)')),
271 ('n', 'number', None, _('list the revision number (default)')),
272 ('c', 'changeset', None, _('list the changeset')),
272 ('c', 'changeset', None, _('list the changeset')),
273 ('l', 'line-number', None, _('show line number at the first appearance')),
273 ('l', 'line-number', None, _('show line number at the first appearance')),
274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
275 ] + diffwsopts + walkopts + formatteropts,
275 ] + diffwsopts + walkopts + formatteropts,
276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
277 helpcategory=command.CATEGORY_FILE_CONTENTS,
277 helpcategory=command.CATEGORY_FILE_CONTENTS,
278 helpbasic=True, inferrepo=True)
278 helpbasic=True, inferrepo=True)
279 def annotate(ui, repo, *pats, **opts):
279 def annotate(ui, repo, *pats, **opts):
280 """show changeset information by line for each file
280 """show changeset information by line for each file
281
281
282 List changes in files, showing the revision id responsible for
282 List changes in files, showing the revision id responsible for
283 each line.
283 each line.
284
284
285 This command is useful for discovering when a change was made and
285 This command is useful for discovering when a change was made and
286 by whom.
286 by whom.
287
287
288 If you include --file, --user, or --date, the revision number is
288 If you include --file, --user, or --date, the revision number is
289 suppressed unless you also include --number.
289 suppressed unless you also include --number.
290
290
291 Without the -a/--text option, annotate will avoid processing files
291 Without the -a/--text option, annotate will avoid processing files
292 it detects as binary. With -a, annotate will annotate the file
292 it detects as binary. With -a, annotate will annotate the file
293 anyway, although the results will probably be neither useful
293 anyway, although the results will probably be neither useful
294 nor desirable.
294 nor desirable.
295
295
296 .. container:: verbose
296 .. container:: verbose
297
297
298 Template:
298 Template:
299
299
300 The following keywords are supported in addition to the common template
300 The following keywords are supported in addition to the common template
301 keywords and functions. See also :hg:`help templates`.
301 keywords and functions. See also :hg:`help templates`.
302
302
303 :lines: List of lines with annotation data.
303 :lines: List of lines with annotation data.
304 :path: String. Repository-absolute path of the specified file.
304 :path: String. Repository-absolute path of the specified file.
305
305
306 And each entry of ``{lines}`` provides the following sub-keywords in
306 And each entry of ``{lines}`` provides the following sub-keywords in
307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
308
308
309 :line: String. Line content.
309 :line: String. Line content.
310 :lineno: Integer. Line number at that revision.
310 :lineno: Integer. Line number at that revision.
311 :path: String. Repository-absolute path of the file at that revision.
311 :path: String. Repository-absolute path of the file at that revision.
312
312
313 See :hg:`help templates.operators` for the list expansion syntax.
313 See :hg:`help templates.operators` for the list expansion syntax.
314
314
315 Returns 0 on success.
315 Returns 0 on success.
316 """
316 """
317 opts = pycompat.byteskwargs(opts)
317 opts = pycompat.byteskwargs(opts)
318 if not pats:
318 if not pats:
319 raise error.Abort(_('at least one filename or pattern is required'))
319 raise error.Abort(_('at least one filename or pattern is required'))
320
320
321 if opts.get('follow'):
321 if opts.get('follow'):
322 # --follow is deprecated and now just an alias for -f/--file
322 # --follow is deprecated and now just an alias for -f/--file
323 # to mimic the behavior of Mercurial before version 1.5
323 # to mimic the behavior of Mercurial before version 1.5
324 opts['file'] = True
324 opts['file'] = True
325
325
326 if (not opts.get('user') and not opts.get('changeset')
326 if (not opts.get('user') and not opts.get('changeset')
327 and not opts.get('date') and not opts.get('file')):
327 and not opts.get('date') and not opts.get('file')):
328 opts['number'] = True
328 opts['number'] = True
329
329
330 linenumber = opts.get('line_number') is not None
330 linenumber = opts.get('line_number') is not None
331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
332 raise error.Abort(_('at least one of -n/-c is required for -l'))
332 raise error.Abort(_('at least one of -n/-c is required for -l'))
333
333
334 rev = opts.get('rev')
334 rev = opts.get('rev')
335 if rev:
335 if rev:
336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
337 ctx = scmutil.revsingle(repo, rev)
337 ctx = scmutil.revsingle(repo, rev)
338
338
339 ui.pager('annotate')
339 ui.pager('annotate')
340 rootfm = ui.formatter('annotate', opts)
340 rootfm = ui.formatter('annotate', opts)
341 if ui.debugflag:
341 if ui.debugflag:
342 shorthex = pycompat.identity
342 shorthex = pycompat.identity
343 else:
343 else:
344 def shorthex(h):
344 def shorthex(h):
345 return h[:12]
345 return h[:12]
346 if ui.quiet:
346 if ui.quiet:
347 datefunc = dateutil.shortdate
347 datefunc = dateutil.shortdate
348 else:
348 else:
349 datefunc = dateutil.datestr
349 datefunc = dateutil.datestr
350 if ctx.rev() is None:
350 if ctx.rev() is None:
351 if opts.get('changeset'):
351 if opts.get('changeset'):
352 # omit "+" suffix which is appended to node hex
352 # omit "+" suffix which is appended to node hex
353 def formatrev(rev):
353 def formatrev(rev):
354 if rev == wdirrev:
354 if rev == wdirrev:
355 return '%d' % ctx.p1().rev()
355 return '%d' % ctx.p1().rev()
356 else:
356 else:
357 return '%d' % rev
357 return '%d' % rev
358 else:
358 else:
359 def formatrev(rev):
359 def formatrev(rev):
360 if rev == wdirrev:
360 if rev == wdirrev:
361 return '%d+' % ctx.p1().rev()
361 return '%d+' % ctx.p1().rev()
362 else:
362 else:
363 return '%d ' % rev
363 return '%d ' % rev
364 def formathex(h):
364 def formathex(h):
365 if h == wdirhex:
365 if h == wdirhex:
366 return '%s+' % shorthex(hex(ctx.p1().node()))
366 return '%s+' % shorthex(hex(ctx.p1().node()))
367 else:
367 else:
368 return '%s ' % shorthex(h)
368 return '%s ' % shorthex(h)
369 else:
369 else:
370 formatrev = b'%d'.__mod__
370 formatrev = b'%d'.__mod__
371 formathex = shorthex
371 formathex = shorthex
372
372
373 opmap = [
373 opmap = [
374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
380 ]
380 ]
381 opnamemap = {
381 opnamemap = {
382 'rev': 'number',
382 'rev': 'number',
383 'node': 'changeset',
383 'node': 'changeset',
384 'path': 'file',
384 'path': 'file',
385 'lineno': 'line_number',
385 'lineno': 'line_number',
386 }
386 }
387
387
388 if rootfm.isplain():
388 if rootfm.isplain():
389 def makefunc(get, fmt):
389 def makefunc(get, fmt):
390 return lambda x: fmt(get(x))
390 return lambda x: fmt(get(x))
391 else:
391 else:
392 def makefunc(get, fmt):
392 def makefunc(get, fmt):
393 return get
393 return get
394 datahint = rootfm.datahint()
394 datahint = rootfm.datahint()
395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
400
400
401 def bad(x, y):
401 def bad(x, y):
402 raise error.Abort("%s: %s" % (x, y))
402 raise error.Abort("%s: %s" % (x, y))
403
403
404 m = scmutil.match(ctx, pats, opts, badfn=bad)
404 m = scmutil.match(ctx, pats, opts, badfn=bad)
405
405
406 follow = not opts.get('no_follow')
406 follow = not opts.get('no_follow')
407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
408 whitespace=True)
408 whitespace=True)
409 skiprevs = opts.get('skip')
409 skiprevs = opts.get('skip')
410 if skiprevs:
410 if skiprevs:
411 skiprevs = scmutil.revrange(repo, skiprevs)
411 skiprevs = scmutil.revrange(repo, skiprevs)
412
412
413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
414 for abs in ctx.walk(m):
414 for abs in ctx.walk(m):
415 fctx = ctx[abs]
415 fctx = ctx[abs]
416 rootfm.startitem()
416 rootfm.startitem()
417 rootfm.data(path=abs)
417 rootfm.data(path=abs)
418 if not opts.get('text') and fctx.isbinary():
418 if not opts.get('text') and fctx.isbinary():
419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
420 continue
420 continue
421
421
422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
424 diffopts=diffopts)
424 diffopts=diffopts)
425 if not lines:
425 if not lines:
426 fm.end()
426 fm.end()
427 continue
427 continue
428 formats = []
428 formats = []
429 pieces = []
429 pieces = []
430
430
431 for f, sep in funcmap:
431 for f, sep in funcmap:
432 l = [f(n) for n in lines]
432 l = [f(n) for n in lines]
433 if fm.isplain():
433 if fm.isplain():
434 sizes = [encoding.colwidth(x) for x in l]
434 sizes = [encoding.colwidth(x) for x in l]
435 ml = max(sizes)
435 ml = max(sizes)
436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
437 else:
437 else:
438 formats.append(['%s' for x in l])
438 formats.append(['%s' for x in l])
439 pieces.append(l)
439 pieces.append(l)
440
440
441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
442 fm.startitem()
442 fm.startitem()
443 fm.context(fctx=n.fctx)
443 fm.context(fctx=n.fctx)
444 fm.write(fields, "".join(f), *p)
444 fm.write(fields, "".join(f), *p)
445 if n.skip:
445 if n.skip:
446 fmt = "* %s"
446 fmt = "* %s"
447 else:
447 else:
448 fmt = ": %s"
448 fmt = ": %s"
449 fm.write('line', fmt, n.text)
449 fm.write('line', fmt, n.text)
450
450
451 if not lines[-1].text.endswith('\n'):
451 if not lines[-1].text.endswith('\n'):
452 fm.plain('\n')
452 fm.plain('\n')
453 fm.end()
453 fm.end()
454
454
455 rootfm.end()
455 rootfm.end()
456
456
457 @command('archive',
457 @command('archive',
458 [('', 'no-decode', None, _('do not pass files through decoders')),
458 [('', 'no-decode', None, _('do not pass files through decoders')),
459 ('p', 'prefix', '', _('directory prefix for files in archive'),
459 ('p', 'prefix', '', _('directory prefix for files in archive'),
460 _('PREFIX')),
460 _('PREFIX')),
461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
463 ] + subrepoopts + walkopts,
463 ] + subrepoopts + walkopts,
464 _('[OPTION]... DEST'),
464 _('[OPTION]... DEST'),
465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
466 def archive(ui, repo, dest, **opts):
466 def archive(ui, repo, dest, **opts):
467 '''create an unversioned archive of a repository revision
467 '''create an unversioned archive of a repository revision
468
468
469 By default, the revision used is the parent of the working
469 By default, the revision used is the parent of the working
470 directory; use -r/--rev to specify a different revision.
470 directory; use -r/--rev to specify a different revision.
471
471
472 The archive type is automatically detected based on file
472 The archive type is automatically detected based on file
473 extension (to override, use -t/--type).
473 extension (to override, use -t/--type).
474
474
475 .. container:: verbose
475 .. container:: verbose
476
476
477 Examples:
477 Examples:
478
478
479 - create a zip file containing the 1.0 release::
479 - create a zip file containing the 1.0 release::
480
480
481 hg archive -r 1.0 project-1.0.zip
481 hg archive -r 1.0 project-1.0.zip
482
482
483 - create a tarball excluding .hg files::
483 - create a tarball excluding .hg files::
484
484
485 hg archive project.tar.gz -X ".hg*"
485 hg archive project.tar.gz -X ".hg*"
486
486
487 Valid types are:
487 Valid types are:
488
488
489 :``files``: a directory full of files (default)
489 :``files``: a directory full of files (default)
490 :``tar``: tar archive, uncompressed
490 :``tar``: tar archive, uncompressed
491 :``tbz2``: tar archive, compressed using bzip2
491 :``tbz2``: tar archive, compressed using bzip2
492 :``tgz``: tar archive, compressed using gzip
492 :``tgz``: tar archive, compressed using gzip
493 :``uzip``: zip archive, uncompressed
493 :``uzip``: zip archive, uncompressed
494 :``zip``: zip archive, compressed using deflate
494 :``zip``: zip archive, compressed using deflate
495
495
496 The exact name of the destination archive or directory is given
496 The exact name of the destination archive or directory is given
497 using a format string; see :hg:`help export` for details.
497 using a format string; see :hg:`help export` for details.
498
498
499 Each member added to an archive file has a directory prefix
499 Each member added to an archive file has a directory prefix
500 prepended. Use -p/--prefix to specify a format string for the
500 prepended. Use -p/--prefix to specify a format string for the
501 prefix. The default is the basename of the archive, with suffixes
501 prefix. The default is the basename of the archive, with suffixes
502 removed.
502 removed.
503
503
504 Returns 0 on success.
504 Returns 0 on success.
505 '''
505 '''
506
506
507 opts = pycompat.byteskwargs(opts)
507 opts = pycompat.byteskwargs(opts)
508 rev = opts.get('rev')
508 rev = opts.get('rev')
509 if rev:
509 if rev:
510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
511 ctx = scmutil.revsingle(repo, rev)
511 ctx = scmutil.revsingle(repo, rev)
512 if not ctx:
512 if not ctx:
513 raise error.Abort(_('no working directory: please specify a revision'))
513 raise error.Abort(_('no working directory: please specify a revision'))
514 node = ctx.node()
514 node = ctx.node()
515 dest = cmdutil.makefilename(ctx, dest)
515 dest = cmdutil.makefilename(ctx, dest)
516 if os.path.realpath(dest) == repo.root:
516 if os.path.realpath(dest) == repo.root:
517 raise error.Abort(_('repository root cannot be destination'))
517 raise error.Abort(_('repository root cannot be destination'))
518
518
519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
520 prefix = opts.get('prefix')
520 prefix = opts.get('prefix')
521
521
522 if dest == '-':
522 if dest == '-':
523 if kind == 'files':
523 if kind == 'files':
524 raise error.Abort(_('cannot archive plain files to stdout'))
524 raise error.Abort(_('cannot archive plain files to stdout'))
525 dest = cmdutil.makefileobj(ctx, dest)
525 dest = cmdutil.makefileobj(ctx, dest)
526 if not prefix:
526 if not prefix:
527 prefix = os.path.basename(repo.root) + '-%h'
527 prefix = os.path.basename(repo.root) + '-%h'
528
528
529 prefix = cmdutil.makefilename(ctx, prefix)
529 prefix = cmdutil.makefilename(ctx, prefix)
530 match = scmutil.match(ctx, [], opts)
530 match = scmutil.match(ctx, [], opts)
531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
532 match, prefix, subrepos=opts.get('subrepos'))
532 match, prefix, subrepos=opts.get('subrepos'))
533
533
534 @command('backout',
534 @command('backout',
535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
536 ('', 'commit', None,
536 ('', 'commit', None,
537 _('commit if no conflicts were encountered (DEPRECATED)')),
537 _('commit if no conflicts were encountered (DEPRECATED)')),
538 ('', 'no-commit', None, _('do not commit')),
538 ('', 'no-commit', None, _('do not commit')),
539 ('', 'parent', '',
539 ('', 'parent', '',
540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
541 ('r', 'rev', '', _('revision to backout'), _('REV')),
541 ('r', 'rev', '', _('revision to backout'), _('REV')),
542 ('e', 'edit', False, _('invoke editor on commit messages')),
542 ('e', 'edit', False, _('invoke editor on commit messages')),
543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
544 _('[OPTION]... [-r] REV'),
544 _('[OPTION]... [-r] REV'),
545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
546 def backout(ui, repo, node=None, rev=None, **opts):
546 def backout(ui, repo, node=None, rev=None, **opts):
547 '''reverse effect of earlier changeset
547 '''reverse effect of earlier changeset
548
548
549 Prepare a new changeset with the effect of REV undone in the
549 Prepare a new changeset with the effect of REV undone in the
550 current working directory. If no conflicts were encountered,
550 current working directory. If no conflicts were encountered,
551 it will be committed immediately.
551 it will be committed immediately.
552
552
553 If REV is the parent of the working directory, then this new changeset
553 If REV is the parent of the working directory, then this new changeset
554 is committed automatically (unless --no-commit is specified).
554 is committed automatically (unless --no-commit is specified).
555
555
556 .. note::
556 .. note::
557
557
558 :hg:`backout` cannot be used to fix either an unwanted or
558 :hg:`backout` cannot be used to fix either an unwanted or
559 incorrect merge.
559 incorrect merge.
560
560
561 .. container:: verbose
561 .. container:: verbose
562
562
563 Examples:
563 Examples:
564
564
565 - Reverse the effect of the parent of the working directory.
565 - Reverse the effect of the parent of the working directory.
566 This backout will be committed immediately::
566 This backout will be committed immediately::
567
567
568 hg backout -r .
568 hg backout -r .
569
569
570 - Reverse the effect of previous bad revision 23::
570 - Reverse the effect of previous bad revision 23::
571
571
572 hg backout -r 23
572 hg backout -r 23
573
573
574 - Reverse the effect of previous bad revision 23 and
574 - Reverse the effect of previous bad revision 23 and
575 leave changes uncommitted::
575 leave changes uncommitted::
576
576
577 hg backout -r 23 --no-commit
577 hg backout -r 23 --no-commit
578 hg commit -m "Backout revision 23"
578 hg commit -m "Backout revision 23"
579
579
580 By default, the pending changeset will have one parent,
580 By default, the pending changeset will have one parent,
581 maintaining a linear history. With --merge, the pending
581 maintaining a linear history. With --merge, the pending
582 changeset will instead have two parents: the old parent of the
582 changeset will instead have two parents: the old parent of the
583 working directory and a new child of REV that simply undoes REV.
583 working directory and a new child of REV that simply undoes REV.
584
584
585 Before version 1.7, the behavior without --merge was equivalent
585 Before version 1.7, the behavior without --merge was equivalent
586 to specifying --merge followed by :hg:`update --clean .` to
586 to specifying --merge followed by :hg:`update --clean .` to
587 cancel the merge and leave the child of REV as a head to be
587 cancel the merge and leave the child of REV as a head to be
588 merged separately.
588 merged separately.
589
589
590 See :hg:`help dates` for a list of formats valid for -d/--date.
590 See :hg:`help dates` for a list of formats valid for -d/--date.
591
591
592 See :hg:`help revert` for a way to restore files to the state
592 See :hg:`help revert` for a way to restore files to the state
593 of another revision.
593 of another revision.
594
594
595 Returns 0 on success, 1 if nothing to backout or there are unresolved
595 Returns 0 on success, 1 if nothing to backout or there are unresolved
596 files.
596 files.
597 '''
597 '''
598 with repo.wlock(), repo.lock():
598 with repo.wlock(), repo.lock():
599 return _dobackout(ui, repo, node, rev, **opts)
599 return _dobackout(ui, repo, node, rev, **opts)
600
600
601 def _dobackout(ui, repo, node=None, rev=None, **opts):
601 def _dobackout(ui, repo, node=None, rev=None, **opts):
602 opts = pycompat.byteskwargs(opts)
602 opts = pycompat.byteskwargs(opts)
603 if opts.get('commit') and opts.get('no_commit'):
603 if opts.get('commit') and opts.get('no_commit'):
604 raise error.Abort(_("cannot use --commit with --no-commit"))
604 raise error.Abort(_("cannot use --commit with --no-commit"))
605 if opts.get('merge') and opts.get('no_commit'):
605 if opts.get('merge') and opts.get('no_commit'):
606 raise error.Abort(_("cannot use --merge with --no-commit"))
606 raise error.Abort(_("cannot use --merge with --no-commit"))
607
607
608 if rev and node:
608 if rev and node:
609 raise error.Abort(_("please specify just one revision"))
609 raise error.Abort(_("please specify just one revision"))
610
610
611 if not rev:
611 if not rev:
612 rev = node
612 rev = node
613
613
614 if not rev:
614 if not rev:
615 raise error.Abort(_("please specify a revision to backout"))
615 raise error.Abort(_("please specify a revision to backout"))
616
616
617 date = opts.get('date')
617 date = opts.get('date')
618 if date:
618 if date:
619 opts['date'] = dateutil.parsedate(date)
619 opts['date'] = dateutil.parsedate(date)
620
620
621 cmdutil.checkunfinished(repo)
621 cmdutil.checkunfinished(repo)
622 cmdutil.bailifchanged(repo)
622 cmdutil.bailifchanged(repo)
623 node = scmutil.revsingle(repo, rev).node()
623 node = scmutil.revsingle(repo, rev).node()
624
624
625 op1, op2 = repo.dirstate.parents()
625 op1, op2 = repo.dirstate.parents()
626 if not repo.changelog.isancestor(node, op1):
626 if not repo.changelog.isancestor(node, op1):
627 raise error.Abort(_('cannot backout change that is not an ancestor'))
627 raise error.Abort(_('cannot backout change that is not an ancestor'))
628
628
629 p1, p2 = repo.changelog.parents(node)
629 p1, p2 = repo.changelog.parents(node)
630 if p1 == nullid:
630 if p1 == nullid:
631 raise error.Abort(_('cannot backout a change with no parents'))
631 raise error.Abort(_('cannot backout a change with no parents'))
632 if p2 != nullid:
632 if p2 != nullid:
633 if not opts.get('parent'):
633 if not opts.get('parent'):
634 raise error.Abort(_('cannot backout a merge changeset'))
634 raise error.Abort(_('cannot backout a merge changeset'))
635 p = repo.lookup(opts['parent'])
635 p = repo.lookup(opts['parent'])
636 if p not in (p1, p2):
636 if p not in (p1, p2):
637 raise error.Abort(_('%s is not a parent of %s') %
637 raise error.Abort(_('%s is not a parent of %s') %
638 (short(p), short(node)))
638 (short(p), short(node)))
639 parent = p
639 parent = p
640 else:
640 else:
641 if opts.get('parent'):
641 if opts.get('parent'):
642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
643 parent = p1
643 parent = p1
644
644
645 # the backout should appear on the same branch
645 # the backout should appear on the same branch
646 branch = repo.dirstate.branch()
646 branch = repo.dirstate.branch()
647 bheads = repo.branchheads(branch)
647 bheads = repo.branchheads(branch)
648 rctx = scmutil.revsingle(repo, hex(parent))
648 rctx = scmutil.revsingle(repo, hex(parent))
649 if not opts.get('merge') and op1 != node:
649 if not opts.get('merge') and op1 != node:
650 with dirstateguard.dirstateguard(repo, 'backout'):
650 with dirstateguard.dirstateguard(repo, 'backout'):
651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
652 with ui.configoverride(overrides, 'backout'):
652 with ui.configoverride(overrides, 'backout'):
653 stats = mergemod.update(repo, parent, branchmerge=True,
653 stats = mergemod.update(repo, parent, branchmerge=True,
654 force=True, ancestor=node,
654 force=True, ancestor=node,
655 mergeancestor=False)
655 mergeancestor=False)
656 repo.setparents(op1, op2)
656 repo.setparents(op1, op2)
657 hg._showstats(repo, stats)
657 hg._showstats(repo, stats)
658 if stats.unresolvedcount:
658 if stats.unresolvedcount:
659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
660 "file merges\n"))
660 "file merges\n"))
661 return 1
661 return 1
662 else:
662 else:
663 hg.clean(repo, node, show_stats=False)
663 hg.clean(repo, node, show_stats=False)
664 repo.dirstate.setbranch(branch)
664 repo.dirstate.setbranch(branch)
665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
666
666
667 if opts.get('no_commit'):
667 if opts.get('no_commit'):
668 msg = _("changeset %s backed out, "
668 msg = _("changeset %s backed out, "
669 "don't forget to commit.\n")
669 "don't forget to commit.\n")
670 ui.status(msg % short(node))
670 ui.status(msg % short(node))
671 return 0
671 return 0
672
672
673 def commitfunc(ui, repo, message, match, opts):
673 def commitfunc(ui, repo, message, match, opts):
674 editform = 'backout'
674 editform = 'backout'
675 e = cmdutil.getcommiteditor(editform=editform,
675 e = cmdutil.getcommiteditor(editform=editform,
676 **pycompat.strkwargs(opts))
676 **pycompat.strkwargs(opts))
677 if not message:
677 if not message:
678 # we don't translate commit messages
678 # we don't translate commit messages
679 message = "Backed out changeset %s" % short(node)
679 message = "Backed out changeset %s" % short(node)
680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
681 return repo.commit(message, opts.get('user'), opts.get('date'),
681 return repo.commit(message, opts.get('user'), opts.get('date'),
682 match, editor=e)
682 match, editor=e)
683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
684 if not newnode:
684 if not newnode:
685 ui.status(_("nothing changed\n"))
685 ui.status(_("nothing changed\n"))
686 return 1
686 return 1
687 cmdutil.commitstatus(repo, newnode, branch, bheads)
687 cmdutil.commitstatus(repo, newnode, branch, bheads)
688
688
689 def nice(node):
689 def nice(node):
690 return '%d:%s' % (repo.changelog.rev(node), short(node))
690 return '%d:%s' % (repo.changelog.rev(node), short(node))
691 ui.status(_('changeset %s backs out changeset %s\n') %
691 ui.status(_('changeset %s backs out changeset %s\n') %
692 (nice(repo.changelog.tip()), nice(node)))
692 (nice(repo.changelog.tip()), nice(node)))
693 if opts.get('merge') and op1 != node:
693 if opts.get('merge') and op1 != node:
694 hg.clean(repo, op1, show_stats=False)
694 hg.clean(repo, op1, show_stats=False)
695 ui.status(_('merging with changeset %s\n')
695 ui.status(_('merging with changeset %s\n')
696 % nice(repo.changelog.tip()))
696 % nice(repo.changelog.tip()))
697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
698 with ui.configoverride(overrides, 'backout'):
698 with ui.configoverride(overrides, 'backout'):
699 return hg.merge(repo, hex(repo.changelog.tip()))
699 return hg.merge(repo, hex(repo.changelog.tip()))
700 return 0
700 return 0
701
701
702 @command('bisect',
702 @command('bisect',
703 [('r', 'reset', False, _('reset bisect state')),
703 [('r', 'reset', False, _('reset bisect state')),
704 ('g', 'good', False, _('mark changeset good')),
704 ('g', 'good', False, _('mark changeset good')),
705 ('b', 'bad', False, _('mark changeset bad')),
705 ('b', 'bad', False, _('mark changeset bad')),
706 ('s', 'skip', False, _('skip testing changeset')),
706 ('s', 'skip', False, _('skip testing changeset')),
707 ('e', 'extend', False, _('extend the bisect range')),
707 ('e', 'extend', False, _('extend the bisect range')),
708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
709 ('U', 'noupdate', False, _('do not update to target'))],
709 ('U', 'noupdate', False, _('do not update to target'))],
710 _("[-gbsr] [-U] [-c CMD] [REV]"),
710 _("[-gbsr] [-U] [-c CMD] [REV]"),
711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
712 def bisect(ui, repo, rev=None, extra=None, command=None,
712 def bisect(ui, repo, rev=None, extra=None, command=None,
713 reset=None, good=None, bad=None, skip=None, extend=None,
713 reset=None, good=None, bad=None, skip=None, extend=None,
714 noupdate=None):
714 noupdate=None):
715 """subdivision search of changesets
715 """subdivision search of changesets
716
716
717 This command helps to find changesets which introduce problems. To
717 This command helps to find changesets which introduce problems. To
718 use, mark the earliest changeset you know exhibits the problem as
718 use, mark the earliest changeset you know exhibits the problem as
719 bad, then mark the latest changeset which is free from the problem
719 bad, then mark the latest changeset which is free from the problem
720 as good. Bisect will update your working directory to a revision
720 as good. Bisect will update your working directory to a revision
721 for testing (unless the -U/--noupdate option is specified). Once
721 for testing (unless the -U/--noupdate option is specified). Once
722 you have performed tests, mark the working directory as good or
722 you have performed tests, mark the working directory as good or
723 bad, and bisect will either update to another candidate changeset
723 bad, and bisect will either update to another candidate changeset
724 or announce that it has found the bad revision.
724 or announce that it has found the bad revision.
725
725
726 As a shortcut, you can also use the revision argument to mark a
726 As a shortcut, you can also use the revision argument to mark a
727 revision as good or bad without checking it out first.
727 revision as good or bad without checking it out first.
728
728
729 If you supply a command, it will be used for automatic bisection.
729 If you supply a command, it will be used for automatic bisection.
730 The environment variable HG_NODE will contain the ID of the
730 The environment variable HG_NODE will contain the ID of the
731 changeset being tested. The exit status of the command will be
731 changeset being tested. The exit status of the command will be
732 used to mark revisions as good or bad: status 0 means good, 125
732 used to mark revisions as good or bad: status 0 means good, 125
733 means to skip the revision, 127 (command not found) will abort the
733 means to skip the revision, 127 (command not found) will abort the
734 bisection, and any other non-zero exit status means the revision
734 bisection, and any other non-zero exit status means the revision
735 is bad.
735 is bad.
736
736
737 .. container:: verbose
737 .. container:: verbose
738
738
739 Some examples:
739 Some examples:
740
740
741 - start a bisection with known bad revision 34, and good revision 12::
741 - start a bisection with known bad revision 34, and good revision 12::
742
742
743 hg bisect --bad 34
743 hg bisect --bad 34
744 hg bisect --good 12
744 hg bisect --good 12
745
745
746 - advance the current bisection by marking current revision as good or
746 - advance the current bisection by marking current revision as good or
747 bad::
747 bad::
748
748
749 hg bisect --good
749 hg bisect --good
750 hg bisect --bad
750 hg bisect --bad
751
751
752 - mark the current revision, or a known revision, to be skipped (e.g. if
752 - mark the current revision, or a known revision, to be skipped (e.g. if
753 that revision is not usable because of another issue)::
753 that revision is not usable because of another issue)::
754
754
755 hg bisect --skip
755 hg bisect --skip
756 hg bisect --skip 23
756 hg bisect --skip 23
757
757
758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
759
759
760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
761
761
762 - forget the current bisection::
762 - forget the current bisection::
763
763
764 hg bisect --reset
764 hg bisect --reset
765
765
766 - use 'make && make tests' to automatically find the first broken
766 - use 'make && make tests' to automatically find the first broken
767 revision::
767 revision::
768
768
769 hg bisect --reset
769 hg bisect --reset
770 hg bisect --bad 34
770 hg bisect --bad 34
771 hg bisect --good 12
771 hg bisect --good 12
772 hg bisect --command "make && make tests"
772 hg bisect --command "make && make tests"
773
773
774 - see all changesets whose states are already known in the current
774 - see all changesets whose states are already known in the current
775 bisection::
775 bisection::
776
776
777 hg log -r "bisect(pruned)"
777 hg log -r "bisect(pruned)"
778
778
779 - see the changeset currently being bisected (especially useful
779 - see the changeset currently being bisected (especially useful
780 if running with -U/--noupdate)::
780 if running with -U/--noupdate)::
781
781
782 hg log -r "bisect(current)"
782 hg log -r "bisect(current)"
783
783
784 - see all changesets that took part in the current bisection::
784 - see all changesets that took part in the current bisection::
785
785
786 hg log -r "bisect(range)"
786 hg log -r "bisect(range)"
787
787
788 - you can even get a nice graph::
788 - you can even get a nice graph::
789
789
790 hg log --graph -r "bisect(range)"
790 hg log --graph -r "bisect(range)"
791
791
792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
793
793
794 Returns 0 on success.
794 Returns 0 on success.
795 """
795 """
796 # backward compatibility
796 # backward compatibility
797 if rev in "good bad reset init".split():
797 if rev in "good bad reset init".split():
798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
799 cmd, rev, extra = rev, extra, None
799 cmd, rev, extra = rev, extra, None
800 if cmd == "good":
800 if cmd == "good":
801 good = True
801 good = True
802 elif cmd == "bad":
802 elif cmd == "bad":
803 bad = True
803 bad = True
804 else:
804 else:
805 reset = True
805 reset = True
806 elif extra:
806 elif extra:
807 raise error.Abort(_('incompatible arguments'))
807 raise error.Abort(_('incompatible arguments'))
808
808
809 incompatibles = {
809 incompatibles = {
810 '--bad': bad,
810 '--bad': bad,
811 '--command': bool(command),
811 '--command': bool(command),
812 '--extend': extend,
812 '--extend': extend,
813 '--good': good,
813 '--good': good,
814 '--reset': reset,
814 '--reset': reset,
815 '--skip': skip,
815 '--skip': skip,
816 }
816 }
817
817
818 enabled = [x for x in incompatibles if incompatibles[x]]
818 enabled = [x for x in incompatibles if incompatibles[x]]
819
819
820 if len(enabled) > 1:
820 if len(enabled) > 1:
821 raise error.Abort(_('%s and %s are incompatible') %
821 raise error.Abort(_('%s and %s are incompatible') %
822 tuple(sorted(enabled)[0:2]))
822 tuple(sorted(enabled)[0:2]))
823
823
824 if reset:
824 if reset:
825 hbisect.resetstate(repo)
825 hbisect.resetstate(repo)
826 return
826 return
827
827
828 state = hbisect.load_state(repo)
828 state = hbisect.load_state(repo)
829
829
830 # update state
830 # update state
831 if good or bad or skip:
831 if good or bad or skip:
832 if rev:
832 if rev:
833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
834 else:
834 else:
835 nodes = [repo.lookup('.')]
835 nodes = [repo.lookup('.')]
836 if good:
836 if good:
837 state['good'] += nodes
837 state['good'] += nodes
838 elif bad:
838 elif bad:
839 state['bad'] += nodes
839 state['bad'] += nodes
840 elif skip:
840 elif skip:
841 state['skip'] += nodes
841 state['skip'] += nodes
842 hbisect.save_state(repo, state)
842 hbisect.save_state(repo, state)
843 if not (state['good'] and state['bad']):
843 if not (state['good'] and state['bad']):
844 return
844 return
845
845
846 def mayupdate(repo, node, show_stats=True):
846 def mayupdate(repo, node, show_stats=True):
847 """common used update sequence"""
847 """common used update sequence"""
848 if noupdate:
848 if noupdate:
849 return
849 return
850 cmdutil.checkunfinished(repo)
850 cmdutil.checkunfinished(repo)
851 cmdutil.bailifchanged(repo)
851 cmdutil.bailifchanged(repo)
852 return hg.clean(repo, node, show_stats=show_stats)
852 return hg.clean(repo, node, show_stats=show_stats)
853
853
854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
855
855
856 if command:
856 if command:
857 changesets = 1
857 changesets = 1
858 if noupdate:
858 if noupdate:
859 try:
859 try:
860 node = state['current'][0]
860 node = state['current'][0]
861 except LookupError:
861 except LookupError:
862 raise error.Abort(_('current bisect revision is unknown - '
862 raise error.Abort(_('current bisect revision is unknown - '
863 'start a new bisect to fix'))
863 'start a new bisect to fix'))
864 else:
864 else:
865 node, p2 = repo.dirstate.parents()
865 node, p2 = repo.dirstate.parents()
866 if p2 != nullid:
866 if p2 != nullid:
867 raise error.Abort(_('current bisect revision is a merge'))
867 raise error.Abort(_('current bisect revision is a merge'))
868 if rev:
868 if rev:
869 node = repo[scmutil.revsingle(repo, rev, node)].node()
869 node = repo[scmutil.revsingle(repo, rev, node)].node()
870 try:
870 try:
871 while changesets:
871 while changesets:
872 # update state
872 # update state
873 state['current'] = [node]
873 state['current'] = [node]
874 hbisect.save_state(repo, state)
874 hbisect.save_state(repo, state)
875 status = ui.system(command, environ={'HG_NODE': hex(node)},
875 status = ui.system(command, environ={'HG_NODE': hex(node)},
876 blockedtag='bisect_check')
876 blockedtag='bisect_check')
877 if status == 125:
877 if status == 125:
878 transition = "skip"
878 transition = "skip"
879 elif status == 0:
879 elif status == 0:
880 transition = "good"
880 transition = "good"
881 # status < 0 means process was killed
881 # status < 0 means process was killed
882 elif status == 127:
882 elif status == 127:
883 raise error.Abort(_("failed to execute %s") % command)
883 raise error.Abort(_("failed to execute %s") % command)
884 elif status < 0:
884 elif status < 0:
885 raise error.Abort(_("%s killed") % command)
885 raise error.Abort(_("%s killed") % command)
886 else:
886 else:
887 transition = "bad"
887 transition = "bad"
888 state[transition].append(node)
888 state[transition].append(node)
889 ctx = repo[node]
889 ctx = repo[node]
890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
891 transition))
891 transition))
892 hbisect.checkstate(state)
892 hbisect.checkstate(state)
893 # bisect
893 # bisect
894 nodes, changesets, bgood = hbisect.bisect(repo, state)
894 nodes, changesets, bgood = hbisect.bisect(repo, state)
895 # update to next check
895 # update to next check
896 node = nodes[0]
896 node = nodes[0]
897 mayupdate(repo, node, show_stats=False)
897 mayupdate(repo, node, show_stats=False)
898 finally:
898 finally:
899 state['current'] = [node]
899 state['current'] = [node]
900 hbisect.save_state(repo, state)
900 hbisect.save_state(repo, state)
901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
902 return
902 return
903
903
904 hbisect.checkstate(state)
904 hbisect.checkstate(state)
905
905
906 # actually bisect
906 # actually bisect
907 nodes, changesets, good = hbisect.bisect(repo, state)
907 nodes, changesets, good = hbisect.bisect(repo, state)
908 if extend:
908 if extend:
909 if not changesets:
909 if not changesets:
910 extendnode = hbisect.extendrange(repo, state, nodes, good)
910 extendnode = hbisect.extendrange(repo, state, nodes, good)
911 if extendnode is not None:
911 if extendnode is not None:
912 ui.write(_("Extending search to changeset %d:%s\n")
912 ui.write(_("Extending search to changeset %d:%s\n")
913 % (extendnode.rev(), extendnode))
913 % (extendnode.rev(), extendnode))
914 state['current'] = [extendnode.node()]
914 state['current'] = [extendnode.node()]
915 hbisect.save_state(repo, state)
915 hbisect.save_state(repo, state)
916 return mayupdate(repo, extendnode.node())
916 return mayupdate(repo, extendnode.node())
917 raise error.Abort(_("nothing to extend"))
917 raise error.Abort(_("nothing to extend"))
918
918
919 if changesets == 0:
919 if changesets == 0:
920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
921 else:
921 else:
922 assert len(nodes) == 1 # only a single node can be tested next
922 assert len(nodes) == 1 # only a single node can be tested next
923 node = nodes[0]
923 node = nodes[0]
924 # compute the approximate number of remaining tests
924 # compute the approximate number of remaining tests
925 tests, size = 0, 2
925 tests, size = 0, 2
926 while size <= changesets:
926 while size <= changesets:
927 tests, size = tests + 1, size * 2
927 tests, size = tests + 1, size * 2
928 rev = repo.changelog.rev(node)
928 rev = repo.changelog.rev(node)
929 ui.write(_("Testing changeset %d:%s "
929 ui.write(_("Testing changeset %d:%s "
930 "(%d changesets remaining, ~%d tests)\n")
930 "(%d changesets remaining, ~%d tests)\n")
931 % (rev, short(node), changesets, tests))
931 % (rev, short(node), changesets, tests))
932 state['current'] = [node]
932 state['current'] = [node]
933 hbisect.save_state(repo, state)
933 hbisect.save_state(repo, state)
934 return mayupdate(repo, node)
934 return mayupdate(repo, node)
935
935
936 @command('bookmarks|bookmark',
936 @command('bookmarks|bookmark',
937 [('f', 'force', False, _('force')),
937 [('f', 'force', False, _('force')),
938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
939 ('d', 'delete', False, _('delete a given bookmark')),
939 ('d', 'delete', False, _('delete a given bookmark')),
940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
941 ('i', 'inactive', False, _('mark a bookmark inactive')),
941 ('i', 'inactive', False, _('mark a bookmark inactive')),
942 ('l', 'list', False, _('list existing bookmarks')),
942 ('l', 'list', False, _('list existing bookmarks')),
943 ] + formatteropts,
943 ] + formatteropts,
944 _('hg bookmarks [OPTIONS]... [NAME]...'),
944 _('hg bookmarks [OPTIONS]... [NAME]...'),
945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
946 def bookmark(ui, repo, *names, **opts):
946 def bookmark(ui, repo, *names, **opts):
947 '''create a new bookmark or list existing bookmarks
947 '''create a new bookmark or list existing bookmarks
948
948
949 Bookmarks are labels on changesets to help track lines of development.
949 Bookmarks are labels on changesets to help track lines of development.
950 Bookmarks are unversioned and can be moved, renamed and deleted.
950 Bookmarks are unversioned and can be moved, renamed and deleted.
951 Deleting or moving a bookmark has no effect on the associated changesets.
951 Deleting or moving a bookmark has no effect on the associated changesets.
952
952
953 Creating or updating to a bookmark causes it to be marked as 'active'.
953 Creating or updating to a bookmark causes it to be marked as 'active'.
954 The active bookmark is indicated with a '*'.
954 The active bookmark is indicated with a '*'.
955 When a commit is made, the active bookmark will advance to the new commit.
955 When a commit is made, the active bookmark will advance to the new commit.
956 A plain :hg:`update` will also advance an active bookmark, if possible.
956 A plain :hg:`update` will also advance an active bookmark, if possible.
957 Updating away from a bookmark will cause it to be deactivated.
957 Updating away from a bookmark will cause it to be deactivated.
958
958
959 Bookmarks can be pushed and pulled between repositories (see
959 Bookmarks can be pushed and pulled between repositories (see
960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
961 diverged, a new 'divergent bookmark' of the form 'name@path' will
961 diverged, a new 'divergent bookmark' of the form 'name@path' will
962 be created. Using :hg:`merge` will resolve the divergence.
962 be created. Using :hg:`merge` will resolve the divergence.
963
963
964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
965 the active bookmark's name.
965 the active bookmark's name.
966
966
967 A bookmark named '@' has the special property that :hg:`clone` will
967 A bookmark named '@' has the special property that :hg:`clone` will
968 check it out by default if it exists.
968 check it out by default if it exists.
969
969
970 .. container:: verbose
970 .. container:: verbose
971
971
972 Template:
972 Template:
973
973
974 The following keywords are supported in addition to the common template
974 The following keywords are supported in addition to the common template
975 keywords and functions such as ``{bookmark}``. See also
975 keywords and functions such as ``{bookmark}``. See also
976 :hg:`help templates`.
976 :hg:`help templates`.
977
977
978 :active: Boolean. True if the bookmark is active.
978 :active: Boolean. True if the bookmark is active.
979
979
980 Examples:
980 Examples:
981
981
982 - create an active bookmark for a new line of development::
982 - create an active bookmark for a new line of development::
983
983
984 hg book new-feature
984 hg book new-feature
985
985
986 - create an inactive bookmark as a place marker::
986 - create an inactive bookmark as a place marker::
987
987
988 hg book -i reviewed
988 hg book -i reviewed
989
989
990 - create an inactive bookmark on another changeset::
990 - create an inactive bookmark on another changeset::
991
991
992 hg book -r .^ tested
992 hg book -r .^ tested
993
993
994 - rename bookmark turkey to dinner::
994 - rename bookmark turkey to dinner::
995
995
996 hg book -m turkey dinner
996 hg book -m turkey dinner
997
997
998 - move the '@' bookmark from another branch::
998 - move the '@' bookmark from another branch::
999
999
1000 hg book -f @
1000 hg book -f @
1001
1001
1002 - print only the active bookmark name::
1002 - print only the active bookmark name::
1003
1003
1004 hg book -ql .
1004 hg book -ql .
1005 '''
1005 '''
1006 opts = pycompat.byteskwargs(opts)
1006 opts = pycompat.byteskwargs(opts)
1007 force = opts.get('force')
1007 force = opts.get('force')
1008 rev = opts.get('rev')
1008 rev = opts.get('rev')
1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1010
1010
1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1012 if len(selactions) > 1:
1012 if len(selactions) > 1:
1013 raise error.Abort(_('--%s and --%s are incompatible')
1013 raise error.Abort(_('--%s and --%s are incompatible')
1014 % tuple(selactions[:2]))
1014 % tuple(selactions[:2]))
1015 if selactions:
1015 if selactions:
1016 action = selactions[0]
1016 action = selactions[0]
1017 elif names or rev:
1017 elif names or rev:
1018 action = 'add'
1018 action = 'add'
1019 elif inactive:
1019 elif inactive:
1020 action = 'inactive' # meaning deactivate
1020 action = 'inactive' # meaning deactivate
1021 else:
1021 else:
1022 action = 'list'
1022 action = 'list'
1023
1023
1024 if rev and action in {'delete', 'rename', 'list'}:
1024 if rev and action in {'delete', 'rename', 'list'}:
1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1026 if inactive and action in {'delete', 'list'}:
1026 if inactive and action in {'delete', 'list'}:
1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1028 if not names and action in {'add', 'delete'}:
1028 if not names and action in {'add', 'delete'}:
1029 raise error.Abort(_("bookmark name required"))
1029 raise error.Abort(_("bookmark name required"))
1030
1030
1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1033 if action == 'delete':
1033 if action == 'delete':
1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1035 bookmarks.delete(repo, tr, names)
1035 bookmarks.delete(repo, tr, names)
1036 elif action == 'rename':
1036 elif action == 'rename':
1037 if not names:
1037 if not names:
1038 raise error.Abort(_("new bookmark name required"))
1038 raise error.Abort(_("new bookmark name required"))
1039 elif len(names) > 1:
1039 elif len(names) > 1:
1040 raise error.Abort(_("only one new bookmark name allowed"))
1040 raise error.Abort(_("only one new bookmark name allowed"))
1041 oldname = repo._bookmarks.expandname(opts['rename'])
1041 oldname = repo._bookmarks.expandname(opts['rename'])
1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1043 elif action == 'add':
1043 elif action == 'add':
1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1045 elif action == 'inactive':
1045 elif action == 'inactive':
1046 if len(repo._bookmarks) == 0:
1046 if len(repo._bookmarks) == 0:
1047 ui.status(_("no bookmarks set\n"))
1047 ui.status(_("no bookmarks set\n"))
1048 elif not repo._activebookmark:
1048 elif not repo._activebookmark:
1049 ui.status(_("no active bookmark\n"))
1049 ui.status(_("no active bookmark\n"))
1050 else:
1050 else:
1051 bookmarks.deactivate(repo)
1051 bookmarks.deactivate(repo)
1052 elif action == 'list':
1052 elif action == 'list':
1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1054 with ui.formatter('bookmarks', opts) as fm:
1054 with ui.formatter('bookmarks', opts) as fm:
1055 bookmarks.printbookmarks(ui, repo, fm, names)
1055 bookmarks.printbookmarks(ui, repo, fm, names)
1056 else:
1056 else:
1057 raise error.ProgrammingError('invalid action: %s' % action)
1057 raise error.ProgrammingError('invalid action: %s' % action)
1058
1058
1059 @command('branch',
1059 @command('branch',
1060 [('f', 'force', None,
1060 [('f', 'force', None,
1061 _('set branch name even if it shadows an existing branch')),
1061 _('set branch name even if it shadows an existing branch')),
1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1064 ],
1064 ],
1065 _('[-fC] [NAME]'),
1065 _('[-fC] [NAME]'),
1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1067 def branch(ui, repo, label=None, **opts):
1067 def branch(ui, repo, label=None, **opts):
1068 """set or show the current branch name
1068 """set or show the current branch name
1069
1069
1070 .. note::
1070 .. note::
1071
1071
1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1073 light-weight bookmark instead. See :hg:`help glossary` for more
1073 light-weight bookmark instead. See :hg:`help glossary` for more
1074 information about named branches and bookmarks.
1074 information about named branches and bookmarks.
1075
1075
1076 With no argument, show the current branch name. With one argument,
1076 With no argument, show the current branch name. With one argument,
1077 set the working directory branch name (the branch will not exist
1077 set the working directory branch name (the branch will not exist
1078 in the repository until the next commit). Standard practice
1078 in the repository until the next commit). Standard practice
1079 recommends that primary development take place on the 'default'
1079 recommends that primary development take place on the 'default'
1080 branch.
1080 branch.
1081
1081
1082 Unless -f/--force is specified, branch will not let you set a
1082 Unless -f/--force is specified, branch will not let you set a
1083 branch name that already exists.
1083 branch name that already exists.
1084
1084
1085 Use -C/--clean to reset the working directory branch to that of
1085 Use -C/--clean to reset the working directory branch to that of
1086 the parent of the working directory, negating a previous branch
1086 the parent of the working directory, negating a previous branch
1087 change.
1087 change.
1088
1088
1089 Use the command :hg:`update` to switch to an existing branch. Use
1089 Use the command :hg:`update` to switch to an existing branch. Use
1090 :hg:`commit --close-branch` to mark this branch head as closed.
1090 :hg:`commit --close-branch` to mark this branch head as closed.
1091 When all heads of a branch are closed, the branch will be
1091 When all heads of a branch are closed, the branch will be
1092 considered closed.
1092 considered closed.
1093
1093
1094 Returns 0 on success.
1094 Returns 0 on success.
1095 """
1095 """
1096 opts = pycompat.byteskwargs(opts)
1096 opts = pycompat.byteskwargs(opts)
1097 revs = opts.get('rev')
1097 revs = opts.get('rev')
1098 if label:
1098 if label:
1099 label = label.strip()
1099 label = label.strip()
1100
1100
1101 if not opts.get('clean') and not label:
1101 if not opts.get('clean') and not label:
1102 if revs:
1102 if revs:
1103 raise error.Abort(_("no branch name specified for the revisions"))
1103 raise error.Abort(_("no branch name specified for the revisions"))
1104 ui.write("%s\n" % repo.dirstate.branch())
1104 ui.write("%s\n" % repo.dirstate.branch())
1105 return
1105 return
1106
1106
1107 with repo.wlock():
1107 with repo.wlock():
1108 if opts.get('clean'):
1108 if opts.get('clean'):
1109 label = repo['.'].branch()
1109 label = repo['.'].branch()
1110 repo.dirstate.setbranch(label)
1110 repo.dirstate.setbranch(label)
1111 ui.status(_('reset working directory to branch %s\n') % label)
1111 ui.status(_('reset working directory to branch %s\n') % label)
1112 elif label:
1112 elif label:
1113
1113
1114 scmutil.checknewlabel(repo, label, 'branch')
1114 scmutil.checknewlabel(repo, label, 'branch')
1115 if revs:
1115 if revs:
1116 return cmdutil.changebranch(ui, repo, revs, label)
1116 return cmdutil.changebranch(ui, repo, revs, label)
1117
1117
1118 if not opts.get('force') and label in repo.branchmap():
1118 if not opts.get('force') and label in repo.branchmap():
1119 if label not in [p.branch() for p in repo[None].parents()]:
1119 if label not in [p.branch() for p in repo[None].parents()]:
1120 raise error.Abort(_('a branch of the same name already'
1120 raise error.Abort(_('a branch of the same name already'
1121 ' exists'),
1121 ' exists'),
1122 # i18n: "it" refers to an existing branch
1122 # i18n: "it" refers to an existing branch
1123 hint=_("use 'hg update' to switch to it"))
1123 hint=_("use 'hg update' to switch to it"))
1124
1124
1125 repo.dirstate.setbranch(label)
1125 repo.dirstate.setbranch(label)
1126 ui.status(_('marked working directory as branch %s\n') % label)
1126 ui.status(_('marked working directory as branch %s\n') % label)
1127
1127
1128 # find any open named branches aside from default
1128 # find any open named branches aside from default
1129 for n, h, t, c in repo.branchmap().iterbranches():
1129 for n, h, t, c in repo.branchmap().iterbranches():
1130 if n != "default" and not c:
1130 if n != "default" and not c:
1131 return 0
1131 return 0
1132 ui.status(_('(branches are permanent and global, '
1132 ui.status(_('(branches are permanent and global, '
1133 'did you want a bookmark?)\n'))
1133 'did you want a bookmark?)\n'))
1134
1134
1135 @command('branches',
1135 @command('branches',
1136 [('a', 'active', False,
1136 [('a', 'active', False,
1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1138 ('c', 'closed', False, _('show normal and closed branches')),
1138 ('c', 'closed', False, _('show normal and closed branches')),
1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1140 ] + formatteropts,
1140 ] + formatteropts,
1141 _('[-c]'),
1141 _('[-c]'),
1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1143 intents={INTENT_READONLY})
1143 intents={INTENT_READONLY})
1144 def branches(ui, repo, active=False, closed=False, **opts):
1144 def branches(ui, repo, active=False, closed=False, **opts):
1145 """list repository named branches
1145 """list repository named branches
1146
1146
1147 List the repository's named branches, indicating which ones are
1147 List the repository's named branches, indicating which ones are
1148 inactive. If -c/--closed is specified, also list branches which have
1148 inactive. If -c/--closed is specified, also list branches which have
1149 been marked closed (see :hg:`commit --close-branch`).
1149 been marked closed (see :hg:`commit --close-branch`).
1150
1150
1151 Use the command :hg:`update` to switch to an existing branch.
1151 Use the command :hg:`update` to switch to an existing branch.
1152
1152
1153 .. container:: verbose
1153 .. container:: verbose
1154
1154
1155 Template:
1155 Template:
1156
1156
1157 The following keywords are supported in addition to the common template
1157 The following keywords are supported in addition to the common template
1158 keywords and functions such as ``{branch}``. See also
1158 keywords and functions such as ``{branch}``. See also
1159 :hg:`help templates`.
1159 :hg:`help templates`.
1160
1160
1161 :active: Boolean. True if the branch is active.
1161 :active: Boolean. True if the branch is active.
1162 :closed: Boolean. True if the branch is closed.
1162 :closed: Boolean. True if the branch is closed.
1163 :current: Boolean. True if it is the current branch.
1163 :current: Boolean. True if it is the current branch.
1164
1164
1165 Returns 0.
1165 Returns 0.
1166 """
1166 """
1167
1167
1168 opts = pycompat.byteskwargs(opts)
1168 opts = pycompat.byteskwargs(opts)
1169 revs = opts.get('rev')
1169 revs = opts.get('rev')
1170 selectedbranches = None
1170 selectedbranches = None
1171 if revs:
1171 if revs:
1172 revs = scmutil.revrange(repo, revs)
1172 revs = scmutil.revrange(repo, revs)
1173 getbi = repo.revbranchcache().branchinfo
1173 getbi = repo.revbranchcache().branchinfo
1174 selectedbranches = {getbi(r)[0] for r in revs}
1174 selectedbranches = {getbi(r)[0] for r in revs}
1175
1175
1176 ui.pager('branches')
1176 ui.pager('branches')
1177 fm = ui.formatter('branches', opts)
1177 fm = ui.formatter('branches', opts)
1178 hexfunc = fm.hexfunc
1178 hexfunc = fm.hexfunc
1179
1179
1180 allheads = set(repo.heads())
1180 allheads = set(repo.heads())
1181 branches = []
1181 branches = []
1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1183 if selectedbranches is not None and tag not in selectedbranches:
1183 if selectedbranches is not None and tag not in selectedbranches:
1184 continue
1184 continue
1185 isactive = False
1185 isactive = False
1186 if not isclosed:
1186 if not isclosed:
1187 openheads = set(repo.branchmap().iteropen(heads))
1187 openheads = set(repo.branchmap().iteropen(heads))
1188 isactive = bool(openheads & allheads)
1188 isactive = bool(openheads & allheads)
1189 branches.append((tag, repo[tip], isactive, not isclosed))
1189 branches.append((tag, repo[tip], isactive, not isclosed))
1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1191 reverse=True)
1191 reverse=True)
1192
1192
1193 for tag, ctx, isactive, isopen in branches:
1193 for tag, ctx, isactive, isopen in branches:
1194 if active and not isactive:
1194 if active and not isactive:
1195 continue
1195 continue
1196 if isactive:
1196 if isactive:
1197 label = 'branches.active'
1197 label = 'branches.active'
1198 notice = ''
1198 notice = ''
1199 elif not isopen:
1199 elif not isopen:
1200 if not closed:
1200 if not closed:
1201 continue
1201 continue
1202 label = 'branches.closed'
1202 label = 'branches.closed'
1203 notice = _(' (closed)')
1203 notice = _(' (closed)')
1204 else:
1204 else:
1205 label = 'branches.inactive'
1205 label = 'branches.inactive'
1206 notice = _(' (inactive)')
1206 notice = _(' (inactive)')
1207 current = (tag == repo.dirstate.branch())
1207 current = (tag == repo.dirstate.branch())
1208 if current:
1208 if current:
1209 label = 'branches.current'
1209 label = 'branches.current'
1210
1210
1211 fm.startitem()
1211 fm.startitem()
1212 fm.write('branch', '%s', tag, label=label)
1212 fm.write('branch', '%s', tag, label=label)
1213 rev = ctx.rev()
1213 rev = ctx.rev()
1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1215 fmt = ' ' * padsize + ' %d:%s'
1215 fmt = ' ' * padsize + ' %d:%s'
1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1217 label='log.changeset changeset.%s' % ctx.phasestr())
1217 label='log.changeset changeset.%s' % ctx.phasestr())
1218 fm.context(ctx=ctx)
1218 fm.context(ctx=ctx)
1219 fm.data(active=isactive, closed=not isopen, current=current)
1219 fm.data(active=isactive, closed=not isopen, current=current)
1220 if not ui.quiet:
1220 if not ui.quiet:
1221 fm.plain(notice)
1221 fm.plain(notice)
1222 fm.plain('\n')
1222 fm.plain('\n')
1223 fm.end()
1223 fm.end()
1224
1224
1225 @command('bundle',
1225 @command('bundle',
1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1228 _('REV')),
1228 _('REV')),
1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1230 _('BRANCH')),
1230 _('BRANCH')),
1231 ('', 'base', [],
1231 ('', 'base', [],
1232 _('a base changeset assumed to be available at the destination'),
1232 _('a base changeset assumed to be available at the destination'),
1233 _('REV')),
1233 _('REV')),
1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1236 ] + remoteopts,
1236 ] + remoteopts,
1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1239 def bundle(ui, repo, fname, dest=None, **opts):
1239 def bundle(ui, repo, fname, dest=None, **opts):
1240 """create a bundle file
1240 """create a bundle file
1241
1241
1242 Generate a bundle file containing data to be transferred to another
1242 Generate a bundle file containing data to be transferred to another
1243 repository.
1243 repository.
1244
1244
1245 To create a bundle containing all changesets, use -a/--all
1245 To create a bundle containing all changesets, use -a/--all
1246 (or --base null). Otherwise, hg assumes the destination will have
1246 (or --base null). Otherwise, hg assumes the destination will have
1247 all the nodes you specify with --base parameters. Otherwise, hg
1247 all the nodes you specify with --base parameters. Otherwise, hg
1248 will assume the repository has all the nodes in destination, or
1248 will assume the repository has all the nodes in destination, or
1249 default-push/default if no destination is specified, where destination
1249 default-push/default if no destination is specified, where destination
1250 is the repository you provide through DEST option.
1250 is the repository you provide through DEST option.
1251
1251
1252 You can change bundle format with the -t/--type option. See
1252 You can change bundle format with the -t/--type option. See
1253 :hg:`help bundlespec` for documentation on this format. By default,
1253 :hg:`help bundlespec` for documentation on this format. By default,
1254 the most appropriate format is used and compression defaults to
1254 the most appropriate format is used and compression defaults to
1255 bzip2.
1255 bzip2.
1256
1256
1257 The bundle file can then be transferred using conventional means
1257 The bundle file can then be transferred using conventional means
1258 and applied to another repository with the unbundle or pull
1258 and applied to another repository with the unbundle or pull
1259 command. This is useful when direct push and pull are not
1259 command. This is useful when direct push and pull are not
1260 available or when exporting an entire repository is undesirable.
1260 available or when exporting an entire repository is undesirable.
1261
1261
1262 Applying bundles preserves all changeset contents including
1262 Applying bundles preserves all changeset contents including
1263 permissions, copy/rename information, and revision history.
1263 permissions, copy/rename information, and revision history.
1264
1264
1265 Returns 0 on success, 1 if no changes found.
1265 Returns 0 on success, 1 if no changes found.
1266 """
1266 """
1267 opts = pycompat.byteskwargs(opts)
1267 opts = pycompat.byteskwargs(opts)
1268 revs = None
1268 revs = None
1269 if 'rev' in opts:
1269 if 'rev' in opts:
1270 revstrings = opts['rev']
1270 revstrings = opts['rev']
1271 revs = scmutil.revrange(repo, revstrings)
1271 revs = scmutil.revrange(repo, revstrings)
1272 if revstrings and not revs:
1272 if revstrings and not revs:
1273 raise error.Abort(_('no commits to bundle'))
1273 raise error.Abort(_('no commits to bundle'))
1274
1274
1275 bundletype = opts.get('type', 'bzip2').lower()
1275 bundletype = opts.get('type', 'bzip2').lower()
1276 try:
1276 try:
1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1278 except error.UnsupportedBundleSpecification as e:
1278 except error.UnsupportedBundleSpecification as e:
1279 raise error.Abort(pycompat.bytestr(e),
1279 raise error.Abort(pycompat.bytestr(e),
1280 hint=_("see 'hg help bundlespec' for supported "
1280 hint=_("see 'hg help bundlespec' for supported "
1281 "values for --type"))
1281 "values for --type"))
1282 cgversion = bundlespec.contentopts["cg.version"]
1282 cgversion = bundlespec.contentopts["cg.version"]
1283
1283
1284 # Packed bundles are a pseudo bundle format for now.
1284 # Packed bundles are a pseudo bundle format for now.
1285 if cgversion == 's1':
1285 if cgversion == 's1':
1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1288
1288
1289 if opts.get('all'):
1289 if opts.get('all'):
1290 if dest:
1290 if dest:
1291 raise error.Abort(_("--all is incompatible with specifying "
1291 raise error.Abort(_("--all is incompatible with specifying "
1292 "a destination"))
1292 "a destination"))
1293 if opts.get('base'):
1293 if opts.get('base'):
1294 ui.warn(_("ignoring --base because --all was specified\n"))
1294 ui.warn(_("ignoring --base because --all was specified\n"))
1295 base = [nullrev]
1295 base = [nullrev]
1296 else:
1296 else:
1297 base = scmutil.revrange(repo, opts.get('base'))
1297 base = scmutil.revrange(repo, opts.get('base'))
1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1299 raise error.Abort(_("repository does not support bundle version %s") %
1299 raise error.Abort(_("repository does not support bundle version %s") %
1300 cgversion)
1300 cgversion)
1301
1301
1302 if base:
1302 if base:
1303 if dest:
1303 if dest:
1304 raise error.Abort(_("--base is incompatible with specifying "
1304 raise error.Abort(_("--base is incompatible with specifying "
1305 "a destination"))
1305 "a destination"))
1306 common = [repo[rev].node() for rev in base]
1306 common = [repo[rev].node() for rev in base]
1307 heads = [repo[r].node() for r in revs] if revs else None
1307 heads = [repo[r].node() for r in revs] if revs else None
1308 outgoing = discovery.outgoing(repo, common, heads)
1308 outgoing = discovery.outgoing(repo, common, heads)
1309 else:
1309 else:
1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1312 other = hg.peer(repo, opts, dest)
1312 other = hg.peer(repo, opts, dest)
1313 revs = [repo[r].hex() for r in revs]
1313 revs = [repo[r].hex() for r in revs]
1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1316 outgoing = discovery.findcommonoutgoing(repo, other,
1316 outgoing = discovery.findcommonoutgoing(repo, other,
1317 onlyheads=heads,
1317 onlyheads=heads,
1318 force=opts.get('force'),
1318 force=opts.get('force'),
1319 portable=True)
1319 portable=True)
1320
1320
1321 if not outgoing.missing:
1321 if not outgoing.missing:
1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1323 return 1
1323 return 1
1324
1324
1325 if cgversion == '01': #bundle1
1325 if cgversion == '01': #bundle1
1326 bversion = 'HG10' + bundlespec.wirecompression
1326 bversion = 'HG10' + bundlespec.wirecompression
1327 bcompression = None
1327 bcompression = None
1328 elif cgversion in ('02', '03'):
1328 elif cgversion in ('02', '03'):
1329 bversion = 'HG20'
1329 bversion = 'HG20'
1330 bcompression = bundlespec.wirecompression
1330 bcompression = bundlespec.wirecompression
1331 else:
1331 else:
1332 raise error.ProgrammingError(
1332 raise error.ProgrammingError(
1333 'bundle: unexpected changegroup version %s' % cgversion)
1333 'bundle: unexpected changegroup version %s' % cgversion)
1334
1334
1335 # TODO compression options should be derived from bundlespec parsing.
1335 # TODO compression options should be derived from bundlespec parsing.
1336 # This is a temporary hack to allow adjusting bundle compression
1336 # This is a temporary hack to allow adjusting bundle compression
1337 # level without a) formalizing the bundlespec changes to declare it
1337 # level without a) formalizing the bundlespec changes to declare it
1338 # b) introducing a command flag.
1338 # b) introducing a command flag.
1339 compopts = {}
1339 compopts = {}
1340 complevel = ui.configint('experimental',
1340 complevel = ui.configint('experimental',
1341 'bundlecomplevel.' + bundlespec.compression)
1341 'bundlecomplevel.' + bundlespec.compression)
1342 if complevel is None:
1342 if complevel is None:
1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1344 if complevel is not None:
1344 if complevel is not None:
1345 compopts['level'] = complevel
1345 compopts['level'] = complevel
1346
1346
1347 # Allow overriding the bundling of obsmarker in phases through
1347 # Allow overriding the bundling of obsmarker in phases through
1348 # configuration while we don't have a bundle version that include them
1348 # configuration while we don't have a bundle version that include them
1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1350 bundlespec.contentopts['obsolescence'] = True
1350 bundlespec.contentopts['obsolescence'] = True
1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1352 bundlespec.contentopts['phases'] = True
1352 bundlespec.contentopts['phases'] = True
1353
1353
1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1355 bundlespec.contentopts, compression=bcompression,
1355 bundlespec.contentopts, compression=bcompression,
1356 compopts=compopts)
1356 compopts=compopts)
1357
1357
1358 @command('cat',
1358 @command('cat',
1359 [('o', 'output', '',
1359 [('o', 'output', '',
1360 _('print output to file with formatted name'), _('FORMAT')),
1360 _('print output to file with formatted name'), _('FORMAT')),
1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1362 ('', 'decode', None, _('apply any matching decode filter')),
1362 ('', 'decode', None, _('apply any matching decode filter')),
1363 ] + walkopts + formatteropts,
1363 ] + walkopts + formatteropts,
1364 _('[OPTION]... FILE...'),
1364 _('[OPTION]... FILE...'),
1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1366 inferrepo=True,
1366 inferrepo=True,
1367 intents={INTENT_READONLY})
1367 intents={INTENT_READONLY})
1368 def cat(ui, repo, file1, *pats, **opts):
1368 def cat(ui, repo, file1, *pats, **opts):
1369 """output the current or given revision of files
1369 """output the current or given revision of files
1370
1370
1371 Print the specified files as they were at the given revision. If
1371 Print the specified files as they were at the given revision. If
1372 no revision is given, the parent of the working directory is used.
1372 no revision is given, the parent of the working directory is used.
1373
1373
1374 Output may be to a file, in which case the name of the file is
1374 Output may be to a file, in which case the name of the file is
1375 given using a template string. See :hg:`help templates`. In addition
1375 given using a template string. See :hg:`help templates`. In addition
1376 to the common template keywords, the following formatting rules are
1376 to the common template keywords, the following formatting rules are
1377 supported:
1377 supported:
1378
1378
1379 :``%%``: literal "%" character
1379 :``%%``: literal "%" character
1380 :``%s``: basename of file being printed
1380 :``%s``: basename of file being printed
1381 :``%d``: dirname of file being printed, or '.' if in repository root
1381 :``%d``: dirname of file being printed, or '.' if in repository root
1382 :``%p``: root-relative path name of file being printed
1382 :``%p``: root-relative path name of file being printed
1383 :``%H``: changeset hash (40 hexadecimal digits)
1383 :``%H``: changeset hash (40 hexadecimal digits)
1384 :``%R``: changeset revision number
1384 :``%R``: changeset revision number
1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1386 :``%r``: zero-padded changeset revision number
1386 :``%r``: zero-padded changeset revision number
1387 :``%b``: basename of the exporting repository
1387 :``%b``: basename of the exporting repository
1388 :``\\``: literal "\\" character
1388 :``\\``: literal "\\" character
1389
1389
1390 .. container:: verbose
1390 .. container:: verbose
1391
1391
1392 Template:
1392 Template:
1393
1393
1394 The following keywords are supported in addition to the common template
1394 The following keywords are supported in addition to the common template
1395 keywords and functions. See also :hg:`help templates`.
1395 keywords and functions. See also :hg:`help templates`.
1396
1396
1397 :data: String. File content.
1397 :data: String. File content.
1398 :path: String. Repository-absolute path of the file.
1398 :path: String. Repository-absolute path of the file.
1399
1399
1400 Returns 0 on success.
1400 Returns 0 on success.
1401 """
1401 """
1402 opts = pycompat.byteskwargs(opts)
1402 opts = pycompat.byteskwargs(opts)
1403 rev = opts.get('rev')
1403 rev = opts.get('rev')
1404 if rev:
1404 if rev:
1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1406 ctx = scmutil.revsingle(repo, rev)
1406 ctx = scmutil.revsingle(repo, rev)
1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1408 fntemplate = opts.pop('output', '')
1408 fntemplate = opts.pop('output', '')
1409 if cmdutil.isstdiofilename(fntemplate):
1409 if cmdutil.isstdiofilename(fntemplate):
1410 fntemplate = ''
1410 fntemplate = ''
1411
1411
1412 if fntemplate:
1412 if fntemplate:
1413 fm = formatter.nullformatter(ui, 'cat', opts)
1413 fm = formatter.nullformatter(ui, 'cat', opts)
1414 else:
1414 else:
1415 ui.pager('cat')
1415 ui.pager('cat')
1416 fm = ui.formatter('cat', opts)
1416 fm = ui.formatter('cat', opts)
1417 with fm:
1417 with fm:
1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1419 **pycompat.strkwargs(opts))
1419 **pycompat.strkwargs(opts))
1420
1420
1421 @command('clone',
1421 @command('clone',
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1423 'directory (only a repository)')),
1423 'directory (only a repository)')),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1425 _('REV')),
1425 _('REV')),
1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1427 ' and its ancestors'), _('REV')),
1427 ' and its ancestors'), _('REV')),
1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1429 ' changesets and their ancestors'), _('BRANCH')),
1429 ' changesets and their ancestors'), _('BRANCH')),
1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1431 ('', 'uncompressed', None,
1431 ('', 'uncompressed', None,
1432 _('an alias to --stream (DEPRECATED)')),
1432 _('an alias to --stream (DEPRECATED)')),
1433 ('', 'stream', None,
1433 ('', 'stream', None,
1434 _('clone with minimal data processing')),
1434 _('clone with minimal data processing')),
1435 ] + remoteopts,
1435 ] + remoteopts,
1436 _('[OPTION]... SOURCE [DEST]'),
1436 _('[OPTION]... SOURCE [DEST]'),
1437 helpcategory=command.CATEGORY_REPO_CREATION,
1437 helpcategory=command.CATEGORY_REPO_CREATION,
1438 helpbasic=True, norepo=True)
1438 helpbasic=True, norepo=True)
1439 def clone(ui, source, dest=None, **opts):
1439 def clone(ui, source, dest=None, **opts):
1440 """make a copy of an existing repository
1440 """make a copy of an existing repository
1441
1441
1442 Create a copy of an existing repository in a new directory.
1442 Create a copy of an existing repository in a new directory.
1443
1443
1444 If no destination directory name is specified, it defaults to the
1444 If no destination directory name is specified, it defaults to the
1445 basename of the source.
1445 basename of the source.
1446
1446
1447 The location of the source is added to the new repository's
1447 The location of the source is added to the new repository's
1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1449
1449
1450 Only local paths and ``ssh://`` URLs are supported as
1450 Only local paths and ``ssh://`` URLs are supported as
1451 destinations. For ``ssh://`` destinations, no working directory or
1451 destinations. For ``ssh://`` destinations, no working directory or
1452 ``.hg/hgrc`` will be created on the remote side.
1452 ``.hg/hgrc`` will be created on the remote side.
1453
1453
1454 If the source repository has a bookmark called '@' set, that
1454 If the source repository has a bookmark called '@' set, that
1455 revision will be checked out in the new repository by default.
1455 revision will be checked out in the new repository by default.
1456
1456
1457 To check out a particular version, use -u/--update, or
1457 To check out a particular version, use -u/--update, or
1458 -U/--noupdate to create a clone with no working directory.
1458 -U/--noupdate to create a clone with no working directory.
1459
1459
1460 To pull only a subset of changesets, specify one or more revisions
1460 To pull only a subset of changesets, specify one or more revisions
1461 identifiers with -r/--rev or branches with -b/--branch. The
1461 identifiers with -r/--rev or branches with -b/--branch. The
1462 resulting clone will contain only the specified changesets and
1462 resulting clone will contain only the specified changesets and
1463 their ancestors. These options (or 'clone src#rev dest') imply
1463 their ancestors. These options (or 'clone src#rev dest') imply
1464 --pull, even for local source repositories.
1464 --pull, even for local source repositories.
1465
1465
1466 In normal clone mode, the remote normalizes repository data into a common
1466 In normal clone mode, the remote normalizes repository data into a common
1467 exchange format and the receiving end translates this data into its local
1467 exchange format and the receiving end translates this data into its local
1468 storage format. --stream activates a different clone mode that essentially
1468 storage format. --stream activates a different clone mode that essentially
1469 copies repository files from the remote with minimal data processing. This
1469 copies repository files from the remote with minimal data processing. This
1470 significantly reduces the CPU cost of a clone both remotely and locally.
1470 significantly reduces the CPU cost of a clone both remotely and locally.
1471 However, it often increases the transferred data size by 30-40%. This can
1471 However, it often increases the transferred data size by 30-40%. This can
1472 result in substantially faster clones where I/O throughput is plentiful,
1472 result in substantially faster clones where I/O throughput is plentiful,
1473 especially for larger repositories. A side-effect of --stream clones is
1473 especially for larger repositories. A side-effect of --stream clones is
1474 that storage settings and requirements on the remote are applied locally:
1474 that storage settings and requirements on the remote are applied locally:
1475 a modern client may inherit legacy or inefficient storage used by the
1475 a modern client may inherit legacy or inefficient storage used by the
1476 remote or a legacy Mercurial client may not be able to clone from a
1476 remote or a legacy Mercurial client may not be able to clone from a
1477 modern Mercurial remote.
1477 modern Mercurial remote.
1478
1478
1479 .. note::
1479 .. note::
1480
1480
1481 Specifying a tag will include the tagged changeset but not the
1481 Specifying a tag will include the tagged changeset but not the
1482 changeset containing the tag.
1482 changeset containing the tag.
1483
1483
1484 .. container:: verbose
1484 .. container:: verbose
1485
1485
1486 For efficiency, hardlinks are used for cloning whenever the
1486 For efficiency, hardlinks are used for cloning whenever the
1487 source and destination are on the same filesystem (note this
1487 source and destination are on the same filesystem (note this
1488 applies only to the repository data, not to the working
1488 applies only to the repository data, not to the working
1489 directory). Some filesystems, such as AFS, implement hardlinking
1489 directory). Some filesystems, such as AFS, implement hardlinking
1490 incorrectly, but do not report errors. In these cases, use the
1490 incorrectly, but do not report errors. In these cases, use the
1491 --pull option to avoid hardlinking.
1491 --pull option to avoid hardlinking.
1492
1492
1493 Mercurial will update the working directory to the first applicable
1493 Mercurial will update the working directory to the first applicable
1494 revision from this list:
1494 revision from this list:
1495
1495
1496 a) null if -U or the source repository has no changesets
1496 a) null if -U or the source repository has no changesets
1497 b) if -u . and the source repository is local, the first parent of
1497 b) if -u . and the source repository is local, the first parent of
1498 the source repository's working directory
1498 the source repository's working directory
1499 c) the changeset specified with -u (if a branch name, this means the
1499 c) the changeset specified with -u (if a branch name, this means the
1500 latest head of that branch)
1500 latest head of that branch)
1501 d) the changeset specified with -r
1501 d) the changeset specified with -r
1502 e) the tipmost head specified with -b
1502 e) the tipmost head specified with -b
1503 f) the tipmost head specified with the url#branch source syntax
1503 f) the tipmost head specified with the url#branch source syntax
1504 g) the revision marked with the '@' bookmark, if present
1504 g) the revision marked with the '@' bookmark, if present
1505 h) the tipmost head of the default branch
1505 h) the tipmost head of the default branch
1506 i) tip
1506 i) tip
1507
1507
1508 When cloning from servers that support it, Mercurial may fetch
1508 When cloning from servers that support it, Mercurial may fetch
1509 pre-generated data from a server-advertised URL or inline from the
1509 pre-generated data from a server-advertised URL or inline from the
1510 same stream. When this is done, hooks operating on incoming changesets
1510 same stream. When this is done, hooks operating on incoming changesets
1511 and changegroups may fire more than once, once for each pre-generated
1511 and changegroups may fire more than once, once for each pre-generated
1512 bundle and as well as for any additional remaining data. In addition,
1512 bundle and as well as for any additional remaining data. In addition,
1513 if an error occurs, the repository may be rolled back to a partial
1513 if an error occurs, the repository may be rolled back to a partial
1514 clone. This behavior may change in future releases.
1514 clone. This behavior may change in future releases.
1515 See :hg:`help -e clonebundles` for more.
1515 See :hg:`help -e clonebundles` for more.
1516
1516
1517 Examples:
1517 Examples:
1518
1518
1519 - clone a remote repository to a new directory named hg/::
1519 - clone a remote repository to a new directory named hg/::
1520
1520
1521 hg clone https://www.mercurial-scm.org/repo/hg/
1521 hg clone https://www.mercurial-scm.org/repo/hg/
1522
1522
1523 - create a lightweight local clone::
1523 - create a lightweight local clone::
1524
1524
1525 hg clone project/ project-feature/
1525 hg clone project/ project-feature/
1526
1526
1527 - clone from an absolute path on an ssh server (note double-slash)::
1527 - clone from an absolute path on an ssh server (note double-slash)::
1528
1528
1529 hg clone ssh://user@server//home/projects/alpha/
1529 hg clone ssh://user@server//home/projects/alpha/
1530
1530
1531 - do a streaming clone while checking out a specified version::
1531 - do a streaming clone while checking out a specified version::
1532
1532
1533 hg clone --stream http://server/repo -u 1.5
1533 hg clone --stream http://server/repo -u 1.5
1534
1534
1535 - create a repository without changesets after a particular revision::
1535 - create a repository without changesets after a particular revision::
1536
1536
1537 hg clone -r 04e544 experimental/ good/
1537 hg clone -r 04e544 experimental/ good/
1538
1538
1539 - clone (and track) a particular named branch::
1539 - clone (and track) a particular named branch::
1540
1540
1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1542
1542
1543 See :hg:`help urls` for details on specifying URLs.
1543 See :hg:`help urls` for details on specifying URLs.
1544
1544
1545 Returns 0 on success.
1545 Returns 0 on success.
1546 """
1546 """
1547 opts = pycompat.byteskwargs(opts)
1547 opts = pycompat.byteskwargs(opts)
1548 if opts.get('noupdate') and opts.get('updaterev'):
1548 if opts.get('noupdate') and opts.get('updaterev'):
1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1550
1550
1551 # --include/--exclude can come from narrow or sparse.
1551 # --include/--exclude can come from narrow or sparse.
1552 includepats, excludepats = None, None
1552 includepats, excludepats = None, None
1553
1553
1554 # hg.clone() differentiates between None and an empty set. So make sure
1554 # hg.clone() differentiates between None and an empty set. So make sure
1555 # patterns are sets if narrow is requested without patterns.
1555 # patterns are sets if narrow is requested without patterns.
1556 if opts.get('narrow'):
1556 if opts.get('narrow'):
1557 includepats = set()
1557 includepats = set()
1558 excludepats = set()
1558 excludepats = set()
1559
1559
1560 if opts.get('include'):
1560 if opts.get('include'):
1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1562 if opts.get('exclude'):
1562 if opts.get('exclude'):
1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1564
1564
1565 r = hg.clone(ui, opts, source, dest,
1565 r = hg.clone(ui, opts, source, dest,
1566 pull=opts.get('pull'),
1566 pull=opts.get('pull'),
1567 stream=opts.get('stream') or opts.get('uncompressed'),
1567 stream=opts.get('stream') or opts.get('uncompressed'),
1568 revs=opts.get('rev'),
1568 revs=opts.get('rev'),
1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1570 branch=opts.get('branch'),
1570 branch=opts.get('branch'),
1571 shareopts=opts.get('shareopts'),
1571 shareopts=opts.get('shareopts'),
1572 storeincludepats=includepats,
1572 storeincludepats=includepats,
1573 storeexcludepats=excludepats,
1573 storeexcludepats=excludepats,
1574 depth=opts.get('depth') or None)
1574 depth=opts.get('depth') or None)
1575
1575
1576 return r is None
1576 return r is None
1577
1577
1578 @command('commit|ci',
1578 @command('commit|ci',
1579 [('A', 'addremove', None,
1579 [('A', 'addremove', None,
1580 _('mark new/missing files as added/removed before committing')),
1580 _('mark new/missing files as added/removed before committing')),
1581 ('', 'close-branch', None,
1581 ('', 'close-branch', None,
1582 _('mark a branch head as closed')),
1582 _('mark a branch head as closed')),
1583 ('', 'amend', None, _('amend the parent of the working directory')),
1583 ('', 'amend', None, _('amend the parent of the working directory')),
1584 ('s', 'secret', None, _('use the secret phase for committing')),
1584 ('s', 'secret', None, _('use the secret phase for committing')),
1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1586 ('i', 'interactive', None, _('use interactive mode')),
1586 ('i', 'interactive', None, _('use interactive mode')),
1587 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1587 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1588 _('[OPTION]... [FILE]...'),
1588 _('[OPTION]... [FILE]...'),
1589 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1589 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1590 inferrepo=True)
1590 inferrepo=True)
1591 def commit(ui, repo, *pats, **opts):
1591 def commit(ui, repo, *pats, **opts):
1592 """commit the specified files or all outstanding changes
1592 """commit the specified files or all outstanding changes
1593
1593
1594 Commit changes to the given files into the repository. Unlike a
1594 Commit changes to the given files into the repository. Unlike a
1595 centralized SCM, this operation is a local operation. See
1595 centralized SCM, this operation is a local operation. See
1596 :hg:`push` for a way to actively distribute your changes.
1596 :hg:`push` for a way to actively distribute your changes.
1597
1597
1598 If a list of files is omitted, all changes reported by :hg:`status`
1598 If a list of files is omitted, all changes reported by :hg:`status`
1599 will be committed.
1599 will be committed.
1600
1600
1601 If you are committing the result of a merge, do not provide any
1601 If you are committing the result of a merge, do not provide any
1602 filenames or -I/-X filters.
1602 filenames or -I/-X filters.
1603
1603
1604 If no commit message is specified, Mercurial starts your
1604 If no commit message is specified, Mercurial starts your
1605 configured editor where you can enter a message. In case your
1605 configured editor where you can enter a message. In case your
1606 commit fails, you will find a backup of your message in
1606 commit fails, you will find a backup of your message in
1607 ``.hg/last-message.txt``.
1607 ``.hg/last-message.txt``.
1608
1608
1609 The --close-branch flag can be used to mark the current branch
1609 The --close-branch flag can be used to mark the current branch
1610 head closed. When all heads of a branch are closed, the branch
1610 head closed. When all heads of a branch are closed, the branch
1611 will be considered closed and no longer listed.
1611 will be considered closed and no longer listed.
1612
1612
1613 The --amend flag can be used to amend the parent of the
1613 The --amend flag can be used to amend the parent of the
1614 working directory with a new commit that contains the changes
1614 working directory with a new commit that contains the changes
1615 in the parent in addition to those currently reported by :hg:`status`,
1615 in the parent in addition to those currently reported by :hg:`status`,
1616 if there are any. The old commit is stored in a backup bundle in
1616 if there are any. The old commit is stored in a backup bundle in
1617 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1617 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1618 on how to restore it).
1618 on how to restore it).
1619
1619
1620 Message, user and date are taken from the amended commit unless
1620 Message, user and date are taken from the amended commit unless
1621 specified. When a message isn't specified on the command line,
1621 specified. When a message isn't specified on the command line,
1622 the editor will open with the message of the amended commit.
1622 the editor will open with the message of the amended commit.
1623
1623
1624 It is not possible to amend public changesets (see :hg:`help phases`)
1624 It is not possible to amend public changesets (see :hg:`help phases`)
1625 or changesets that have children.
1625 or changesets that have children.
1626
1626
1627 See :hg:`help dates` for a list of formats valid for -d/--date.
1627 See :hg:`help dates` for a list of formats valid for -d/--date.
1628
1628
1629 Returns 0 on success, 1 if nothing changed.
1629 Returns 0 on success, 1 if nothing changed.
1630
1630
1631 .. container:: verbose
1631 .. container:: verbose
1632
1632
1633 Examples:
1633 Examples:
1634
1634
1635 - commit all files ending in .py::
1635 - commit all files ending in .py::
1636
1636
1637 hg commit --include "set:**.py"
1637 hg commit --include "set:**.py"
1638
1638
1639 - commit all non-binary files::
1639 - commit all non-binary files::
1640
1640
1641 hg commit --exclude "set:binary()"
1641 hg commit --exclude "set:binary()"
1642
1642
1643 - amend the current commit and set the date to now::
1643 - amend the current commit and set the date to now::
1644
1644
1645 hg commit --amend --date now
1645 hg commit --amend --date now
1646 """
1646 """
1647 with repo.wlock(), repo.lock():
1647 with repo.wlock(), repo.lock():
1648 return _docommit(ui, repo, *pats, **opts)
1648 return _docommit(ui, repo, *pats, **opts)
1649
1649
1650 def _docommit(ui, repo, *pats, **opts):
1650 def _docommit(ui, repo, *pats, **opts):
1651 if opts.get(r'interactive'):
1651 if opts.get(r'interactive'):
1652 opts.pop(r'interactive')
1652 opts.pop(r'interactive')
1653 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1653 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1654 cmdutil.recordfilter, *pats,
1654 cmdutil.recordfilter, *pats,
1655 **opts)
1655 **opts)
1656 # ret can be 0 (no changes to record) or the value returned by
1656 # ret can be 0 (no changes to record) or the value returned by
1657 # commit(), 1 if nothing changed or None on success.
1657 # commit(), 1 if nothing changed or None on success.
1658 return 1 if ret == 0 else ret
1658 return 1 if ret == 0 else ret
1659
1659
1660 opts = pycompat.byteskwargs(opts)
1660 opts = pycompat.byteskwargs(opts)
1661 if opts.get('subrepos'):
1661 if opts.get('subrepos'):
1662 if opts.get('amend'):
1662 if opts.get('amend'):
1663 raise error.Abort(_('cannot amend with --subrepos'))
1663 raise error.Abort(_('cannot amend with --subrepos'))
1664 # Let --subrepos on the command line override config setting.
1664 # Let --subrepos on the command line override config setting.
1665 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1665 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1666
1666
1667 cmdutil.checkunfinished(repo, commit=True)
1667 cmdutil.checkunfinished(repo, commit=True)
1668
1668
1669 branch = repo[None].branch()
1669 branch = repo[None].branch()
1670 bheads = repo.branchheads(branch)
1670 bheads = repo.branchheads(branch)
1671
1671
1672 extra = {}
1672 extra = {}
1673 if opts.get('close_branch'):
1673 if opts.get('close_branch'):
1674 extra['close'] = '1'
1674 extra['close'] = '1'
1675
1675
1676 if repo['.'].closesbranch():
1676 if repo['.'].closesbranch():
1677 raise error.Abort(_('current revision is already a branch closing'
1677 raise error.Abort(_('current revision is already a branch closing'
1678 ' head'))
1678 ' head'))
1679 elif not bheads:
1679 elif not bheads:
1680 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1680 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1681 elif branch == repo['.'].branch() and repo['.'].node() not in bheads:
1681 elif branch == repo['.'].branch() and repo['.'].node() not in bheads:
1682 raise error.Abort(_('can only close branch heads'))
1682 raise error.Abort(_('can only close branch heads'))
1683 elif opts.get('amend'):
1683 elif opts.get('amend'):
1684 if (repo['.'].p1().branch() != branch and
1684 if (repo['.'].p1().branch() != branch and
1685 repo['.'].p2().branch() != branch):
1685 repo['.'].p2().branch() != branch):
1686 raise error.Abort(_('can only close branch heads'))
1686 raise error.Abort(_('can only close branch heads'))
1687
1687
1688 if opts.get('amend'):
1688 if opts.get('amend'):
1689 if ui.configbool('ui', 'commitsubrepos'):
1689 if ui.configbool('ui', 'commitsubrepos'):
1690 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1690 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1691
1691
1692 old = repo['.']
1692 old = repo['.']
1693 rewriteutil.precheck(repo, [old.rev()], 'amend')
1693 rewriteutil.precheck(repo, [old.rev()], 'amend')
1694
1694
1695 # Currently histedit gets confused if an amend happens while histedit
1695 # Currently histedit gets confused if an amend happens while histedit
1696 # is in progress. Since we have a checkunfinished command, we are
1696 # is in progress. Since we have a checkunfinished command, we are
1697 # temporarily honoring it.
1697 # temporarily honoring it.
1698 #
1698 #
1699 # Note: eventually this guard will be removed. Please do not expect
1699 # Note: eventually this guard will be removed. Please do not expect
1700 # this behavior to remain.
1700 # this behavior to remain.
1701 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1701 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1702 cmdutil.checkunfinished(repo)
1702 cmdutil.checkunfinished(repo)
1703
1703
1704 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1704 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1705 if node == old.node():
1705 if node == old.node():
1706 ui.status(_("nothing changed\n"))
1706 ui.status(_("nothing changed\n"))
1707 return 1
1707 return 1
1708 else:
1708 else:
1709 def commitfunc(ui, repo, message, match, opts):
1709 def commitfunc(ui, repo, message, match, opts):
1710 overrides = {}
1710 overrides = {}
1711 if opts.get('secret'):
1711 if opts.get('secret'):
1712 overrides[('phases', 'new-commit')] = 'secret'
1712 overrides[('phases', 'new-commit')] = 'secret'
1713
1713
1714 baseui = repo.baseui
1714 baseui = repo.baseui
1715 with baseui.configoverride(overrides, 'commit'):
1715 with baseui.configoverride(overrides, 'commit'):
1716 with ui.configoverride(overrides, 'commit'):
1716 with ui.configoverride(overrides, 'commit'):
1717 editform = cmdutil.mergeeditform(repo[None],
1717 editform = cmdutil.mergeeditform(repo[None],
1718 'commit.normal')
1718 'commit.normal')
1719 editor = cmdutil.getcommiteditor(
1719 editor = cmdutil.getcommiteditor(
1720 editform=editform, **pycompat.strkwargs(opts))
1720 editform=editform, **pycompat.strkwargs(opts))
1721 return repo.commit(message,
1721 return repo.commit(message,
1722 opts.get('user'),
1722 opts.get('user'),
1723 opts.get('date'),
1723 opts.get('date'),
1724 match,
1724 match,
1725 editor=editor,
1725 editor=editor,
1726 extra=extra)
1726 extra=extra)
1727
1727
1728 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1728 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1729
1729
1730 if not node:
1730 if not node:
1731 stat = cmdutil.postcommitstatus(repo, pats, opts)
1731 stat = cmdutil.postcommitstatus(repo, pats, opts)
1732 if stat[3]:
1732 if stat[3]:
1733 ui.status(_("nothing changed (%d missing files, see "
1733 ui.status(_("nothing changed (%d missing files, see "
1734 "'hg status')\n") % len(stat[3]))
1734 "'hg status')\n") % len(stat[3]))
1735 else:
1735 else:
1736 ui.status(_("nothing changed\n"))
1736 ui.status(_("nothing changed\n"))
1737 return 1
1737 return 1
1738
1738
1739 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1739 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1740
1740
1741 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1741 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1742 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1742 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1743 unknown=True, subrepos=opts.get('subrepos'))
1743 unknown=True, subrepos=opts.get('subrepos'))
1744
1744
1745 @command('config|showconfig|debugconfig',
1745 @command('config|showconfig|debugconfig',
1746 [('u', 'untrusted', None, _('show untrusted configuration options')),
1746 [('u', 'untrusted', None, _('show untrusted configuration options')),
1747 ('e', 'edit', None, _('edit user config')),
1747 ('e', 'edit', None, _('edit user config')),
1748 ('l', 'local', None, _('edit repository config')),
1748 ('l', 'local', None, _('edit repository config')),
1749 ('g', 'global', None, _('edit global config'))] + formatteropts,
1749 ('g', 'global', None, _('edit global config'))] + formatteropts,
1750 _('[-u] [NAME]...'),
1750 _('[-u] [NAME]...'),
1751 helpcategory=command.CATEGORY_HELP,
1751 helpcategory=command.CATEGORY_HELP,
1752 optionalrepo=True,
1752 optionalrepo=True,
1753 intents={INTENT_READONLY})
1753 intents={INTENT_READONLY})
1754 def config(ui, repo, *values, **opts):
1754 def config(ui, repo, *values, **opts):
1755 """show combined config settings from all hgrc files
1755 """show combined config settings from all hgrc files
1756
1756
1757 With no arguments, print names and values of all config items.
1757 With no arguments, print names and values of all config items.
1758
1758
1759 With one argument of the form section.name, print just the value
1759 With one argument of the form section.name, print just the value
1760 of that config item.
1760 of that config item.
1761
1761
1762 With multiple arguments, print names and values of all config
1762 With multiple arguments, print names and values of all config
1763 items with matching section names or section.names.
1763 items with matching section names or section.names.
1764
1764
1765 With --edit, start an editor on the user-level config file. With
1765 With --edit, start an editor on the user-level config file. With
1766 --global, edit the system-wide config file. With --local, edit the
1766 --global, edit the system-wide config file. With --local, edit the
1767 repository-level config file.
1767 repository-level config file.
1768
1768
1769 With --debug, the source (filename and line number) is printed
1769 With --debug, the source (filename and line number) is printed
1770 for each config item.
1770 for each config item.
1771
1771
1772 See :hg:`help config` for more information about config files.
1772 See :hg:`help config` for more information about config files.
1773
1773
1774 .. container:: verbose
1774 .. container:: verbose
1775
1775
1776 Template:
1776 Template:
1777
1777
1778 The following keywords are supported. See also :hg:`help templates`.
1778 The following keywords are supported. See also :hg:`help templates`.
1779
1779
1780 :name: String. Config name.
1780 :name: String. Config name.
1781 :source: String. Filename and line number where the item is defined.
1781 :source: String. Filename and line number where the item is defined.
1782 :value: String. Config value.
1782 :value: String. Config value.
1783
1783
1784 Returns 0 on success, 1 if NAME does not exist.
1784 Returns 0 on success, 1 if NAME does not exist.
1785
1785
1786 """
1786 """
1787
1787
1788 opts = pycompat.byteskwargs(opts)
1788 opts = pycompat.byteskwargs(opts)
1789 if opts.get('edit') or opts.get('local') or opts.get('global'):
1789 if opts.get('edit') or opts.get('local') or opts.get('global'):
1790 if opts.get('local') and opts.get('global'):
1790 if opts.get('local') and opts.get('global'):
1791 raise error.Abort(_("can't use --local and --global together"))
1791 raise error.Abort(_("can't use --local and --global together"))
1792
1792
1793 if opts.get('local'):
1793 if opts.get('local'):
1794 if not repo:
1794 if not repo:
1795 raise error.Abort(_("can't use --local outside a repository"))
1795 raise error.Abort(_("can't use --local outside a repository"))
1796 paths = [repo.vfs.join('hgrc')]
1796 paths = [repo.vfs.join('hgrc')]
1797 elif opts.get('global'):
1797 elif opts.get('global'):
1798 paths = rcutil.systemrcpath()
1798 paths = rcutil.systemrcpath()
1799 else:
1799 else:
1800 paths = rcutil.userrcpath()
1800 paths = rcutil.userrcpath()
1801
1801
1802 for f in paths:
1802 for f in paths:
1803 if os.path.exists(f):
1803 if os.path.exists(f):
1804 break
1804 break
1805 else:
1805 else:
1806 if opts.get('global'):
1806 if opts.get('global'):
1807 samplehgrc = uimod.samplehgrcs['global']
1807 samplehgrc = uimod.samplehgrcs['global']
1808 elif opts.get('local'):
1808 elif opts.get('local'):
1809 samplehgrc = uimod.samplehgrcs['local']
1809 samplehgrc = uimod.samplehgrcs['local']
1810 else:
1810 else:
1811 samplehgrc = uimod.samplehgrcs['user']
1811 samplehgrc = uimod.samplehgrcs['user']
1812
1812
1813 f = paths[0]
1813 f = paths[0]
1814 fp = open(f, "wb")
1814 fp = open(f, "wb")
1815 fp.write(util.tonativeeol(samplehgrc))
1815 fp.write(util.tonativeeol(samplehgrc))
1816 fp.close()
1816 fp.close()
1817
1817
1818 editor = ui.geteditor()
1818 editor = ui.geteditor()
1819 ui.system("%s \"%s\"" % (editor, f),
1819 ui.system("%s \"%s\"" % (editor, f),
1820 onerr=error.Abort, errprefix=_("edit failed"),
1820 onerr=error.Abort, errprefix=_("edit failed"),
1821 blockedtag='config_edit')
1821 blockedtag='config_edit')
1822 return
1822 return
1823 ui.pager('config')
1823 ui.pager('config')
1824 fm = ui.formatter('config', opts)
1824 fm = ui.formatter('config', opts)
1825 for t, f in rcutil.rccomponents():
1825 for t, f in rcutil.rccomponents():
1826 if t == 'path':
1826 if t == 'path':
1827 ui.debug('read config from: %s\n' % f)
1827 ui.debug('read config from: %s\n' % f)
1828 elif t == 'items':
1828 elif t == 'items':
1829 for section, name, value, source in f:
1829 for section, name, value, source in f:
1830 ui.debug('set config by: %s\n' % source)
1830 ui.debug('set config by: %s\n' % source)
1831 else:
1831 else:
1832 raise error.ProgrammingError('unknown rctype: %s' % t)
1832 raise error.ProgrammingError('unknown rctype: %s' % t)
1833 untrusted = bool(opts.get('untrusted'))
1833 untrusted = bool(opts.get('untrusted'))
1834
1834
1835 selsections = selentries = []
1835 selsections = selentries = []
1836 if values:
1836 if values:
1837 selsections = [v for v in values if '.' not in v]
1837 selsections = [v for v in values if '.' not in v]
1838 selentries = [v for v in values if '.' in v]
1838 selentries = [v for v in values if '.' in v]
1839 uniquesel = (len(selentries) == 1 and not selsections)
1839 uniquesel = (len(selentries) == 1 and not selsections)
1840 selsections = set(selsections)
1840 selsections = set(selsections)
1841 selentries = set(selentries)
1841 selentries = set(selentries)
1842
1842
1843 matched = False
1843 matched = False
1844 for section, name, value in ui.walkconfig(untrusted=untrusted):
1844 for section, name, value in ui.walkconfig(untrusted=untrusted):
1845 source = ui.configsource(section, name, untrusted)
1845 source = ui.configsource(section, name, untrusted)
1846 value = pycompat.bytestr(value)
1846 value = pycompat.bytestr(value)
1847 if fm.isplain():
1847 if fm.isplain():
1848 source = source or 'none'
1848 source = source or 'none'
1849 value = value.replace('\n', '\\n')
1849 value = value.replace('\n', '\\n')
1850 entryname = section + '.' + name
1850 entryname = section + '.' + name
1851 if values and not (section in selsections or entryname in selentries):
1851 if values and not (section in selsections or entryname in selentries):
1852 continue
1852 continue
1853 fm.startitem()
1853 fm.startitem()
1854 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1854 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1855 if uniquesel:
1855 if uniquesel:
1856 fm.data(name=entryname)
1856 fm.data(name=entryname)
1857 fm.write('value', '%s\n', value)
1857 fm.write('value', '%s\n', value)
1858 else:
1858 else:
1859 fm.write('name value', '%s=%s\n', entryname, value)
1859 fm.write('name value', '%s=%s\n', entryname, value)
1860 matched = True
1860 matched = True
1861 fm.end()
1861 fm.end()
1862 if matched:
1862 if matched:
1863 return 0
1863 return 0
1864 return 1
1864 return 1
1865
1865
1866 @command('copy|cp',
1866 @command('copy|cp',
1867 [('A', 'after', None, _('record a copy that has already occurred')),
1867 [('A', 'after', None, _('record a copy that has already occurred')),
1868 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1868 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1869 ] + walkopts + dryrunopts,
1869 ] + walkopts + dryrunopts,
1870 _('[OPTION]... [SOURCE]... DEST'),
1870 _('[OPTION]... [SOURCE]... DEST'),
1871 helpcategory=command.CATEGORY_FILE_CONTENTS)
1871 helpcategory=command.CATEGORY_FILE_CONTENTS)
1872 def copy(ui, repo, *pats, **opts):
1872 def copy(ui, repo, *pats, **opts):
1873 """mark files as copied for the next commit
1873 """mark files as copied for the next commit
1874
1874
1875 Mark dest as having copies of source files. If dest is a
1875 Mark dest as having copies of source files. If dest is a
1876 directory, copies are put in that directory. If dest is a file,
1876 directory, copies are put in that directory. If dest is a file,
1877 the source must be a single file.
1877 the source must be a single file.
1878
1878
1879 By default, this command copies the contents of files as they
1879 By default, this command copies the contents of files as they
1880 exist in the working directory. If invoked with -A/--after, the
1880 exist in the working directory. If invoked with -A/--after, the
1881 operation is recorded, but no copying is performed.
1881 operation is recorded, but no copying is performed.
1882
1882
1883 This command takes effect with the next commit. To undo a copy
1883 This command takes effect with the next commit. To undo a copy
1884 before that, see :hg:`revert`.
1884 before that, see :hg:`revert`.
1885
1885
1886 Returns 0 on success, 1 if errors are encountered.
1886 Returns 0 on success, 1 if errors are encountered.
1887 """
1887 """
1888 opts = pycompat.byteskwargs(opts)
1888 opts = pycompat.byteskwargs(opts)
1889 with repo.wlock(False):
1889 with repo.wlock(False):
1890 return cmdutil.copy(ui, repo, pats, opts)
1890 return cmdutil.copy(ui, repo, pats, opts)
1891
1891
1892 @command(
1892 @command(
1893 'debugcommands', [], _('[COMMAND]'),
1893 'debugcommands', [], _('[COMMAND]'),
1894 helpcategory=command.CATEGORY_HELP,
1894 helpcategory=command.CATEGORY_HELP,
1895 norepo=True)
1895 norepo=True)
1896 def debugcommands(ui, cmd='', *args):
1896 def debugcommands(ui, cmd='', *args):
1897 """list all available commands and options"""
1897 """list all available commands and options"""
1898 for cmd, vals in sorted(table.iteritems()):
1898 for cmd, vals in sorted(table.iteritems()):
1899 cmd = cmd.split('|')[0]
1899 cmd = cmd.split('|')[0]
1900 opts = ', '.join([i[1] for i in vals[1]])
1900 opts = ', '.join([i[1] for i in vals[1]])
1901 ui.write('%s: %s\n' % (cmd, opts))
1901 ui.write('%s: %s\n' % (cmd, opts))
1902
1902
1903 @command('debugcomplete',
1903 @command('debugcomplete',
1904 [('o', 'options', None, _('show the command options'))],
1904 [('o', 'options', None, _('show the command options'))],
1905 _('[-o] CMD'),
1905 _('[-o] CMD'),
1906 helpcategory=command.CATEGORY_HELP,
1906 helpcategory=command.CATEGORY_HELP,
1907 norepo=True)
1907 norepo=True)
1908 def debugcomplete(ui, cmd='', **opts):
1908 def debugcomplete(ui, cmd='', **opts):
1909 """returns the completion list associated with the given command"""
1909 """returns the completion list associated with the given command"""
1910
1910
1911 if opts.get(r'options'):
1911 if opts.get(r'options'):
1912 options = []
1912 options = []
1913 otables = [globalopts]
1913 otables = [globalopts]
1914 if cmd:
1914 if cmd:
1915 aliases, entry = cmdutil.findcmd(cmd, table, False)
1915 aliases, entry = cmdutil.findcmd(cmd, table, False)
1916 otables.append(entry[1])
1916 otables.append(entry[1])
1917 for t in otables:
1917 for t in otables:
1918 for o in t:
1918 for o in t:
1919 if "(DEPRECATED)" in o[3]:
1919 if "(DEPRECATED)" in o[3]:
1920 continue
1920 continue
1921 if o[0]:
1921 if o[0]:
1922 options.append('-%s' % o[0])
1922 options.append('-%s' % o[0])
1923 options.append('--%s' % o[1])
1923 options.append('--%s' % o[1])
1924 ui.write("%s\n" % "\n".join(options))
1924 ui.write("%s\n" % "\n".join(options))
1925 return
1925 return
1926
1926
1927 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1927 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1928 if ui.verbose:
1928 if ui.verbose:
1929 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1929 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1930 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1930 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1931
1931
1932 @command('diff',
1932 @command('diff',
1933 [('r', 'rev', [], _('revision'), _('REV')),
1933 [('r', 'rev', [], _('revision'), _('REV')),
1934 ('c', 'change', '', _('change made by revision'), _('REV'))
1934 ('c', 'change', '', _('change made by revision'), _('REV'))
1935 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1935 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1936 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1936 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1937 helpcategory=command.CATEGORY_FILE_CONTENTS,
1937 helpcategory=command.CATEGORY_FILE_CONTENTS,
1938 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1938 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1939 def diff(ui, repo, *pats, **opts):
1939 def diff(ui, repo, *pats, **opts):
1940 """diff repository (or selected files)
1940 """diff repository (or selected files)
1941
1941
1942 Show differences between revisions for the specified files.
1942 Show differences between revisions for the specified files.
1943
1943
1944 Differences between files are shown using the unified diff format.
1944 Differences between files are shown using the unified diff format.
1945
1945
1946 .. note::
1946 .. note::
1947
1947
1948 :hg:`diff` may generate unexpected results for merges, as it will
1948 :hg:`diff` may generate unexpected results for merges, as it will
1949 default to comparing against the working directory's first
1949 default to comparing against the working directory's first
1950 parent changeset if no revisions are specified.
1950 parent changeset if no revisions are specified.
1951
1951
1952 When two revision arguments are given, then changes are shown
1952 When two revision arguments are given, then changes are shown
1953 between those revisions. If only one revision is specified then
1953 between those revisions. If only one revision is specified then
1954 that revision is compared to the working directory, and, when no
1954 that revision is compared to the working directory, and, when no
1955 revisions are specified, the working directory files are compared
1955 revisions are specified, the working directory files are compared
1956 to its first parent.
1956 to its first parent.
1957
1957
1958 Alternatively you can specify -c/--change with a revision to see
1958 Alternatively you can specify -c/--change with a revision to see
1959 the changes in that changeset relative to its first parent.
1959 the changes in that changeset relative to its first parent.
1960
1960
1961 Without the -a/--text option, diff will avoid generating diffs of
1961 Without the -a/--text option, diff will avoid generating diffs of
1962 files it detects as binary. With -a, diff will generate a diff
1962 files it detects as binary. With -a, diff will generate a diff
1963 anyway, probably with undesirable results.
1963 anyway, probably with undesirable results.
1964
1964
1965 Use the -g/--git option to generate diffs in the git extended diff
1965 Use the -g/--git option to generate diffs in the git extended diff
1966 format. For more information, read :hg:`help diffs`.
1966 format. For more information, read :hg:`help diffs`.
1967
1967
1968 .. container:: verbose
1968 .. container:: verbose
1969
1969
1970 Examples:
1970 Examples:
1971
1971
1972 - compare a file in the current working directory to its parent::
1972 - compare a file in the current working directory to its parent::
1973
1973
1974 hg diff foo.c
1974 hg diff foo.c
1975
1975
1976 - compare two historical versions of a directory, with rename info::
1976 - compare two historical versions of a directory, with rename info::
1977
1977
1978 hg diff --git -r 1.0:1.2 lib/
1978 hg diff --git -r 1.0:1.2 lib/
1979
1979
1980 - get change stats relative to the last change on some date::
1980 - get change stats relative to the last change on some date::
1981
1981
1982 hg diff --stat -r "date('may 2')"
1982 hg diff --stat -r "date('may 2')"
1983
1983
1984 - diff all newly-added files that contain a keyword::
1984 - diff all newly-added files that contain a keyword::
1985
1985
1986 hg diff "set:added() and grep(GNU)"
1986 hg diff "set:added() and grep(GNU)"
1987
1987
1988 - compare a revision and its parents::
1988 - compare a revision and its parents::
1989
1989
1990 hg diff -c 9353 # compare against first parent
1990 hg diff -c 9353 # compare against first parent
1991 hg diff -r 9353^:9353 # same using revset syntax
1991 hg diff -r 9353^:9353 # same using revset syntax
1992 hg diff -r 9353^2:9353 # compare against the second parent
1992 hg diff -r 9353^2:9353 # compare against the second parent
1993
1993
1994 Returns 0 on success.
1994 Returns 0 on success.
1995 """
1995 """
1996
1996
1997 opts = pycompat.byteskwargs(opts)
1997 opts = pycompat.byteskwargs(opts)
1998 revs = opts.get('rev')
1998 revs = opts.get('rev')
1999 change = opts.get('change')
1999 change = opts.get('change')
2000 stat = opts.get('stat')
2000 stat = opts.get('stat')
2001 reverse = opts.get('reverse')
2001 reverse = opts.get('reverse')
2002
2002
2003 if revs and change:
2003 if revs and change:
2004 msg = _('cannot specify --rev and --change at the same time')
2004 msg = _('cannot specify --rev and --change at the same time')
2005 raise error.Abort(msg)
2005 raise error.Abort(msg)
2006 elif change:
2006 elif change:
2007 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2007 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2008 ctx2 = scmutil.revsingle(repo, change, None)
2008 ctx2 = scmutil.revsingle(repo, change, None)
2009 ctx1 = ctx2.p1()
2009 ctx1 = ctx2.p1()
2010 else:
2010 else:
2011 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2011 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2012 ctx1, ctx2 = scmutil.revpair(repo, revs)
2012 ctx1, ctx2 = scmutil.revpair(repo, revs)
2013 node1, node2 = ctx1.node(), ctx2.node()
2013 node1, node2 = ctx1.node(), ctx2.node()
2014
2014
2015 if reverse:
2015 if reverse:
2016 node1, node2 = node2, node1
2016 node1, node2 = node2, node1
2017
2017
2018 diffopts = patch.diffallopts(ui, opts)
2018 diffopts = patch.diffallopts(ui, opts)
2019 m = scmutil.match(ctx2, pats, opts)
2019 m = scmutil.match(ctx2, pats, opts)
2020 m = repo.narrowmatch(m)
2020 m = repo.narrowmatch(m)
2021 ui.pager('diff')
2021 ui.pager('diff')
2022 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2022 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2023 listsubrepos=opts.get('subrepos'),
2023 listsubrepos=opts.get('subrepos'),
2024 root=opts.get('root'))
2024 root=opts.get('root'))
2025
2025
2026 @command('export',
2026 @command('export',
2027 [('B', 'bookmark', '',
2027 [('B', 'bookmark', '',
2028 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2028 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2029 ('o', 'output', '',
2029 ('o', 'output', '',
2030 _('print output to file with formatted name'), _('FORMAT')),
2030 _('print output to file with formatted name'), _('FORMAT')),
2031 ('', 'switch-parent', None, _('diff against the second parent')),
2031 ('', 'switch-parent', None, _('diff against the second parent')),
2032 ('r', 'rev', [], _('revisions to export'), _('REV')),
2032 ('r', 'rev', [], _('revisions to export'), _('REV')),
2033 ] + diffopts + formatteropts,
2033 ] + diffopts + formatteropts,
2034 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2034 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2035 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2035 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2036 helpbasic=True, intents={INTENT_READONLY})
2036 helpbasic=True, intents={INTENT_READONLY})
2037 def export(ui, repo, *changesets, **opts):
2037 def export(ui, repo, *changesets, **opts):
2038 """dump the header and diffs for one or more changesets
2038 """dump the header and diffs for one or more changesets
2039
2039
2040 Print the changeset header and diffs for one or more revisions.
2040 Print the changeset header and diffs for one or more revisions.
2041 If no revision is given, the parent of the working directory is used.
2041 If no revision is given, the parent of the working directory is used.
2042
2042
2043 The information shown in the changeset header is: author, date,
2043 The information shown in the changeset header is: author, date,
2044 branch name (if non-default), changeset hash, parent(s) and commit
2044 branch name (if non-default), changeset hash, parent(s) and commit
2045 comment.
2045 comment.
2046
2046
2047 .. note::
2047 .. note::
2048
2048
2049 :hg:`export` may generate unexpected diff output for merge
2049 :hg:`export` may generate unexpected diff output for merge
2050 changesets, as it will compare the merge changeset against its
2050 changesets, as it will compare the merge changeset against its
2051 first parent only.
2051 first parent only.
2052
2052
2053 Output may be to a file, in which case the name of the file is
2053 Output may be to a file, in which case the name of the file is
2054 given using a template string. See :hg:`help templates`. In addition
2054 given using a template string. See :hg:`help templates`. In addition
2055 to the common template keywords, the following formatting rules are
2055 to the common template keywords, the following formatting rules are
2056 supported:
2056 supported:
2057
2057
2058 :``%%``: literal "%" character
2058 :``%%``: literal "%" character
2059 :``%H``: changeset hash (40 hexadecimal digits)
2059 :``%H``: changeset hash (40 hexadecimal digits)
2060 :``%N``: number of patches being generated
2060 :``%N``: number of patches being generated
2061 :``%R``: changeset revision number
2061 :``%R``: changeset revision number
2062 :``%b``: basename of the exporting repository
2062 :``%b``: basename of the exporting repository
2063 :``%h``: short-form changeset hash (12 hexadecimal digits)
2063 :``%h``: short-form changeset hash (12 hexadecimal digits)
2064 :``%m``: first line of the commit message (only alphanumeric characters)
2064 :``%m``: first line of the commit message (only alphanumeric characters)
2065 :``%n``: zero-padded sequence number, starting at 1
2065 :``%n``: zero-padded sequence number, starting at 1
2066 :``%r``: zero-padded changeset revision number
2066 :``%r``: zero-padded changeset revision number
2067 :``\\``: literal "\\" character
2067 :``\\``: literal "\\" character
2068
2068
2069 Without the -a/--text option, export will avoid generating diffs
2069 Without the -a/--text option, export will avoid generating diffs
2070 of files it detects as binary. With -a, export will generate a
2070 of files it detects as binary. With -a, export will generate a
2071 diff anyway, probably with undesirable results.
2071 diff anyway, probably with undesirable results.
2072
2072
2073 With -B/--bookmark changesets reachable by the given bookmark are
2073 With -B/--bookmark changesets reachable by the given bookmark are
2074 selected.
2074 selected.
2075
2075
2076 Use the -g/--git option to generate diffs in the git extended diff
2076 Use the -g/--git option to generate diffs in the git extended diff
2077 format. See :hg:`help diffs` for more information.
2077 format. See :hg:`help diffs` for more information.
2078
2078
2079 With the --switch-parent option, the diff will be against the
2079 With the --switch-parent option, the diff will be against the
2080 second parent. It can be useful to review a merge.
2080 second parent. It can be useful to review a merge.
2081
2081
2082 .. container:: verbose
2082 .. container:: verbose
2083
2083
2084 Template:
2084 Template:
2085
2085
2086 The following keywords are supported in addition to the common template
2086 The following keywords are supported in addition to the common template
2087 keywords and functions. See also :hg:`help templates`.
2087 keywords and functions. See also :hg:`help templates`.
2088
2088
2089 :diff: String. Diff content.
2089 :diff: String. Diff content.
2090 :parents: List of strings. Parent nodes of the changeset.
2090 :parents: List of strings. Parent nodes of the changeset.
2091
2091
2092 Examples:
2092 Examples:
2093
2093
2094 - use export and import to transplant a bugfix to the current
2094 - use export and import to transplant a bugfix to the current
2095 branch::
2095 branch::
2096
2096
2097 hg export -r 9353 | hg import -
2097 hg export -r 9353 | hg import -
2098
2098
2099 - export all the changesets between two revisions to a file with
2099 - export all the changesets between two revisions to a file with
2100 rename information::
2100 rename information::
2101
2101
2102 hg export --git -r 123:150 > changes.txt
2102 hg export --git -r 123:150 > changes.txt
2103
2103
2104 - split outgoing changes into a series of patches with
2104 - split outgoing changes into a series of patches with
2105 descriptive names::
2105 descriptive names::
2106
2106
2107 hg export -r "outgoing()" -o "%n-%m.patch"
2107 hg export -r "outgoing()" -o "%n-%m.patch"
2108
2108
2109 Returns 0 on success.
2109 Returns 0 on success.
2110 """
2110 """
2111 opts = pycompat.byteskwargs(opts)
2111 opts = pycompat.byteskwargs(opts)
2112 bookmark = opts.get('bookmark')
2112 bookmark = opts.get('bookmark')
2113 changesets += tuple(opts.get('rev', []))
2113 changesets += tuple(opts.get('rev', []))
2114
2114
2115 if bookmark and changesets:
2115 if bookmark and changesets:
2116 raise error.Abort(_("-r and -B are mutually exclusive"))
2116 raise error.Abort(_("-r and -B are mutually exclusive"))
2117
2117
2118 if bookmark:
2118 if bookmark:
2119 if bookmark not in repo._bookmarks:
2119 if bookmark not in repo._bookmarks:
2120 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2120 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2121
2121
2122 revs = scmutil.bookmarkrevs(repo, bookmark)
2122 revs = scmutil.bookmarkrevs(repo, bookmark)
2123 else:
2123 else:
2124 if not changesets:
2124 if not changesets:
2125 changesets = ['.']
2125 changesets = ['.']
2126
2126
2127 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2127 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2128 revs = scmutil.revrange(repo, changesets)
2128 revs = scmutil.revrange(repo, changesets)
2129
2129
2130 if not revs:
2130 if not revs:
2131 raise error.Abort(_("export requires at least one changeset"))
2131 raise error.Abort(_("export requires at least one changeset"))
2132 if len(revs) > 1:
2132 if len(revs) > 1:
2133 ui.note(_('exporting patches:\n'))
2133 ui.note(_('exporting patches:\n'))
2134 else:
2134 else:
2135 ui.note(_('exporting patch:\n'))
2135 ui.note(_('exporting patch:\n'))
2136
2136
2137 fntemplate = opts.get('output')
2137 fntemplate = opts.get('output')
2138 if cmdutil.isstdiofilename(fntemplate):
2138 if cmdutil.isstdiofilename(fntemplate):
2139 fntemplate = ''
2139 fntemplate = ''
2140
2140
2141 if fntemplate:
2141 if fntemplate:
2142 fm = formatter.nullformatter(ui, 'export', opts)
2142 fm = formatter.nullformatter(ui, 'export', opts)
2143 else:
2143 else:
2144 ui.pager('export')
2144 ui.pager('export')
2145 fm = ui.formatter('export', opts)
2145 fm = ui.formatter('export', opts)
2146 with fm:
2146 with fm:
2147 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2147 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2148 switch_parent=opts.get('switch_parent'),
2148 switch_parent=opts.get('switch_parent'),
2149 opts=patch.diffallopts(ui, opts))
2149 opts=patch.diffallopts(ui, opts))
2150
2150
2151 @command('files',
2151 @command('files',
2152 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2152 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2153 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2153 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2154 ] + walkopts + formatteropts + subrepoopts,
2154 ] + walkopts + formatteropts + subrepoopts,
2155 _('[OPTION]... [FILE]...'),
2155 _('[OPTION]... [FILE]...'),
2156 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2156 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2157 intents={INTENT_READONLY})
2157 intents={INTENT_READONLY})
2158 def files(ui, repo, *pats, **opts):
2158 def files(ui, repo, *pats, **opts):
2159 """list tracked files
2159 """list tracked files
2160
2160
2161 Print files under Mercurial control in the working directory or
2161 Print files under Mercurial control in the working directory or
2162 specified revision for given files (excluding removed files).
2162 specified revision for given files (excluding removed files).
2163 Files can be specified as filenames or filesets.
2163 Files can be specified as filenames or filesets.
2164
2164
2165 If no files are given to match, this command prints the names
2165 If no files are given to match, this command prints the names
2166 of all files under Mercurial control.
2166 of all files under Mercurial control.
2167
2167
2168 .. container:: verbose
2168 .. container:: verbose
2169
2169
2170 Template:
2170 Template:
2171
2171
2172 The following keywords are supported in addition to the common template
2172 The following keywords are supported in addition to the common template
2173 keywords and functions. See also :hg:`help templates`.
2173 keywords and functions. See also :hg:`help templates`.
2174
2174
2175 :flags: String. Character denoting file's symlink and executable bits.
2175 :flags: String. Character denoting file's symlink and executable bits.
2176 :path: String. Repository-absolute path of the file.
2176 :path: String. Repository-absolute path of the file.
2177 :size: Integer. Size of the file in bytes.
2177 :size: Integer. Size of the file in bytes.
2178
2178
2179 Examples:
2179 Examples:
2180
2180
2181 - list all files under the current directory::
2181 - list all files under the current directory::
2182
2182
2183 hg files .
2183 hg files .
2184
2184
2185 - shows sizes and flags for current revision::
2185 - shows sizes and flags for current revision::
2186
2186
2187 hg files -vr .
2187 hg files -vr .
2188
2188
2189 - list all files named README::
2189 - list all files named README::
2190
2190
2191 hg files -I "**/README"
2191 hg files -I "**/README"
2192
2192
2193 - list all binary files::
2193 - list all binary files::
2194
2194
2195 hg files "set:binary()"
2195 hg files "set:binary()"
2196
2196
2197 - find files containing a regular expression::
2197 - find files containing a regular expression::
2198
2198
2199 hg files "set:grep('bob')"
2199 hg files "set:grep('bob')"
2200
2200
2201 - search tracked file contents with xargs and grep::
2201 - search tracked file contents with xargs and grep::
2202
2202
2203 hg files -0 | xargs -0 grep foo
2203 hg files -0 | xargs -0 grep foo
2204
2204
2205 See :hg:`help patterns` and :hg:`help filesets` for more information
2205 See :hg:`help patterns` and :hg:`help filesets` for more information
2206 on specifying file patterns.
2206 on specifying file patterns.
2207
2207
2208 Returns 0 if a match is found, 1 otherwise.
2208 Returns 0 if a match is found, 1 otherwise.
2209
2209
2210 """
2210 """
2211
2211
2212 opts = pycompat.byteskwargs(opts)
2212 opts = pycompat.byteskwargs(opts)
2213 rev = opts.get('rev')
2213 rev = opts.get('rev')
2214 if rev:
2214 if rev:
2215 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2215 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2216 ctx = scmutil.revsingle(repo, rev, None)
2216 ctx = scmutil.revsingle(repo, rev, None)
2217
2217
2218 end = '\n'
2218 end = '\n'
2219 if opts.get('print0'):
2219 if opts.get('print0'):
2220 end = '\0'
2220 end = '\0'
2221 fmt = '%s' + end
2221 fmt = '%s' + end
2222
2222
2223 m = scmutil.match(ctx, pats, opts)
2223 m = scmutil.match(ctx, pats, opts)
2224 ui.pager('files')
2224 ui.pager('files')
2225 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2225 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2226 with ui.formatter('files', opts) as fm:
2226 with ui.formatter('files', opts) as fm:
2227 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2227 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2228 opts.get('subrepos'))
2228 opts.get('subrepos'))
2229
2229
2230 @command(
2230 @command(
2231 'forget',
2231 'forget',
2232 [('i', 'interactive', None, _('use interactive mode')),
2232 [('i', 'interactive', None, _('use interactive mode')),
2233 ] + walkopts + dryrunopts,
2233 ] + walkopts + dryrunopts,
2234 _('[OPTION]... FILE...'),
2234 _('[OPTION]... FILE...'),
2235 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2235 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2236 helpbasic=True, inferrepo=True)
2236 helpbasic=True, inferrepo=True)
2237 def forget(ui, repo, *pats, **opts):
2237 def forget(ui, repo, *pats, **opts):
2238 """forget the specified files on the next commit
2238 """forget the specified files on the next commit
2239
2239
2240 Mark the specified files so they will no longer be tracked
2240 Mark the specified files so they will no longer be tracked
2241 after the next commit.
2241 after the next commit.
2242
2242
2243 This only removes files from the current branch, not from the
2243 This only removes files from the current branch, not from the
2244 entire project history, and it does not delete them from the
2244 entire project history, and it does not delete them from the
2245 working directory.
2245 working directory.
2246
2246
2247 To delete the file from the working directory, see :hg:`remove`.
2247 To delete the file from the working directory, see :hg:`remove`.
2248
2248
2249 To undo a forget before the next commit, see :hg:`add`.
2249 To undo a forget before the next commit, see :hg:`add`.
2250
2250
2251 .. container:: verbose
2251 .. container:: verbose
2252
2252
2253 Examples:
2253 Examples:
2254
2254
2255 - forget newly-added binary files::
2255 - forget newly-added binary files::
2256
2256
2257 hg forget "set:added() and binary()"
2257 hg forget "set:added() and binary()"
2258
2258
2259 - forget files that would be excluded by .hgignore::
2259 - forget files that would be excluded by .hgignore::
2260
2260
2261 hg forget "set:hgignore()"
2261 hg forget "set:hgignore()"
2262
2262
2263 Returns 0 on success.
2263 Returns 0 on success.
2264 """
2264 """
2265
2265
2266 opts = pycompat.byteskwargs(opts)
2266 opts = pycompat.byteskwargs(opts)
2267 if not pats:
2267 if not pats:
2268 raise error.Abort(_('no files specified'))
2268 raise error.Abort(_('no files specified'))
2269
2269
2270 m = scmutil.match(repo[None], pats, opts)
2270 m = scmutil.match(repo[None], pats, opts)
2271 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2271 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2272 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2272 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2273 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2273 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2274 explicitonly=False, dryrun=dryrun,
2274 explicitonly=False, dryrun=dryrun,
2275 interactive=interactive)[0]
2275 interactive=interactive)[0]
2276 return rejected and 1 or 0
2276 return rejected and 1 or 0
2277
2277
2278 @command(
2278 @command(
2279 'graft',
2279 'graft',
2280 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2280 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2281 ('', 'base', '',
2281 ('', 'base', '',
2282 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2282 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2283 ('c', 'continue', False, _('resume interrupted graft')),
2283 ('c', 'continue', False, _('resume interrupted graft')),
2284 ('', 'stop', False, _('stop interrupted graft')),
2284 ('', 'stop', False, _('stop interrupted graft')),
2285 ('', 'abort', False, _('abort interrupted graft')),
2285 ('', 'abort', False, _('abort interrupted graft')),
2286 ('e', 'edit', False, _('invoke editor on commit messages')),
2286 ('e', 'edit', False, _('invoke editor on commit messages')),
2287 ('', 'log', None, _('append graft info to log message')),
2287 ('', 'log', None, _('append graft info to log message')),
2288 ('', 'no-commit', None,
2288 ('', 'no-commit', None,
2289 _("don't commit, just apply the changes in working directory")),
2289 _("don't commit, just apply the changes in working directory")),
2290 ('f', 'force', False, _('force graft')),
2290 ('f', 'force', False, _('force graft')),
2291 ('D', 'currentdate', False,
2291 ('D', 'currentdate', False,
2292 _('record the current date as commit date')),
2292 _('record the current date as commit date')),
2293 ('U', 'currentuser', False,
2293 ('U', 'currentuser', False,
2294 _('record the current user as committer'))]
2294 _('record the current user as committer'))]
2295 + commitopts2 + mergetoolopts + dryrunopts,
2295 + commitopts2 + mergetoolopts + dryrunopts,
2296 _('[OPTION]... [-r REV]... REV...'),
2296 _('[OPTION]... [-r REV]... REV...'),
2297 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2297 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2298 def graft(ui, repo, *revs, **opts):
2298 def graft(ui, repo, *revs, **opts):
2299 '''copy changes from other branches onto the current branch
2299 '''copy changes from other branches onto the current branch
2300
2300
2301 This command uses Mercurial's merge logic to copy individual
2301 This command uses Mercurial's merge logic to copy individual
2302 changes from other branches without merging branches in the
2302 changes from other branches without merging branches in the
2303 history graph. This is sometimes known as 'backporting' or
2303 history graph. This is sometimes known as 'backporting' or
2304 'cherry-picking'. By default, graft will copy user, date, and
2304 'cherry-picking'. By default, graft will copy user, date, and
2305 description from the source changesets.
2305 description from the source changesets.
2306
2306
2307 Changesets that are ancestors of the current revision, that have
2307 Changesets that are ancestors of the current revision, that have
2308 already been grafted, or that are merges will be skipped.
2308 already been grafted, or that are merges will be skipped.
2309
2309
2310 If --log is specified, log messages will have a comment appended
2310 If --log is specified, log messages will have a comment appended
2311 of the form::
2311 of the form::
2312
2312
2313 (grafted from CHANGESETHASH)
2313 (grafted from CHANGESETHASH)
2314
2314
2315 If --force is specified, revisions will be grafted even if they
2315 If --force is specified, revisions will be grafted even if they
2316 are already ancestors of, or have been grafted to, the destination.
2316 are already ancestors of, or have been grafted to, the destination.
2317 This is useful when the revisions have since been backed out.
2317 This is useful when the revisions have since been backed out.
2318
2318
2319 If a graft merge results in conflicts, the graft process is
2319 If a graft merge results in conflicts, the graft process is
2320 interrupted so that the current merge can be manually resolved.
2320 interrupted so that the current merge can be manually resolved.
2321 Once all conflicts are addressed, the graft process can be
2321 Once all conflicts are addressed, the graft process can be
2322 continued with the -c/--continue option.
2322 continued with the -c/--continue option.
2323
2323
2324 The -c/--continue option reapplies all the earlier options.
2324 The -c/--continue option reapplies all the earlier options.
2325
2325
2326 .. container:: verbose
2326 .. container:: verbose
2327
2327
2328 The --base option exposes more of how graft internally uses merge with a
2328 The --base option exposes more of how graft internally uses merge with a
2329 custom base revision. --base can be used to specify another ancestor than
2329 custom base revision. --base can be used to specify another ancestor than
2330 the first and only parent.
2330 the first and only parent.
2331
2331
2332 The command::
2332 The command::
2333
2333
2334 hg graft -r 345 --base 234
2334 hg graft -r 345 --base 234
2335
2335
2336 is thus pretty much the same as::
2336 is thus pretty much the same as::
2337
2337
2338 hg diff -r 234 -r 345 | hg import
2338 hg diff -r 234 -r 345 | hg import
2339
2339
2340 but using merge to resolve conflicts and track moved files.
2340 but using merge to resolve conflicts and track moved files.
2341
2341
2342 The result of a merge can thus be backported as a single commit by
2342 The result of a merge can thus be backported as a single commit by
2343 specifying one of the merge parents as base, and thus effectively
2343 specifying one of the merge parents as base, and thus effectively
2344 grafting the changes from the other side.
2344 grafting the changes from the other side.
2345
2345
2346 It is also possible to collapse multiple changesets and clean up history
2346 It is also possible to collapse multiple changesets and clean up history
2347 by specifying another ancestor as base, much like rebase --collapse
2347 by specifying another ancestor as base, much like rebase --collapse
2348 --keep.
2348 --keep.
2349
2349
2350 The commit message can be tweaked after the fact using commit --amend .
2350 The commit message can be tweaked after the fact using commit --amend .
2351
2351
2352 For using non-ancestors as the base to backout changes, see the backout
2352 For using non-ancestors as the base to backout changes, see the backout
2353 command and the hidden --parent option.
2353 command and the hidden --parent option.
2354
2354
2355 .. container:: verbose
2355 .. container:: verbose
2356
2356
2357 Examples:
2357 Examples:
2358
2358
2359 - copy a single change to the stable branch and edit its description::
2359 - copy a single change to the stable branch and edit its description::
2360
2360
2361 hg update stable
2361 hg update stable
2362 hg graft --edit 9393
2362 hg graft --edit 9393
2363
2363
2364 - graft a range of changesets with one exception, updating dates::
2364 - graft a range of changesets with one exception, updating dates::
2365
2365
2366 hg graft -D "2085::2093 and not 2091"
2366 hg graft -D "2085::2093 and not 2091"
2367
2367
2368 - continue a graft after resolving conflicts::
2368 - continue a graft after resolving conflicts::
2369
2369
2370 hg graft -c
2370 hg graft -c
2371
2371
2372 - show the source of a grafted changeset::
2372 - show the source of a grafted changeset::
2373
2373
2374 hg log --debug -r .
2374 hg log --debug -r .
2375
2375
2376 - show revisions sorted by date::
2376 - show revisions sorted by date::
2377
2377
2378 hg log -r "sort(all(), date)"
2378 hg log -r "sort(all(), date)"
2379
2379
2380 - backport the result of a merge as a single commit::
2380 - backport the result of a merge as a single commit::
2381
2381
2382 hg graft -r 123 --base 123^
2382 hg graft -r 123 --base 123^
2383
2383
2384 - land a feature branch as one changeset::
2384 - land a feature branch as one changeset::
2385
2385
2386 hg up -cr default
2386 hg up -cr default
2387 hg graft -r featureX --base "ancestor('featureX', 'default')"
2387 hg graft -r featureX --base "ancestor('featureX', 'default')"
2388
2388
2389 See :hg:`help revisions` for more about specifying revisions.
2389 See :hg:`help revisions` for more about specifying revisions.
2390
2390
2391 Returns 0 on successful completion.
2391 Returns 0 on successful completion.
2392 '''
2392 '''
2393 with repo.wlock():
2393 with repo.wlock():
2394 return _dograft(ui, repo, *revs, **opts)
2394 return _dograft(ui, repo, *revs, **opts)
2395
2395
2396 def _dograft(ui, repo, *revs, **opts):
2396 def _dograft(ui, repo, *revs, **opts):
2397 opts = pycompat.byteskwargs(opts)
2397 opts = pycompat.byteskwargs(opts)
2398 if revs and opts.get('rev'):
2398 if revs and opts.get('rev'):
2399 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2399 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2400 'revision ordering!\n'))
2400 'revision ordering!\n'))
2401
2401
2402 revs = list(revs)
2402 revs = list(revs)
2403 revs.extend(opts.get('rev'))
2403 revs.extend(opts.get('rev'))
2404 basectx = None
2404 basectx = None
2405 if opts.get('base'):
2405 if opts.get('base'):
2406 basectx = scmutil.revsingle(repo, opts['base'], None)
2406 basectx = scmutil.revsingle(repo, opts['base'], None)
2407 # a dict of data to be stored in state file
2407 # a dict of data to be stored in state file
2408 statedata = {}
2408 statedata = {}
2409 # list of new nodes created by ongoing graft
2409 # list of new nodes created by ongoing graft
2410 statedata['newnodes'] = []
2410 statedata['newnodes'] = []
2411
2411
2412 if opts.get('user') and opts.get('currentuser'):
2412 if opts.get('user') and opts.get('currentuser'):
2413 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2413 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2414 if opts.get('date') and opts.get('currentdate'):
2414 if opts.get('date') and opts.get('currentdate'):
2415 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2415 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2416 if not opts.get('user') and opts.get('currentuser'):
2416 if not opts.get('user') and opts.get('currentuser'):
2417 opts['user'] = ui.username()
2417 opts['user'] = ui.username()
2418 if not opts.get('date') and opts.get('currentdate'):
2418 if not opts.get('date') and opts.get('currentdate'):
2419 opts['date'] = "%d %d" % dateutil.makedate()
2419 opts['date'] = "%d %d" % dateutil.makedate()
2420
2420
2421 editor = cmdutil.getcommiteditor(editform='graft',
2421 editor = cmdutil.getcommiteditor(editform='graft',
2422 **pycompat.strkwargs(opts))
2422 **pycompat.strkwargs(opts))
2423
2423
2424 cont = False
2424 cont = False
2425 if opts.get('no_commit'):
2425 if opts.get('no_commit'):
2426 if opts.get('edit'):
2426 if opts.get('edit'):
2427 raise error.Abort(_("cannot specify --no-commit and "
2427 raise error.Abort(_("cannot specify --no-commit and "
2428 "--edit together"))
2428 "--edit together"))
2429 if opts.get('currentuser'):
2429 if opts.get('currentuser'):
2430 raise error.Abort(_("cannot specify --no-commit and "
2430 raise error.Abort(_("cannot specify --no-commit and "
2431 "--currentuser together"))
2431 "--currentuser together"))
2432 if opts.get('currentdate'):
2432 if opts.get('currentdate'):
2433 raise error.Abort(_("cannot specify --no-commit and "
2433 raise error.Abort(_("cannot specify --no-commit and "
2434 "--currentdate together"))
2434 "--currentdate together"))
2435 if opts.get('log'):
2435 if opts.get('log'):
2436 raise error.Abort(_("cannot specify --no-commit and "
2436 raise error.Abort(_("cannot specify --no-commit and "
2437 "--log together"))
2437 "--log together"))
2438
2438
2439 graftstate = statemod.cmdstate(repo, 'graftstate')
2439 graftstate = statemod.cmdstate(repo, 'graftstate')
2440
2440
2441 if opts.get('stop'):
2441 if opts.get('stop'):
2442 if opts.get('continue'):
2442 if opts.get('continue'):
2443 raise error.Abort(_("cannot use '--continue' and "
2443 raise error.Abort(_("cannot use '--continue' and "
2444 "'--stop' together"))
2444 "'--stop' together"))
2445 if opts.get('abort'):
2445 if opts.get('abort'):
2446 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2446 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2447
2447
2448 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2448 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2449 opts.get('date'), opts.get('currentdate'),
2449 opts.get('date'), opts.get('currentdate'),
2450 opts.get('currentuser'), opts.get('rev'))):
2450 opts.get('currentuser'), opts.get('rev'))):
2451 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2451 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2452 return _stopgraft(ui, repo, graftstate)
2452 return _stopgraft(ui, repo, graftstate)
2453 elif opts.get('abort'):
2453 elif opts.get('abort'):
2454 if opts.get('continue'):
2454 if opts.get('continue'):
2455 raise error.Abort(_("cannot use '--continue' and "
2455 raise error.Abort(_("cannot use '--continue' and "
2456 "'--abort' together"))
2456 "'--abort' together"))
2457 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2457 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2458 opts.get('date'), opts.get('currentdate'),
2458 opts.get('date'), opts.get('currentdate'),
2459 opts.get('currentuser'), opts.get('rev'))):
2459 opts.get('currentuser'), opts.get('rev'))):
2460 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2460 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2461
2461
2462 return _abortgraft(ui, repo, graftstate)
2462 return _abortgraft(ui, repo, graftstate)
2463 elif opts.get('continue'):
2463 elif opts.get('continue'):
2464 cont = True
2464 cont = True
2465 if revs:
2465 if revs:
2466 raise error.Abort(_("can't specify --continue and revisions"))
2466 raise error.Abort(_("can't specify --continue and revisions"))
2467 # read in unfinished revisions
2467 # read in unfinished revisions
2468 if graftstate.exists():
2468 if graftstate.exists():
2469 statedata = _readgraftstate(repo, graftstate)
2469 statedata = _readgraftstate(repo, graftstate)
2470 if statedata.get('date'):
2470 if statedata.get('date'):
2471 opts['date'] = statedata['date']
2471 opts['date'] = statedata['date']
2472 if statedata.get('user'):
2472 if statedata.get('user'):
2473 opts['user'] = statedata['user']
2473 opts['user'] = statedata['user']
2474 if statedata.get('log'):
2474 if statedata.get('log'):
2475 opts['log'] = True
2475 opts['log'] = True
2476 if statedata.get('no_commit'):
2476 if statedata.get('no_commit'):
2477 opts['no_commit'] = statedata.get('no_commit')
2477 opts['no_commit'] = statedata.get('no_commit')
2478 nodes = statedata['nodes']
2478 nodes = statedata['nodes']
2479 revs = [repo[node].rev() for node in nodes]
2479 revs = [repo[node].rev() for node in nodes]
2480 else:
2480 else:
2481 cmdutil.wrongtooltocontinue(repo, _('graft'))
2481 cmdutil.wrongtooltocontinue(repo, _('graft'))
2482 else:
2482 else:
2483 if not revs:
2483 if not revs:
2484 raise error.Abort(_('no revisions specified'))
2484 raise error.Abort(_('no revisions specified'))
2485 cmdutil.checkunfinished(repo)
2485 cmdutil.checkunfinished(repo)
2486 cmdutil.bailifchanged(repo)
2486 cmdutil.bailifchanged(repo)
2487 revs = scmutil.revrange(repo, revs)
2487 revs = scmutil.revrange(repo, revs)
2488
2488
2489 skipped = set()
2489 skipped = set()
2490 if basectx is None:
2490 if basectx is None:
2491 # check for merges
2491 # check for merges
2492 for rev in repo.revs('%ld and merge()', revs):
2492 for rev in repo.revs('%ld and merge()', revs):
2493 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2493 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2494 skipped.add(rev)
2494 skipped.add(rev)
2495 revs = [r for r in revs if r not in skipped]
2495 revs = [r for r in revs if r not in skipped]
2496 if not revs:
2496 if not revs:
2497 return -1
2497 return -1
2498 if basectx is not None and len(revs) != 1:
2498 if basectx is not None and len(revs) != 1:
2499 raise error.Abort(_('only one revision allowed with --base '))
2499 raise error.Abort(_('only one revision allowed with --base '))
2500
2500
2501 # Don't check in the --continue case, in effect retaining --force across
2501 # Don't check in the --continue case, in effect retaining --force across
2502 # --continues. That's because without --force, any revisions we decided to
2502 # --continues. That's because without --force, any revisions we decided to
2503 # skip would have been filtered out here, so they wouldn't have made their
2503 # skip would have been filtered out here, so they wouldn't have made their
2504 # way to the graftstate. With --force, any revisions we would have otherwise
2504 # way to the graftstate. With --force, any revisions we would have otherwise
2505 # skipped would not have been filtered out, and if they hadn't been applied
2505 # skipped would not have been filtered out, and if they hadn't been applied
2506 # already, they'd have been in the graftstate.
2506 # already, they'd have been in the graftstate.
2507 if not (cont or opts.get('force')) and basectx is None:
2507 if not (cont or opts.get('force')) and basectx is None:
2508 # check for ancestors of dest branch
2508 # check for ancestors of dest branch
2509 crev = repo['.'].rev()
2509 crev = repo['.'].rev()
2510 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2510 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2511 # XXX make this lazy in the future
2511 # XXX make this lazy in the future
2512 # don't mutate while iterating, create a copy
2512 # don't mutate while iterating, create a copy
2513 for rev in list(revs):
2513 for rev in list(revs):
2514 if rev in ancestors:
2514 if rev in ancestors:
2515 ui.warn(_('skipping ancestor revision %d:%s\n') %
2515 ui.warn(_('skipping ancestor revision %d:%s\n') %
2516 (rev, repo[rev]))
2516 (rev, repo[rev]))
2517 # XXX remove on list is slow
2517 # XXX remove on list is slow
2518 revs.remove(rev)
2518 revs.remove(rev)
2519 if not revs:
2519 if not revs:
2520 return -1
2520 return -1
2521
2521
2522 # analyze revs for earlier grafts
2522 # analyze revs for earlier grafts
2523 ids = {}
2523 ids = {}
2524 for ctx in repo.set("%ld", revs):
2524 for ctx in repo.set("%ld", revs):
2525 ids[ctx.hex()] = ctx.rev()
2525 ids[ctx.hex()] = ctx.rev()
2526 n = ctx.extra().get('source')
2526 n = ctx.extra().get('source')
2527 if n:
2527 if n:
2528 ids[n] = ctx.rev()
2528 ids[n] = ctx.rev()
2529
2529
2530 # check ancestors for earlier grafts
2530 # check ancestors for earlier grafts
2531 ui.debug('scanning for duplicate grafts\n')
2531 ui.debug('scanning for duplicate grafts\n')
2532
2532
2533 # The only changesets we can be sure doesn't contain grafts of any
2533 # The only changesets we can be sure doesn't contain grafts of any
2534 # revs, are the ones that are common ancestors of *all* revs:
2534 # revs, are the ones that are common ancestors of *all* revs:
2535 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2535 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2536 ctx = repo[rev]
2536 ctx = repo[rev]
2537 n = ctx.extra().get('source')
2537 n = ctx.extra().get('source')
2538 if n in ids:
2538 if n in ids:
2539 try:
2539 try:
2540 r = repo[n].rev()
2540 r = repo[n].rev()
2541 except error.RepoLookupError:
2541 except error.RepoLookupError:
2542 r = None
2542 r = None
2543 if r in revs:
2543 if r in revs:
2544 ui.warn(_('skipping revision %d:%s '
2544 ui.warn(_('skipping revision %d:%s '
2545 '(already grafted to %d:%s)\n')
2545 '(already grafted to %d:%s)\n')
2546 % (r, repo[r], rev, ctx))
2546 % (r, repo[r], rev, ctx))
2547 revs.remove(r)
2547 revs.remove(r)
2548 elif ids[n] in revs:
2548 elif ids[n] in revs:
2549 if r is None:
2549 if r is None:
2550 ui.warn(_('skipping already grafted revision %d:%s '
2550 ui.warn(_('skipping already grafted revision %d:%s '
2551 '(%d:%s also has unknown origin %s)\n')
2551 '(%d:%s also has unknown origin %s)\n')
2552 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2552 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2553 else:
2553 else:
2554 ui.warn(_('skipping already grafted revision %d:%s '
2554 ui.warn(_('skipping already grafted revision %d:%s '
2555 '(%d:%s also has origin %d:%s)\n')
2555 '(%d:%s also has origin %d:%s)\n')
2556 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2556 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2557 revs.remove(ids[n])
2557 revs.remove(ids[n])
2558 elif ctx.hex() in ids:
2558 elif ctx.hex() in ids:
2559 r = ids[ctx.hex()]
2559 r = ids[ctx.hex()]
2560 if r in revs:
2560 if r in revs:
2561 ui.warn(_('skipping already grafted revision %d:%s '
2561 ui.warn(_('skipping already grafted revision %d:%s '
2562 '(was grafted from %d:%s)\n') %
2562 '(was grafted from %d:%s)\n') %
2563 (r, repo[r], rev, ctx))
2563 (r, repo[r], rev, ctx))
2564 revs.remove(r)
2564 revs.remove(r)
2565 if not revs:
2565 if not revs:
2566 return -1
2566 return -1
2567
2567
2568 if opts.get('no_commit'):
2568 if opts.get('no_commit'):
2569 statedata['no_commit'] = True
2569 statedata['no_commit'] = True
2570 for pos, ctx in enumerate(repo.set("%ld", revs)):
2570 for pos, ctx in enumerate(repo.set("%ld", revs)):
2571 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2571 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2572 ctx.description().split('\n', 1)[0])
2572 ctx.description().split('\n', 1)[0])
2573 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2573 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2574 if names:
2574 if names:
2575 desc += ' (%s)' % ' '.join(names)
2575 desc += ' (%s)' % ' '.join(names)
2576 ui.status(_('grafting %s\n') % desc)
2576 ui.status(_('grafting %s\n') % desc)
2577 if opts.get('dry_run'):
2577 if opts.get('dry_run'):
2578 continue
2578 continue
2579
2579
2580 source = ctx.extra().get('source')
2580 source = ctx.extra().get('source')
2581 extra = {}
2581 extra = {}
2582 if source:
2582 if source:
2583 extra['source'] = source
2583 extra['source'] = source
2584 extra['intermediate-source'] = ctx.hex()
2584 extra['intermediate-source'] = ctx.hex()
2585 else:
2585 else:
2586 extra['source'] = ctx.hex()
2586 extra['source'] = ctx.hex()
2587 user = ctx.user()
2587 user = ctx.user()
2588 if opts.get('user'):
2588 if opts.get('user'):
2589 user = opts['user']
2589 user = opts['user']
2590 statedata['user'] = user
2590 statedata['user'] = user
2591 date = ctx.date()
2591 date = ctx.date()
2592 if opts.get('date'):
2592 if opts.get('date'):
2593 date = opts['date']
2593 date = opts['date']
2594 statedata['date'] = date
2594 statedata['date'] = date
2595 message = ctx.description()
2595 message = ctx.description()
2596 if opts.get('log'):
2596 if opts.get('log'):
2597 message += '\n(grafted from %s)' % ctx.hex()
2597 message += '\n(grafted from %s)' % ctx.hex()
2598 statedata['log'] = True
2598 statedata['log'] = True
2599
2599
2600 # we don't merge the first commit when continuing
2600 # we don't merge the first commit when continuing
2601 if not cont:
2601 if not cont:
2602 # perform the graft merge with p1(rev) as 'ancestor'
2602 # perform the graft merge with p1(rev) as 'ancestor'
2603 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2603 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2604 base = ctx.p1() if basectx is None else basectx
2604 base = ctx.p1() if basectx is None else basectx
2605 with ui.configoverride(overrides, 'graft'):
2605 with ui.configoverride(overrides, 'graft'):
2606 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2606 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2607 # report any conflicts
2607 # report any conflicts
2608 if stats.unresolvedcount > 0:
2608 if stats.unresolvedcount > 0:
2609 # write out state for --continue
2609 # write out state for --continue
2610 nodes = [repo[rev].hex() for rev in revs[pos:]]
2610 nodes = [repo[rev].hex() for rev in revs[pos:]]
2611 statedata['nodes'] = nodes
2611 statedata['nodes'] = nodes
2612 stateversion = 1
2612 stateversion = 1
2613 graftstate.save(stateversion, statedata)
2613 graftstate.save(stateversion, statedata)
2614 hint = _("use 'hg resolve' and 'hg graft --continue'")
2614 hint = _("use 'hg resolve' and 'hg graft --continue'")
2615 raise error.Abort(
2615 raise error.Abort(
2616 _("unresolved conflicts, can't continue"),
2616 _("unresolved conflicts, can't continue"),
2617 hint=hint)
2617 hint=hint)
2618 else:
2618 else:
2619 cont = False
2619 cont = False
2620
2620
2621 # commit if --no-commit is false
2621 # commit if --no-commit is false
2622 if not opts.get('no_commit'):
2622 if not opts.get('no_commit'):
2623 node = repo.commit(text=message, user=user, date=date, extra=extra,
2623 node = repo.commit(text=message, user=user, date=date, extra=extra,
2624 editor=editor)
2624 editor=editor)
2625 if node is None:
2625 if node is None:
2626 ui.warn(
2626 ui.warn(
2627 _('note: graft of %d:%s created no changes to commit\n') %
2627 _('note: graft of %d:%s created no changes to commit\n') %
2628 (ctx.rev(), ctx))
2628 (ctx.rev(), ctx))
2629 # checking that newnodes exist because old state files won't have it
2629 # checking that newnodes exist because old state files won't have it
2630 elif statedata.get('newnodes') is not None:
2630 elif statedata.get('newnodes') is not None:
2631 statedata['newnodes'].append(node)
2631 statedata['newnodes'].append(node)
2632
2632
2633 # remove state when we complete successfully
2633 # remove state when we complete successfully
2634 if not opts.get('dry_run'):
2634 if not opts.get('dry_run'):
2635 graftstate.delete()
2635 graftstate.delete()
2636
2636
2637 return 0
2637 return 0
2638
2638
2639 def _abortgraft(ui, repo, graftstate):
2639 def _abortgraft(ui, repo, graftstate):
2640 """abort the interrupted graft and rollbacks to the state before interrupted
2640 """abort the interrupted graft and rollbacks to the state before interrupted
2641 graft"""
2641 graft"""
2642 if not graftstate.exists():
2642 if not graftstate.exists():
2643 raise error.Abort(_("no interrupted graft to abort"))
2643 raise error.Abort(_("no interrupted graft to abort"))
2644 statedata = _readgraftstate(repo, graftstate)
2644 statedata = _readgraftstate(repo, graftstate)
2645 newnodes = statedata.get('newnodes')
2645 newnodes = statedata.get('newnodes')
2646 if newnodes is None:
2646 if newnodes is None:
2647 # and old graft state which does not have all the data required to abort
2647 # and old graft state which does not have all the data required to abort
2648 # the graft
2648 # the graft
2649 raise error.Abort(_("cannot abort using an old graftstate"))
2649 raise error.Abort(_("cannot abort using an old graftstate"))
2650
2650
2651 # changeset from which graft operation was started
2651 # changeset from which graft operation was started
2652 if len(newnodes) > 0:
2652 if len(newnodes) > 0:
2653 startctx = repo[newnodes[0]].p1()
2653 startctx = repo[newnodes[0]].p1()
2654 else:
2654 else:
2655 startctx = repo['.']
2655 startctx = repo['.']
2656 # whether to strip or not
2656 # whether to strip or not
2657 cleanup = False
2657 cleanup = False
2658 if newnodes:
2658 if newnodes:
2659 newnodes = [repo[r].rev() for r in newnodes]
2659 newnodes = [repo[r].rev() for r in newnodes]
2660 cleanup = True
2660 cleanup = True
2661 # checking that none of the newnodes turned public or is public
2661 # checking that none of the newnodes turned public or is public
2662 immutable = [c for c in newnodes if not repo[c].mutable()]
2662 immutable = [c for c in newnodes if not repo[c].mutable()]
2663 if immutable:
2663 if immutable:
2664 repo.ui.warn(_("cannot clean up public changesets %s\n")
2664 repo.ui.warn(_("cannot clean up public changesets %s\n")
2665 % ', '.join(bytes(repo[r]) for r in immutable),
2665 % ', '.join(bytes(repo[r]) for r in immutable),
2666 hint=_("see 'hg help phases' for details"))
2666 hint=_("see 'hg help phases' for details"))
2667 cleanup = False
2667 cleanup = False
2668
2668
2669 # checking that no new nodes are created on top of grafted revs
2669 # checking that no new nodes are created on top of grafted revs
2670 desc = set(repo.changelog.descendants(newnodes))
2670 desc = set(repo.changelog.descendants(newnodes))
2671 if desc - set(newnodes):
2671 if desc - set(newnodes):
2672 repo.ui.warn(_("new changesets detected on destination "
2672 repo.ui.warn(_("new changesets detected on destination "
2673 "branch, can't strip\n"))
2673 "branch, can't strip\n"))
2674 cleanup = False
2674 cleanup = False
2675
2675
2676 if cleanup:
2676 if cleanup:
2677 with repo.wlock(), repo.lock():
2677 with repo.wlock(), repo.lock():
2678 hg.updaterepo(repo, startctx.node(), overwrite=True)
2678 hg.updaterepo(repo, startctx.node(), overwrite=True)
2679 # stripping the new nodes created
2679 # stripping the new nodes created
2680 strippoints = [c.node() for c in repo.set("roots(%ld)",
2680 strippoints = [c.node() for c in repo.set("roots(%ld)",
2681 newnodes)]
2681 newnodes)]
2682 repair.strip(repo.ui, repo, strippoints, backup=False)
2682 repair.strip(repo.ui, repo, strippoints, backup=False)
2683
2683
2684 if not cleanup:
2684 if not cleanup:
2685 # we don't update to the startnode if we can't strip
2685 # we don't update to the startnode if we can't strip
2686 startctx = repo['.']
2686 startctx = repo['.']
2687 hg.updaterepo(repo, startctx.node(), overwrite=True)
2687 hg.updaterepo(repo, startctx.node(), overwrite=True)
2688
2688
2689 ui.status(_("graft aborted\n"))
2689 ui.status(_("graft aborted\n"))
2690 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2690 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2691 graftstate.delete()
2691 graftstate.delete()
2692 return 0
2692 return 0
2693
2693
2694 def _readgraftstate(repo, graftstate):
2694 def _readgraftstate(repo, graftstate):
2695 """read the graft state file and return a dict of the data stored in it"""
2695 """read the graft state file and return a dict of the data stored in it"""
2696 try:
2696 try:
2697 return graftstate.read()
2697 return graftstate.read()
2698 except error.CorruptedState:
2698 except error.CorruptedState:
2699 nodes = repo.vfs.read('graftstate').splitlines()
2699 nodes = repo.vfs.read('graftstate').splitlines()
2700 return {'nodes': nodes}
2700 return {'nodes': nodes}
2701
2701
2702 def _stopgraft(ui, repo, graftstate):
2702 def _stopgraft(ui, repo, graftstate):
2703 """stop the interrupted graft"""
2703 """stop the interrupted graft"""
2704 if not graftstate.exists():
2704 if not graftstate.exists():
2705 raise error.Abort(_("no interrupted graft found"))
2705 raise error.Abort(_("no interrupted graft found"))
2706 pctx = repo['.']
2706 pctx = repo['.']
2707 hg.updaterepo(repo, pctx.node(), overwrite=True)
2707 hg.updaterepo(repo, pctx.node(), overwrite=True)
2708 graftstate.delete()
2708 graftstate.delete()
2709 ui.status(_("stopped the interrupted graft\n"))
2709 ui.status(_("stopped the interrupted graft\n"))
2710 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2710 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2711 return 0
2711 return 0
2712
2712
2713 @command('grep',
2713 @command('grep',
2714 [('0', 'print0', None, _('end fields with NUL')),
2714 [('0', 'print0', None, _('end fields with NUL')),
2715 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2715 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2716 ('', 'diff', None, _('print all revisions when the term was introduced '
2716 ('', 'diff', None, _('print all revisions when the term was introduced '
2717 'or removed')),
2717 'or removed')),
2718 ('a', 'text', None, _('treat all files as text')),
2718 ('a', 'text', None, _('treat all files as text')),
2719 ('f', 'follow', None,
2719 ('f', 'follow', None,
2720 _('follow changeset history,'
2720 _('follow changeset history,'
2721 ' or file history across copies and renames')),
2721 ' or file history across copies and renames')),
2722 ('i', 'ignore-case', None, _('ignore case when matching')),
2722 ('i', 'ignore-case', None, _('ignore case when matching')),
2723 ('l', 'files-with-matches', None,
2723 ('l', 'files-with-matches', None,
2724 _('print only filenames and revisions that match')),
2724 _('print only filenames and revisions that match')),
2725 ('n', 'line-number', None, _('print matching line numbers')),
2725 ('n', 'line-number', None, _('print matching line numbers')),
2726 ('r', 'rev', [],
2726 ('r', 'rev', [],
2727 _('only search files changed within revision range'), _('REV')),
2727 _('only search files changed within revision range'), _('REV')),
2728 ('', 'all-files', None,
2728 ('', 'all-files', None,
2729 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2729 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2730 ('u', 'user', None, _('list the author (long with -v)')),
2730 ('u', 'user', None, _('list the author (long with -v)')),
2731 ('d', 'date', None, _('list the date (short with -q)')),
2731 ('d', 'date', None, _('list the date (short with -q)')),
2732 ] + formatteropts + walkopts,
2732 ] + formatteropts + walkopts,
2733 _('[OPTION]... PATTERN [FILE]...'),
2733 _('[OPTION]... PATTERN [FILE]...'),
2734 helpcategory=command.CATEGORY_FILE_CONTENTS,
2734 helpcategory=command.CATEGORY_FILE_CONTENTS,
2735 inferrepo=True,
2735 inferrepo=True,
2736 intents={INTENT_READONLY})
2736 intents={INTENT_READONLY})
2737 def grep(ui, repo, pattern, *pats, **opts):
2737 def grep(ui, repo, pattern, *pats, **opts):
2738 """search revision history for a pattern in specified files
2738 """search revision history for a pattern in specified files
2739
2739
2740 Search revision history for a regular expression in the specified
2740 Search revision history for a regular expression in the specified
2741 files or the entire project.
2741 files or the entire project.
2742
2742
2743 By default, grep prints the most recent revision number for each
2743 By default, grep prints the most recent revision number for each
2744 file in which it finds a match. To get it to print every revision
2744 file in which it finds a match. To get it to print every revision
2745 that contains a change in match status ("-" for a match that becomes
2745 that contains a change in match status ("-" for a match that becomes
2746 a non-match, or "+" for a non-match that becomes a match), use the
2746 a non-match, or "+" for a non-match that becomes a match), use the
2747 --diff flag.
2747 --diff flag.
2748
2748
2749 PATTERN can be any Python (roughly Perl-compatible) regular
2749 PATTERN can be any Python (roughly Perl-compatible) regular
2750 expression.
2750 expression.
2751
2751
2752 If no FILEs are specified (and -f/--follow isn't set), all files in
2752 If no FILEs are specified (and -f/--follow isn't set), all files in
2753 the repository are searched, including those that don't exist in the
2753 the repository are searched, including those that don't exist in the
2754 current branch or have been deleted in a prior changeset.
2754 current branch or have been deleted in a prior changeset.
2755
2755
2756 .. container:: verbose
2756 .. container:: verbose
2757
2757
2758 Template:
2758 Template:
2759
2759
2760 The following keywords are supported in addition to the common template
2760 The following keywords are supported in addition to the common template
2761 keywords and functions. See also :hg:`help templates`.
2761 keywords and functions. See also :hg:`help templates`.
2762
2762
2763 :change: String. Character denoting insertion ``+`` or removal ``-``.
2763 :change: String. Character denoting insertion ``+`` or removal ``-``.
2764 Available if ``--diff`` is specified.
2764 Available if ``--diff`` is specified.
2765 :lineno: Integer. Line number of the match.
2765 :lineno: Integer. Line number of the match.
2766 :path: String. Repository-absolute path of the file.
2766 :path: String. Repository-absolute path of the file.
2767 :texts: List of text chunks.
2767 :texts: List of text chunks.
2768
2768
2769 And each entry of ``{texts}`` provides the following sub-keywords.
2769 And each entry of ``{texts}`` provides the following sub-keywords.
2770
2770
2771 :matched: Boolean. True if the chunk matches the specified pattern.
2771 :matched: Boolean. True if the chunk matches the specified pattern.
2772 :text: String. Chunk content.
2772 :text: String. Chunk content.
2773
2773
2774 See :hg:`help templates.operators` for the list expansion syntax.
2774 See :hg:`help templates.operators` for the list expansion syntax.
2775
2775
2776 Returns 0 if a match is found, 1 otherwise.
2776 Returns 0 if a match is found, 1 otherwise.
2777 """
2777 """
2778 opts = pycompat.byteskwargs(opts)
2778 opts = pycompat.byteskwargs(opts)
2779 diff = opts.get('all') or opts.get('diff')
2779 diff = opts.get('all') or opts.get('diff')
2780 all_files = opts.get('all_files')
2780 all_files = opts.get('all_files')
2781 if diff and opts.get('all_files'):
2781 if diff and opts.get('all_files'):
2782 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2782 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2783 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2783 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2784 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2784 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2785 # experimental config: commands.grep.all-files
2785 # experimental config: commands.grep.all-files
2786 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2786 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2787 plaingrep = opts.get('all_files') and not opts.get('rev')
2787 plaingrep = opts.get('all_files') and not opts.get('rev')
2788 if plaingrep:
2788 if plaingrep:
2789 opts['rev'] = ['wdir()']
2789 opts['rev'] = ['wdir()']
2790
2790
2791 reflags = re.M
2791 reflags = re.M
2792 if opts.get('ignore_case'):
2792 if opts.get('ignore_case'):
2793 reflags |= re.I
2793 reflags |= re.I
2794 try:
2794 try:
2795 regexp = util.re.compile(pattern, reflags)
2795 regexp = util.re.compile(pattern, reflags)
2796 except re.error as inst:
2796 except re.error as inst:
2797 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2797 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2798 return 1
2798 return 1
2799 sep, eol = ':', '\n'
2799 sep, eol = ':', '\n'
2800 if opts.get('print0'):
2800 if opts.get('print0'):
2801 sep = eol = '\0'
2801 sep = eol = '\0'
2802
2802
2803 getfile = util.lrucachefunc(repo.file)
2803 getfile = util.lrucachefunc(repo.file)
2804
2804
2805 def matchlines(body):
2805 def matchlines(body):
2806 begin = 0
2806 begin = 0
2807 linenum = 0
2807 linenum = 0
2808 while begin < len(body):
2808 while begin < len(body):
2809 match = regexp.search(body, begin)
2809 match = regexp.search(body, begin)
2810 if not match:
2810 if not match:
2811 break
2811 break
2812 mstart, mend = match.span()
2812 mstart, mend = match.span()
2813 linenum += body.count('\n', begin, mstart) + 1
2813 linenum += body.count('\n', begin, mstart) + 1
2814 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2814 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2815 begin = body.find('\n', mend) + 1 or len(body) + 1
2815 begin = body.find('\n', mend) + 1 or len(body) + 1
2816 lend = begin - 1
2816 lend = begin - 1
2817 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2817 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2818
2818
2819 class linestate(object):
2819 class linestate(object):
2820 def __init__(self, line, linenum, colstart, colend):
2820 def __init__(self, line, linenum, colstart, colend):
2821 self.line = line
2821 self.line = line
2822 self.linenum = linenum
2822 self.linenum = linenum
2823 self.colstart = colstart
2823 self.colstart = colstart
2824 self.colend = colend
2824 self.colend = colend
2825
2825
2826 def __hash__(self):
2826 def __hash__(self):
2827 return hash((self.linenum, self.line))
2827 return hash((self.linenum, self.line))
2828
2828
2829 def __eq__(self, other):
2829 def __eq__(self, other):
2830 return self.line == other.line
2830 return self.line == other.line
2831
2831
2832 def findpos(self):
2832 def findpos(self):
2833 """Iterate all (start, end) indices of matches"""
2833 """Iterate all (start, end) indices of matches"""
2834 yield self.colstart, self.colend
2834 yield self.colstart, self.colend
2835 p = self.colend
2835 p = self.colend
2836 while p < len(self.line):
2836 while p < len(self.line):
2837 m = regexp.search(self.line, p)
2837 m = regexp.search(self.line, p)
2838 if not m:
2838 if not m:
2839 break
2839 break
2840 yield m.span()
2840 yield m.span()
2841 p = m.end()
2841 p = m.end()
2842
2842
2843 matches = {}
2843 matches = {}
2844 copies = {}
2844 copies = {}
2845 def grepbody(fn, rev, body):
2845 def grepbody(fn, rev, body):
2846 matches[rev].setdefault(fn, [])
2846 matches[rev].setdefault(fn, [])
2847 m = matches[rev][fn]
2847 m = matches[rev][fn]
2848 for lnum, cstart, cend, line in matchlines(body):
2848 for lnum, cstart, cend, line in matchlines(body):
2849 s = linestate(line, lnum, cstart, cend)
2849 s = linestate(line, lnum, cstart, cend)
2850 m.append(s)
2850 m.append(s)
2851
2851
2852 def difflinestates(a, b):
2852 def difflinestates(a, b):
2853 sm = difflib.SequenceMatcher(None, a, b)
2853 sm = difflib.SequenceMatcher(None, a, b)
2854 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2854 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2855 if tag == r'insert':
2855 if tag == r'insert':
2856 for i in pycompat.xrange(blo, bhi):
2856 for i in pycompat.xrange(blo, bhi):
2857 yield ('+', b[i])
2857 yield ('+', b[i])
2858 elif tag == r'delete':
2858 elif tag == r'delete':
2859 for i in pycompat.xrange(alo, ahi):
2859 for i in pycompat.xrange(alo, ahi):
2860 yield ('-', a[i])
2860 yield ('-', a[i])
2861 elif tag == r'replace':
2861 elif tag == r'replace':
2862 for i in pycompat.xrange(alo, ahi):
2862 for i in pycompat.xrange(alo, ahi):
2863 yield ('-', a[i])
2863 yield ('-', a[i])
2864 for i in pycompat.xrange(blo, bhi):
2864 for i in pycompat.xrange(blo, bhi):
2865 yield ('+', b[i])
2865 yield ('+', b[i])
2866
2866
2867 uipathfn = scmutil.getuipathfn(repo)
2867 uipathfn = scmutil.getuipathfn(repo)
2868 def display(fm, fn, ctx, pstates, states):
2868 def display(fm, fn, ctx, pstates, states):
2869 rev = scmutil.intrev(ctx)
2869 rev = scmutil.intrev(ctx)
2870 if fm.isplain():
2870 if fm.isplain():
2871 formatuser = ui.shortuser
2871 formatuser = ui.shortuser
2872 else:
2872 else:
2873 formatuser = pycompat.bytestr
2873 formatuser = pycompat.bytestr
2874 if ui.quiet:
2874 if ui.quiet:
2875 datefmt = '%Y-%m-%d'
2875 datefmt = '%Y-%m-%d'
2876 else:
2876 else:
2877 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2877 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2878 found = False
2878 found = False
2879 @util.cachefunc
2879 @util.cachefunc
2880 def binary():
2880 def binary():
2881 flog = getfile(fn)
2881 flog = getfile(fn)
2882 try:
2882 try:
2883 return stringutil.binary(flog.read(ctx.filenode(fn)))
2883 return stringutil.binary(flog.read(ctx.filenode(fn)))
2884 except error.WdirUnsupported:
2884 except error.WdirUnsupported:
2885 return ctx[fn].isbinary()
2885 return ctx[fn].isbinary()
2886
2886
2887 fieldnamemap = {'linenumber': 'lineno'}
2887 fieldnamemap = {'linenumber': 'lineno'}
2888 if diff:
2888 if diff:
2889 iter = difflinestates(pstates, states)
2889 iter = difflinestates(pstates, states)
2890 else:
2890 else:
2891 iter = [('', l) for l in states]
2891 iter = [('', l) for l in states]
2892 for change, l in iter:
2892 for change, l in iter:
2893 fm.startitem()
2893 fm.startitem()
2894 fm.context(ctx=ctx)
2894 fm.context(ctx=ctx)
2895 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2895 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2896 fm.plain(uipathfn(fn), label='grep.filename')
2896 fm.plain(uipathfn(fn), label='grep.filename')
2897
2897
2898 cols = [
2898 cols = [
2899 ('rev', '%d', rev, not plaingrep, ''),
2899 ('rev', '%d', rev, not plaingrep, ''),
2900 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2900 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2901 ]
2901 ]
2902 if diff:
2902 if diff:
2903 cols.append(
2903 cols.append(
2904 ('change', '%s', change, True,
2904 ('change', '%s', change, True,
2905 'grep.inserted ' if change == '+' else 'grep.deleted ')
2905 'grep.inserted ' if change == '+' else 'grep.deleted ')
2906 )
2906 )
2907 cols.extend([
2907 cols.extend([
2908 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2908 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2909 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2909 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2910 opts.get('date'), ''),
2910 opts.get('date'), ''),
2911 ])
2911 ])
2912 for name, fmt, data, cond, extra_label in cols:
2912 for name, fmt, data, cond, extra_label in cols:
2913 if cond:
2913 if cond:
2914 fm.plain(sep, label='grep.sep')
2914 fm.plain(sep, label='grep.sep')
2915 field = fieldnamemap.get(name, name)
2915 field = fieldnamemap.get(name, name)
2916 label = extra_label + ('grep.%s' % name)
2916 label = extra_label + ('grep.%s' % name)
2917 fm.condwrite(cond, field, fmt, data, label=label)
2917 fm.condwrite(cond, field, fmt, data, label=label)
2918 if not opts.get('files_with_matches'):
2918 if not opts.get('files_with_matches'):
2919 fm.plain(sep, label='grep.sep')
2919 fm.plain(sep, label='grep.sep')
2920 if not opts.get('text') and binary():
2920 if not opts.get('text') and binary():
2921 fm.plain(_(" Binary file matches"))
2921 fm.plain(_(" Binary file matches"))
2922 else:
2922 else:
2923 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2923 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2924 fm.plain(eol)
2924 fm.plain(eol)
2925 found = True
2925 found = True
2926 if opts.get('files_with_matches'):
2926 if opts.get('files_with_matches'):
2927 break
2927 break
2928 return found
2928 return found
2929
2929
2930 def displaymatches(fm, l):
2930 def displaymatches(fm, l):
2931 p = 0
2931 p = 0
2932 for s, e in l.findpos():
2932 for s, e in l.findpos():
2933 if p < s:
2933 if p < s:
2934 fm.startitem()
2934 fm.startitem()
2935 fm.write('text', '%s', l.line[p:s])
2935 fm.write('text', '%s', l.line[p:s])
2936 fm.data(matched=False)
2936 fm.data(matched=False)
2937 fm.startitem()
2937 fm.startitem()
2938 fm.write('text', '%s', l.line[s:e], label='grep.match')
2938 fm.write('text', '%s', l.line[s:e], label='grep.match')
2939 fm.data(matched=True)
2939 fm.data(matched=True)
2940 p = e
2940 p = e
2941 if p < len(l.line):
2941 if p < len(l.line):
2942 fm.startitem()
2942 fm.startitem()
2943 fm.write('text', '%s', l.line[p:])
2943 fm.write('text', '%s', l.line[p:])
2944 fm.data(matched=False)
2944 fm.data(matched=False)
2945 fm.end()
2945 fm.end()
2946
2946
2947 skip = set()
2947 skip = set()
2948 revfiles = {}
2948 revfiles = {}
2949 match = scmutil.match(repo[None], pats, opts)
2949 match = scmutil.match(repo[None], pats, opts)
2950 found = False
2950 found = False
2951 follow = opts.get('follow')
2951 follow = opts.get('follow')
2952
2952
2953 getrenamed = scmutil.getrenamedfn(repo)
2953 getrenamed = scmutil.getrenamedfn(repo)
2954 def prep(ctx, fns):
2954 def prep(ctx, fns):
2955 rev = ctx.rev()
2955 rev = ctx.rev()
2956 pctx = ctx.p1()
2956 pctx = ctx.p1()
2957 parent = pctx.rev()
2957 parent = pctx.rev()
2958 matches.setdefault(rev, {})
2958 matches.setdefault(rev, {})
2959 matches.setdefault(parent, {})
2959 matches.setdefault(parent, {})
2960 files = revfiles.setdefault(rev, [])
2960 files = revfiles.setdefault(rev, [])
2961 for fn in fns:
2961 for fn in fns:
2962 flog = getfile(fn)
2962 flog = getfile(fn)
2963 try:
2963 try:
2964 fnode = ctx.filenode(fn)
2964 fnode = ctx.filenode(fn)
2965 except error.LookupError:
2965 except error.LookupError:
2966 continue
2966 continue
2967
2967
2968 copy = None
2968 copy = None
2969 if follow:
2969 if follow:
2970 copy = getrenamed(fn, rev)
2970 copy = getrenamed(fn, rev)
2971 if copy:
2971 if copy:
2972 copies.setdefault(rev, {})[fn] = copy
2972 copies.setdefault(rev, {})[fn] = copy
2973 if fn in skip:
2973 if fn in skip:
2974 skip.add(copy)
2974 skip.add(copy)
2975 if fn in skip:
2975 if fn in skip:
2976 continue
2976 continue
2977 files.append(fn)
2977 files.append(fn)
2978
2978
2979 if fn not in matches[rev]:
2979 if fn not in matches[rev]:
2980 try:
2980 try:
2981 content = flog.read(fnode)
2981 content = flog.read(fnode)
2982 except error.WdirUnsupported:
2982 except error.WdirUnsupported:
2983 content = ctx[fn].data()
2983 content = ctx[fn].data()
2984 grepbody(fn, rev, content)
2984 grepbody(fn, rev, content)
2985
2985
2986 pfn = copy or fn
2986 pfn = copy or fn
2987 if pfn not in matches[parent]:
2987 if pfn not in matches[parent]:
2988 try:
2988 try:
2989 fnode = pctx.filenode(pfn)
2989 fnode = pctx.filenode(pfn)
2990 grepbody(pfn, parent, flog.read(fnode))
2990 grepbody(pfn, parent, flog.read(fnode))
2991 except error.LookupError:
2991 except error.LookupError:
2992 pass
2992 pass
2993
2993
2994 ui.pager('grep')
2994 ui.pager('grep')
2995 fm = ui.formatter('grep', opts)
2995 fm = ui.formatter('grep', opts)
2996 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2996 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2997 rev = ctx.rev()
2997 rev = ctx.rev()
2998 parent = ctx.p1().rev()
2998 parent = ctx.p1().rev()
2999 for fn in sorted(revfiles.get(rev, [])):
2999 for fn in sorted(revfiles.get(rev, [])):
3000 states = matches[rev][fn]
3000 states = matches[rev][fn]
3001 copy = copies.get(rev, {}).get(fn)
3001 copy = copies.get(rev, {}).get(fn)
3002 if fn in skip:
3002 if fn in skip:
3003 if copy:
3003 if copy:
3004 skip.add(copy)
3004 skip.add(copy)
3005 continue
3005 continue
3006 pstates = matches.get(parent, {}).get(copy or fn, [])
3006 pstates = matches.get(parent, {}).get(copy or fn, [])
3007 if pstates or states:
3007 if pstates or states:
3008 r = display(fm, fn, ctx, pstates, states)
3008 r = display(fm, fn, ctx, pstates, states)
3009 found = found or r
3009 found = found or r
3010 if r and not diff and not all_files:
3010 if r and not diff and not all_files:
3011 skip.add(fn)
3011 skip.add(fn)
3012 if copy:
3012 if copy:
3013 skip.add(copy)
3013 skip.add(copy)
3014 del revfiles[rev]
3014 del revfiles[rev]
3015 # We will keep the matches dict for the duration of the window
3015 # We will keep the matches dict for the duration of the window
3016 # clear the matches dict once the window is over
3016 # clear the matches dict once the window is over
3017 if not revfiles:
3017 if not revfiles:
3018 matches.clear()
3018 matches.clear()
3019 fm.end()
3019 fm.end()
3020
3020
3021 return not found
3021 return not found
3022
3022
3023 @command('heads',
3023 @command('heads',
3024 [('r', 'rev', '',
3024 [('r', 'rev', '',
3025 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3025 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3026 ('t', 'topo', False, _('show topological heads only')),
3026 ('t', 'topo', False, _('show topological heads only')),
3027 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3027 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3028 ('c', 'closed', False, _('show normal and closed branch heads')),
3028 ('c', 'closed', False, _('show normal and closed branch heads')),
3029 ] + templateopts,
3029 ] + templateopts,
3030 _('[-ct] [-r STARTREV] [REV]...'),
3030 _('[-ct] [-r STARTREV] [REV]...'),
3031 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3031 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3032 intents={INTENT_READONLY})
3032 intents={INTENT_READONLY})
3033 def heads(ui, repo, *branchrevs, **opts):
3033 def heads(ui, repo, *branchrevs, **opts):
3034 """show branch heads
3034 """show branch heads
3035
3035
3036 With no arguments, show all open branch heads in the repository.
3036 With no arguments, show all open branch heads in the repository.
3037 Branch heads are changesets that have no descendants on the
3037 Branch heads are changesets that have no descendants on the
3038 same branch. They are where development generally takes place and
3038 same branch. They are where development generally takes place and
3039 are the usual targets for update and merge operations.
3039 are the usual targets for update and merge operations.
3040
3040
3041 If one or more REVs are given, only open branch heads on the
3041 If one or more REVs are given, only open branch heads on the
3042 branches associated with the specified changesets are shown. This
3042 branches associated with the specified changesets are shown. This
3043 means that you can use :hg:`heads .` to see the heads on the
3043 means that you can use :hg:`heads .` to see the heads on the
3044 currently checked-out branch.
3044 currently checked-out branch.
3045
3045
3046 If -c/--closed is specified, also show branch heads marked closed
3046 If -c/--closed is specified, also show branch heads marked closed
3047 (see :hg:`commit --close-branch`).
3047 (see :hg:`commit --close-branch`).
3048
3048
3049 If STARTREV is specified, only those heads that are descendants of
3049 If STARTREV is specified, only those heads that are descendants of
3050 STARTREV will be displayed.
3050 STARTREV will be displayed.
3051
3051
3052 If -t/--topo is specified, named branch mechanics will be ignored and only
3052 If -t/--topo is specified, named branch mechanics will be ignored and only
3053 topological heads (changesets with no children) will be shown.
3053 topological heads (changesets with no children) will be shown.
3054
3054
3055 Returns 0 if matching heads are found, 1 if not.
3055 Returns 0 if matching heads are found, 1 if not.
3056 """
3056 """
3057
3057
3058 opts = pycompat.byteskwargs(opts)
3058 opts = pycompat.byteskwargs(opts)
3059 start = None
3059 start = None
3060 rev = opts.get('rev')
3060 rev = opts.get('rev')
3061 if rev:
3061 if rev:
3062 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3062 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3063 start = scmutil.revsingle(repo, rev, None).node()
3063 start = scmutil.revsingle(repo, rev, None).node()
3064
3064
3065 if opts.get('topo'):
3065 if opts.get('topo'):
3066 heads = [repo[h] for h in repo.heads(start)]
3066 heads = [repo[h] for h in repo.heads(start)]
3067 else:
3067 else:
3068 heads = []
3068 heads = []
3069 for branch in repo.branchmap():
3069 for branch in repo.branchmap():
3070 heads += repo.branchheads(branch, start, opts.get('closed'))
3070 heads += repo.branchheads(branch, start, opts.get('closed'))
3071 heads = [repo[h] for h in heads]
3071 heads = [repo[h] for h in heads]
3072
3072
3073 if branchrevs:
3073 if branchrevs:
3074 branches = set(repo[r].branch()
3074 branches = set(repo[r].branch()
3075 for r in scmutil.revrange(repo, branchrevs))
3075 for r in scmutil.revrange(repo, branchrevs))
3076 heads = [h for h in heads if h.branch() in branches]
3076 heads = [h for h in heads if h.branch() in branches]
3077
3077
3078 if opts.get('active') and branchrevs:
3078 if opts.get('active') and branchrevs:
3079 dagheads = repo.heads(start)
3079 dagheads = repo.heads(start)
3080 heads = [h for h in heads if h.node() in dagheads]
3080 heads = [h for h in heads if h.node() in dagheads]
3081
3081
3082 if branchrevs:
3082 if branchrevs:
3083 haveheads = set(h.branch() for h in heads)
3083 haveheads = set(h.branch() for h in heads)
3084 if branches - haveheads:
3084 if branches - haveheads:
3085 headless = ', '.join(b for b in branches - haveheads)
3085 headless = ', '.join(b for b in branches - haveheads)
3086 msg = _('no open branch heads found on branches %s')
3086 msg = _('no open branch heads found on branches %s')
3087 if opts.get('rev'):
3087 if opts.get('rev'):
3088 msg += _(' (started at %s)') % opts['rev']
3088 msg += _(' (started at %s)') % opts['rev']
3089 ui.warn((msg + '\n') % headless)
3089 ui.warn((msg + '\n') % headless)
3090
3090
3091 if not heads:
3091 if not heads:
3092 return 1
3092 return 1
3093
3093
3094 ui.pager('heads')
3094 ui.pager('heads')
3095 heads = sorted(heads, key=lambda x: -x.rev())
3095 heads = sorted(heads, key=lambda x: -x.rev())
3096 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3096 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3097 for ctx in heads:
3097 for ctx in heads:
3098 displayer.show(ctx)
3098 displayer.show(ctx)
3099 displayer.close()
3099 displayer.close()
3100
3100
3101 @command('help',
3101 @command('help',
3102 [('e', 'extension', None, _('show only help for extensions')),
3102 [('e', 'extension', None, _('show only help for extensions')),
3103 ('c', 'command', None, _('show only help for commands')),
3103 ('c', 'command', None, _('show only help for commands')),
3104 ('k', 'keyword', None, _('show topics matching keyword')),
3104 ('k', 'keyword', None, _('show topics matching keyword')),
3105 ('s', 'system', [],
3105 ('s', 'system', [],
3106 _('show help for specific platform(s)'), _('PLATFORM')),
3106 _('show help for specific platform(s)'), _('PLATFORM')),
3107 ],
3107 ],
3108 _('[-eck] [-s PLATFORM] [TOPIC]'),
3108 _('[-eck] [-s PLATFORM] [TOPIC]'),
3109 helpcategory=command.CATEGORY_HELP,
3109 helpcategory=command.CATEGORY_HELP,
3110 norepo=True,
3110 norepo=True,
3111 intents={INTENT_READONLY})
3111 intents={INTENT_READONLY})
3112 def help_(ui, name=None, **opts):
3112 def help_(ui, name=None, **opts):
3113 """show help for a given topic or a help overview
3113 """show help for a given topic or a help overview
3114
3114
3115 With no arguments, print a list of commands with short help messages.
3115 With no arguments, print a list of commands with short help messages.
3116
3116
3117 Given a topic, extension, or command name, print help for that
3117 Given a topic, extension, or command name, print help for that
3118 topic.
3118 topic.
3119
3119
3120 Returns 0 if successful.
3120 Returns 0 if successful.
3121 """
3121 """
3122
3122
3123 keep = opts.get(r'system') or []
3123 keep = opts.get(r'system') or []
3124 if len(keep) == 0:
3124 if len(keep) == 0:
3125 if pycompat.sysplatform.startswith('win'):
3125 if pycompat.sysplatform.startswith('win'):
3126 keep.append('windows')
3126 keep.append('windows')
3127 elif pycompat.sysplatform == 'OpenVMS':
3127 elif pycompat.sysplatform == 'OpenVMS':
3128 keep.append('vms')
3128 keep.append('vms')
3129 elif pycompat.sysplatform == 'plan9':
3129 elif pycompat.sysplatform == 'plan9':
3130 keep.append('plan9')
3130 keep.append('plan9')
3131 else:
3131 else:
3132 keep.append('unix')
3132 keep.append('unix')
3133 keep.append(pycompat.sysplatform.lower())
3133 keep.append(pycompat.sysplatform.lower())
3134 if ui.verbose:
3134 if ui.verbose:
3135 keep.append('verbose')
3135 keep.append('verbose')
3136
3136
3137 commands = sys.modules[__name__]
3137 commands = sys.modules[__name__]
3138 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3138 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3139 ui.pager('help')
3139 ui.pager('help')
3140 ui.write(formatted)
3140 ui.write(formatted)
3141
3141
3142
3142
3143 @command('identify|id',
3143 @command('identify|id',
3144 [('r', 'rev', '',
3144 [('r', 'rev', '',
3145 _('identify the specified revision'), _('REV')),
3145 _('identify the specified revision'), _('REV')),
3146 ('n', 'num', None, _('show local revision number')),
3146 ('n', 'num', None, _('show local revision number')),
3147 ('i', 'id', None, _('show global revision id')),
3147 ('i', 'id', None, _('show global revision id')),
3148 ('b', 'branch', None, _('show branch')),
3148 ('b', 'branch', None, _('show branch')),
3149 ('t', 'tags', None, _('show tags')),
3149 ('t', 'tags', None, _('show tags')),
3150 ('B', 'bookmarks', None, _('show bookmarks')),
3150 ('B', 'bookmarks', None, _('show bookmarks')),
3151 ] + remoteopts + formatteropts,
3151 ] + remoteopts + formatteropts,
3152 _('[-nibtB] [-r REV] [SOURCE]'),
3152 _('[-nibtB] [-r REV] [SOURCE]'),
3153 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3153 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3154 optionalrepo=True,
3154 optionalrepo=True,
3155 intents={INTENT_READONLY})
3155 intents={INTENT_READONLY})
3156 def identify(ui, repo, source=None, rev=None,
3156 def identify(ui, repo, source=None, rev=None,
3157 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3157 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3158 """identify the working directory or specified revision
3158 """identify the working directory or specified revision
3159
3159
3160 Print a summary identifying the repository state at REV using one or
3160 Print a summary identifying the repository state at REV using one or
3161 two parent hash identifiers, followed by a "+" if the working
3161 two parent hash identifiers, followed by a "+" if the working
3162 directory has uncommitted changes, the branch name (if not default),
3162 directory has uncommitted changes, the branch name (if not default),
3163 a list of tags, and a list of bookmarks.
3163 a list of tags, and a list of bookmarks.
3164
3164
3165 When REV is not given, print a summary of the current state of the
3165 When REV is not given, print a summary of the current state of the
3166 repository including the working directory. Specify -r. to get information
3166 repository including the working directory. Specify -r. to get information
3167 of the working directory parent without scanning uncommitted changes.
3167 of the working directory parent without scanning uncommitted changes.
3168
3168
3169 Specifying a path to a repository root or Mercurial bundle will
3169 Specifying a path to a repository root or Mercurial bundle will
3170 cause lookup to operate on that repository/bundle.
3170 cause lookup to operate on that repository/bundle.
3171
3171
3172 .. container:: verbose
3172 .. container:: verbose
3173
3173
3174 Template:
3174 Template:
3175
3175
3176 The following keywords are supported in addition to the common template
3176 The following keywords are supported in addition to the common template
3177 keywords and functions. See also :hg:`help templates`.
3177 keywords and functions. See also :hg:`help templates`.
3178
3178
3179 :dirty: String. Character ``+`` denoting if the working directory has
3179 :dirty: String. Character ``+`` denoting if the working directory has
3180 uncommitted changes.
3180 uncommitted changes.
3181 :id: String. One or two nodes, optionally followed by ``+``.
3181 :id: String. One or two nodes, optionally followed by ``+``.
3182 :parents: List of strings. Parent nodes of the changeset.
3182 :parents: List of strings. Parent nodes of the changeset.
3183
3183
3184 Examples:
3184 Examples:
3185
3185
3186 - generate a build identifier for the working directory::
3186 - generate a build identifier for the working directory::
3187
3187
3188 hg id --id > build-id.dat
3188 hg id --id > build-id.dat
3189
3189
3190 - find the revision corresponding to a tag::
3190 - find the revision corresponding to a tag::
3191
3191
3192 hg id -n -r 1.3
3192 hg id -n -r 1.3
3193
3193
3194 - check the most recent revision of a remote repository::
3194 - check the most recent revision of a remote repository::
3195
3195
3196 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3196 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3197
3197
3198 See :hg:`log` for generating more information about specific revisions,
3198 See :hg:`log` for generating more information about specific revisions,
3199 including full hash identifiers.
3199 including full hash identifiers.
3200
3200
3201 Returns 0 if successful.
3201 Returns 0 if successful.
3202 """
3202 """
3203
3203
3204 opts = pycompat.byteskwargs(opts)
3204 opts = pycompat.byteskwargs(opts)
3205 if not repo and not source:
3205 if not repo and not source:
3206 raise error.Abort(_("there is no Mercurial repository here "
3206 raise error.Abort(_("there is no Mercurial repository here "
3207 "(.hg not found)"))
3207 "(.hg not found)"))
3208
3208
3209 default = not (num or id or branch or tags or bookmarks)
3209 default = not (num or id or branch or tags or bookmarks)
3210 output = []
3210 output = []
3211 revs = []
3211 revs = []
3212
3212
3213 if source:
3213 if source:
3214 source, branches = hg.parseurl(ui.expandpath(source))
3214 source, branches = hg.parseurl(ui.expandpath(source))
3215 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3215 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3216 repo = peer.local()
3216 repo = peer.local()
3217 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3217 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3218
3218
3219 fm = ui.formatter('identify', opts)
3219 fm = ui.formatter('identify', opts)
3220 fm.startitem()
3220 fm.startitem()
3221
3221
3222 if not repo:
3222 if not repo:
3223 if num or branch or tags:
3223 if num or branch or tags:
3224 raise error.Abort(
3224 raise error.Abort(
3225 _("can't query remote revision number, branch, or tags"))
3225 _("can't query remote revision number, branch, or tags"))
3226 if not rev and revs:
3226 if not rev and revs:
3227 rev = revs[0]
3227 rev = revs[0]
3228 if not rev:
3228 if not rev:
3229 rev = "tip"
3229 rev = "tip"
3230
3230
3231 remoterev = peer.lookup(rev)
3231 remoterev = peer.lookup(rev)
3232 hexrev = fm.hexfunc(remoterev)
3232 hexrev = fm.hexfunc(remoterev)
3233 if default or id:
3233 if default or id:
3234 output = [hexrev]
3234 output = [hexrev]
3235 fm.data(id=hexrev)
3235 fm.data(id=hexrev)
3236
3236
3237 @util.cachefunc
3237 @util.cachefunc
3238 def getbms():
3238 def getbms():
3239 bms = []
3239 bms = []
3240
3240
3241 if 'bookmarks' in peer.listkeys('namespaces'):
3241 if 'bookmarks' in peer.listkeys('namespaces'):
3242 hexremoterev = hex(remoterev)
3242 hexremoterev = hex(remoterev)
3243 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3243 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3244 if bmr == hexremoterev]
3244 if bmr == hexremoterev]
3245
3245
3246 return sorted(bms)
3246 return sorted(bms)
3247
3247
3248 if fm.isplain():
3248 if fm.isplain():
3249 if bookmarks:
3249 if bookmarks:
3250 output.extend(getbms())
3250 output.extend(getbms())
3251 elif default and not ui.quiet:
3251 elif default and not ui.quiet:
3252 # multiple bookmarks for a single parent separated by '/'
3252 # multiple bookmarks for a single parent separated by '/'
3253 bm = '/'.join(getbms())
3253 bm = '/'.join(getbms())
3254 if bm:
3254 if bm:
3255 output.append(bm)
3255 output.append(bm)
3256 else:
3256 else:
3257 fm.data(node=hex(remoterev))
3257 fm.data(node=hex(remoterev))
3258 if bookmarks or 'bookmarks' in fm.datahint():
3258 if bookmarks or 'bookmarks' in fm.datahint():
3259 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3259 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3260 else:
3260 else:
3261 if rev:
3261 if rev:
3262 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3262 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3263 ctx = scmutil.revsingle(repo, rev, None)
3263 ctx = scmutil.revsingle(repo, rev, None)
3264
3264
3265 if ctx.rev() is None:
3265 if ctx.rev() is None:
3266 ctx = repo[None]
3266 ctx = repo[None]
3267 parents = ctx.parents()
3267 parents = ctx.parents()
3268 taglist = []
3268 taglist = []
3269 for p in parents:
3269 for p in parents:
3270 taglist.extend(p.tags())
3270 taglist.extend(p.tags())
3271
3271
3272 dirty = ""
3272 dirty = ""
3273 if ctx.dirty(missing=True, merge=False, branch=False):
3273 if ctx.dirty(missing=True, merge=False, branch=False):
3274 dirty = '+'
3274 dirty = '+'
3275 fm.data(dirty=dirty)
3275 fm.data(dirty=dirty)
3276
3276
3277 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3277 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3278 if default or id:
3278 if default or id:
3279 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3279 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3280 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3280 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3281
3281
3282 if num:
3282 if num:
3283 numoutput = ["%d" % p.rev() for p in parents]
3283 numoutput = ["%d" % p.rev() for p in parents]
3284 output.append("%s%s" % ('+'.join(numoutput), dirty))
3284 output.append("%s%s" % ('+'.join(numoutput), dirty))
3285
3285
3286 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3286 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3287 for p in parents], name='node'))
3287 for p in parents], name='node'))
3288 else:
3288 else:
3289 hexoutput = fm.hexfunc(ctx.node())
3289 hexoutput = fm.hexfunc(ctx.node())
3290 if default or id:
3290 if default or id:
3291 output = [hexoutput]
3291 output = [hexoutput]
3292 fm.data(id=hexoutput)
3292 fm.data(id=hexoutput)
3293
3293
3294 if num:
3294 if num:
3295 output.append(pycompat.bytestr(ctx.rev()))
3295 output.append(pycompat.bytestr(ctx.rev()))
3296 taglist = ctx.tags()
3296 taglist = ctx.tags()
3297
3297
3298 if default and not ui.quiet:
3298 if default and not ui.quiet:
3299 b = ctx.branch()
3299 b = ctx.branch()
3300 if b != 'default':
3300 if b != 'default':
3301 output.append("(%s)" % b)
3301 output.append("(%s)" % b)
3302
3302
3303 # multiple tags for a single parent separated by '/'
3303 # multiple tags for a single parent separated by '/'
3304 t = '/'.join(taglist)
3304 t = '/'.join(taglist)
3305 if t:
3305 if t:
3306 output.append(t)
3306 output.append(t)
3307
3307
3308 # multiple bookmarks for a single parent separated by '/'
3308 # multiple bookmarks for a single parent separated by '/'
3309 bm = '/'.join(ctx.bookmarks())
3309 bm = '/'.join(ctx.bookmarks())
3310 if bm:
3310 if bm:
3311 output.append(bm)
3311 output.append(bm)
3312 else:
3312 else:
3313 if branch:
3313 if branch:
3314 output.append(ctx.branch())
3314 output.append(ctx.branch())
3315
3315
3316 if tags:
3316 if tags:
3317 output.extend(taglist)
3317 output.extend(taglist)
3318
3318
3319 if bookmarks:
3319 if bookmarks:
3320 output.extend(ctx.bookmarks())
3320 output.extend(ctx.bookmarks())
3321
3321
3322 fm.data(node=ctx.hex())
3322 fm.data(node=ctx.hex())
3323 fm.data(branch=ctx.branch())
3323 fm.data(branch=ctx.branch())
3324 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3324 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3325 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3325 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3326 fm.context(ctx=ctx)
3326 fm.context(ctx=ctx)
3327
3327
3328 fm.plain("%s\n" % ' '.join(output))
3328 fm.plain("%s\n" % ' '.join(output))
3329 fm.end()
3329 fm.end()
3330
3330
3331 @command('import|patch',
3331 @command('import|patch',
3332 [('p', 'strip', 1,
3332 [('p', 'strip', 1,
3333 _('directory strip option for patch. This has the same '
3333 _('directory strip option for patch. This has the same '
3334 'meaning as the corresponding patch option'), _('NUM')),
3334 'meaning as the corresponding patch option'), _('NUM')),
3335 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3335 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3336 ('e', 'edit', False, _('invoke editor on commit messages')),
3336 ('e', 'edit', False, _('invoke editor on commit messages')),
3337 ('f', 'force', None,
3337 ('f', 'force', None,
3338 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3338 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3339 ('', 'no-commit', None,
3339 ('', 'no-commit', None,
3340 _("don't commit, just update the working directory")),
3340 _("don't commit, just update the working directory")),
3341 ('', 'bypass', None,
3341 ('', 'bypass', None,
3342 _("apply patch without touching the working directory")),
3342 _("apply patch without touching the working directory")),
3343 ('', 'partial', None,
3343 ('', 'partial', None,
3344 _('commit even if some hunks fail')),
3344 _('commit even if some hunks fail')),
3345 ('', 'exact', None,
3345 ('', 'exact', None,
3346 _('abort if patch would apply lossily')),
3346 _('abort if patch would apply lossily')),
3347 ('', 'prefix', '',
3347 ('', 'prefix', '',
3348 _('apply patch to subdirectory'), _('DIR')),
3348 _('apply patch to subdirectory'), _('DIR')),
3349 ('', 'import-branch', None,
3349 ('', 'import-branch', None,
3350 _('use any branch information in patch (implied by --exact)'))] +
3350 _('use any branch information in patch (implied by --exact)'))] +
3351 commitopts + commitopts2 + similarityopts,
3351 commitopts + commitopts2 + similarityopts,
3352 _('[OPTION]... PATCH...'),
3352 _('[OPTION]... PATCH...'),
3353 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3353 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3354 def import_(ui, repo, patch1=None, *patches, **opts):
3354 def import_(ui, repo, patch1=None, *patches, **opts):
3355 """import an ordered set of patches
3355 """import an ordered set of patches
3356
3356
3357 Import a list of patches and commit them individually (unless
3357 Import a list of patches and commit them individually (unless
3358 --no-commit is specified).
3358 --no-commit is specified).
3359
3359
3360 To read a patch from standard input (stdin), use "-" as the patch
3360 To read a patch from standard input (stdin), use "-" as the patch
3361 name. If a URL is specified, the patch will be downloaded from
3361 name. If a URL is specified, the patch will be downloaded from
3362 there.
3362 there.
3363
3363
3364 Import first applies changes to the working directory (unless
3364 Import first applies changes to the working directory (unless
3365 --bypass is specified), import will abort if there are outstanding
3365 --bypass is specified), import will abort if there are outstanding
3366 changes.
3366 changes.
3367
3367
3368 Use --bypass to apply and commit patches directly to the
3368 Use --bypass to apply and commit patches directly to the
3369 repository, without affecting the working directory. Without
3369 repository, without affecting the working directory. Without
3370 --exact, patches will be applied on top of the working directory
3370 --exact, patches will be applied on top of the working directory
3371 parent revision.
3371 parent revision.
3372
3372
3373 You can import a patch straight from a mail message. Even patches
3373 You can import a patch straight from a mail message. Even patches
3374 as attachments work (to use the body part, it must have type
3374 as attachments work (to use the body part, it must have type
3375 text/plain or text/x-patch). From and Subject headers of email
3375 text/plain or text/x-patch). From and Subject headers of email
3376 message are used as default committer and commit message. All
3376 message are used as default committer and commit message. All
3377 text/plain body parts before first diff are added to the commit
3377 text/plain body parts before first diff are added to the commit
3378 message.
3378 message.
3379
3379
3380 If the imported patch was generated by :hg:`export`, user and
3380 If the imported patch was generated by :hg:`export`, user and
3381 description from patch override values from message headers and
3381 description from patch override values from message headers and
3382 body. Values given on command line with -m/--message and -u/--user
3382 body. Values given on command line with -m/--message and -u/--user
3383 override these.
3383 override these.
3384
3384
3385 If --exact is specified, import will set the working directory to
3385 If --exact is specified, import will set the working directory to
3386 the parent of each patch before applying it, and will abort if the
3386 the parent of each patch before applying it, and will abort if the
3387 resulting changeset has a different ID than the one recorded in
3387 resulting changeset has a different ID than the one recorded in
3388 the patch. This will guard against various ways that portable
3388 the patch. This will guard against various ways that portable
3389 patch formats and mail systems might fail to transfer Mercurial
3389 patch formats and mail systems might fail to transfer Mercurial
3390 data or metadata. See :hg:`bundle` for lossless transmission.
3390 data or metadata. See :hg:`bundle` for lossless transmission.
3391
3391
3392 Use --partial to ensure a changeset will be created from the patch
3392 Use --partial to ensure a changeset will be created from the patch
3393 even if some hunks fail to apply. Hunks that fail to apply will be
3393 even if some hunks fail to apply. Hunks that fail to apply will be
3394 written to a <target-file>.rej file. Conflicts can then be resolved
3394 written to a <target-file>.rej file. Conflicts can then be resolved
3395 by hand before :hg:`commit --amend` is run to update the created
3395 by hand before :hg:`commit --amend` is run to update the created
3396 changeset. This flag exists to let people import patches that
3396 changeset. This flag exists to let people import patches that
3397 partially apply without losing the associated metadata (author,
3397 partially apply without losing the associated metadata (author,
3398 date, description, ...).
3398 date, description, ...).
3399
3399
3400 .. note::
3400 .. note::
3401
3401
3402 When no hunks apply cleanly, :hg:`import --partial` will create
3402 When no hunks apply cleanly, :hg:`import --partial` will create
3403 an empty changeset, importing only the patch metadata.
3403 an empty changeset, importing only the patch metadata.
3404
3404
3405 With -s/--similarity, hg will attempt to discover renames and
3405 With -s/--similarity, hg will attempt to discover renames and
3406 copies in the patch in the same way as :hg:`addremove`.
3406 copies in the patch in the same way as :hg:`addremove`.
3407
3407
3408 It is possible to use external patch programs to perform the patch
3408 It is possible to use external patch programs to perform the patch
3409 by setting the ``ui.patch`` configuration option. For the default
3409 by setting the ``ui.patch`` configuration option. For the default
3410 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3410 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3411 See :hg:`help config` for more information about configuration
3411 See :hg:`help config` for more information about configuration
3412 files and how to use these options.
3412 files and how to use these options.
3413
3413
3414 See :hg:`help dates` for a list of formats valid for -d/--date.
3414 See :hg:`help dates` for a list of formats valid for -d/--date.
3415
3415
3416 .. container:: verbose
3416 .. container:: verbose
3417
3417
3418 Examples:
3418 Examples:
3419
3419
3420 - import a traditional patch from a website and detect renames::
3420 - import a traditional patch from a website and detect renames::
3421
3421
3422 hg import -s 80 http://example.com/bugfix.patch
3422 hg import -s 80 http://example.com/bugfix.patch
3423
3423
3424 - import a changeset from an hgweb server::
3424 - import a changeset from an hgweb server::
3425
3425
3426 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3426 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3427
3427
3428 - import all the patches in an Unix-style mbox::
3428 - import all the patches in an Unix-style mbox::
3429
3429
3430 hg import incoming-patches.mbox
3430 hg import incoming-patches.mbox
3431
3431
3432 - import patches from stdin::
3432 - import patches from stdin::
3433
3433
3434 hg import -
3434 hg import -
3435
3435
3436 - attempt to exactly restore an exported changeset (not always
3436 - attempt to exactly restore an exported changeset (not always
3437 possible)::
3437 possible)::
3438
3438
3439 hg import --exact proposed-fix.patch
3439 hg import --exact proposed-fix.patch
3440
3440
3441 - use an external tool to apply a patch which is too fuzzy for
3441 - use an external tool to apply a patch which is too fuzzy for
3442 the default internal tool.
3442 the default internal tool.
3443
3443
3444 hg import --config ui.patch="patch --merge" fuzzy.patch
3444 hg import --config ui.patch="patch --merge" fuzzy.patch
3445
3445
3446 - change the default fuzzing from 2 to a less strict 7
3446 - change the default fuzzing from 2 to a less strict 7
3447
3447
3448 hg import --config ui.fuzz=7 fuzz.patch
3448 hg import --config ui.fuzz=7 fuzz.patch
3449
3449
3450 Returns 0 on success, 1 on partial success (see --partial).
3450 Returns 0 on success, 1 on partial success (see --partial).
3451 """
3451 """
3452
3452
3453 opts = pycompat.byteskwargs(opts)
3453 opts = pycompat.byteskwargs(opts)
3454 if not patch1:
3454 if not patch1:
3455 raise error.Abort(_('need at least one patch to import'))
3455 raise error.Abort(_('need at least one patch to import'))
3456
3456
3457 patches = (patch1,) + patches
3457 patches = (patch1,) + patches
3458
3458
3459 date = opts.get('date')
3459 date = opts.get('date')
3460 if date:
3460 if date:
3461 opts['date'] = dateutil.parsedate(date)
3461 opts['date'] = dateutil.parsedate(date)
3462
3462
3463 exact = opts.get('exact')
3463 exact = opts.get('exact')
3464 update = not opts.get('bypass')
3464 update = not opts.get('bypass')
3465 if not update and opts.get('no_commit'):
3465 if not update and opts.get('no_commit'):
3466 raise error.Abort(_('cannot use --no-commit with --bypass'))
3466 raise error.Abort(_('cannot use --no-commit with --bypass'))
3467 try:
3467 try:
3468 sim = float(opts.get('similarity') or 0)
3468 sim = float(opts.get('similarity') or 0)
3469 except ValueError:
3469 except ValueError:
3470 raise error.Abort(_('similarity must be a number'))
3470 raise error.Abort(_('similarity must be a number'))
3471 if sim < 0 or sim > 100:
3471 if sim < 0 or sim > 100:
3472 raise error.Abort(_('similarity must be between 0 and 100'))
3472 raise error.Abort(_('similarity must be between 0 and 100'))
3473 if sim and not update:
3473 if sim and not update:
3474 raise error.Abort(_('cannot use --similarity with --bypass'))
3474 raise error.Abort(_('cannot use --similarity with --bypass'))
3475 if exact:
3475 if exact:
3476 if opts.get('edit'):
3476 if opts.get('edit'):
3477 raise error.Abort(_('cannot use --exact with --edit'))
3477 raise error.Abort(_('cannot use --exact with --edit'))
3478 if opts.get('prefix'):
3478 if opts.get('prefix'):
3479 raise error.Abort(_('cannot use --exact with --prefix'))
3479 raise error.Abort(_('cannot use --exact with --prefix'))
3480
3480
3481 base = opts["base"]
3481 base = opts["base"]
3482 msgs = []
3482 msgs = []
3483 ret = 0
3483 ret = 0
3484
3484
3485 with repo.wlock():
3485 with repo.wlock():
3486 if update:
3486 if update:
3487 cmdutil.checkunfinished(repo)
3487 cmdutil.checkunfinished(repo)
3488 if (exact or not opts.get('force')):
3488 if (exact or not opts.get('force')):
3489 cmdutil.bailifchanged(repo)
3489 cmdutil.bailifchanged(repo)
3490
3490
3491 if not opts.get('no_commit'):
3491 if not opts.get('no_commit'):
3492 lock = repo.lock
3492 lock = repo.lock
3493 tr = lambda: repo.transaction('import')
3493 tr = lambda: repo.transaction('import')
3494 dsguard = util.nullcontextmanager
3494 dsguard = util.nullcontextmanager
3495 else:
3495 else:
3496 lock = util.nullcontextmanager
3496 lock = util.nullcontextmanager
3497 tr = util.nullcontextmanager
3497 tr = util.nullcontextmanager
3498 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3498 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3499 with lock(), tr(), dsguard():
3499 with lock(), tr(), dsguard():
3500 parents = repo[None].parents()
3500 parents = repo[None].parents()
3501 for patchurl in patches:
3501 for patchurl in patches:
3502 if patchurl == '-':
3502 if patchurl == '-':
3503 ui.status(_('applying patch from stdin\n'))
3503 ui.status(_('applying patch from stdin\n'))
3504 patchfile = ui.fin
3504 patchfile = ui.fin
3505 patchurl = 'stdin' # for error message
3505 patchurl = 'stdin' # for error message
3506 else:
3506 else:
3507 patchurl = os.path.join(base, patchurl)
3507 patchurl = os.path.join(base, patchurl)
3508 ui.status(_('applying %s\n') % patchurl)
3508 ui.status(_('applying %s\n') % patchurl)
3509 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3509 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3510
3510
3511 haspatch = False
3511 haspatch = False
3512 for hunk in patch.split(patchfile):
3512 for hunk in patch.split(patchfile):
3513 with patch.extract(ui, hunk) as patchdata:
3513 with patch.extract(ui, hunk) as patchdata:
3514 msg, node, rej = cmdutil.tryimportone(ui, repo,
3514 msg, node, rej = cmdutil.tryimportone(ui, repo,
3515 patchdata,
3515 patchdata,
3516 parents, opts,
3516 parents, opts,
3517 msgs, hg.clean)
3517 msgs, hg.clean)
3518 if msg:
3518 if msg:
3519 haspatch = True
3519 haspatch = True
3520 ui.note(msg + '\n')
3520 ui.note(msg + '\n')
3521 if update or exact:
3521 if update or exact:
3522 parents = repo[None].parents()
3522 parents = repo[None].parents()
3523 else:
3523 else:
3524 parents = [repo[node]]
3524 parents = [repo[node]]
3525 if rej:
3525 if rej:
3526 ui.write_err(_("patch applied partially\n"))
3526 ui.write_err(_("patch applied partially\n"))
3527 ui.write_err(_("(fix the .rej files and run "
3527 ui.write_err(_("(fix the .rej files and run "
3528 "`hg commit --amend`)\n"))
3528 "`hg commit --amend`)\n"))
3529 ret = 1
3529 ret = 1
3530 break
3530 break
3531
3531
3532 if not haspatch:
3532 if not haspatch:
3533 raise error.Abort(_('%s: no diffs found') % patchurl)
3533 raise error.Abort(_('%s: no diffs found') % patchurl)
3534
3534
3535 if msgs:
3535 if msgs:
3536 repo.savecommitmessage('\n* * *\n'.join(msgs))
3536 repo.savecommitmessage('\n* * *\n'.join(msgs))
3537 return ret
3537 return ret
3538
3538
3539 @command('incoming|in',
3539 @command('incoming|in',
3540 [('f', 'force', None,
3540 [('f', 'force', None,
3541 _('run even if remote repository is unrelated')),
3541 _('run even if remote repository is unrelated')),
3542 ('n', 'newest-first', None, _('show newest record first')),
3542 ('n', 'newest-first', None, _('show newest record first')),
3543 ('', 'bundle', '',
3543 ('', 'bundle', '',
3544 _('file to store the bundles into'), _('FILE')),
3544 _('file to store the bundles into'), _('FILE')),
3545 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3545 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3546 ('B', 'bookmarks', False, _("compare bookmarks")),
3546 ('B', 'bookmarks', False, _("compare bookmarks")),
3547 ('b', 'branch', [],
3547 ('b', 'branch', [],
3548 _('a specific branch you would like to pull'), _('BRANCH')),
3548 _('a specific branch you would like to pull'), _('BRANCH')),
3549 ] + logopts + remoteopts + subrepoopts,
3549 ] + logopts + remoteopts + subrepoopts,
3550 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3550 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3551 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3551 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3552 def incoming(ui, repo, source="default", **opts):
3552 def incoming(ui, repo, source="default", **opts):
3553 """show new changesets found in source
3553 """show new changesets found in source
3554
3554
3555 Show new changesets found in the specified path/URL or the default
3555 Show new changesets found in the specified path/URL or the default
3556 pull location. These are the changesets that would have been pulled
3556 pull location. These are the changesets that would have been pulled
3557 by :hg:`pull` at the time you issued this command.
3557 by :hg:`pull` at the time you issued this command.
3558
3558
3559 See pull for valid source format details.
3559 See pull for valid source format details.
3560
3560
3561 .. container:: verbose
3561 .. container:: verbose
3562
3562
3563 With -B/--bookmarks, the result of bookmark comparison between
3563 With -B/--bookmarks, the result of bookmark comparison between
3564 local and remote repositories is displayed. With -v/--verbose,
3564 local and remote repositories is displayed. With -v/--verbose,
3565 status is also displayed for each bookmark like below::
3565 status is also displayed for each bookmark like below::
3566
3566
3567 BM1 01234567890a added
3567 BM1 01234567890a added
3568 BM2 1234567890ab advanced
3568 BM2 1234567890ab advanced
3569 BM3 234567890abc diverged
3569 BM3 234567890abc diverged
3570 BM4 34567890abcd changed
3570 BM4 34567890abcd changed
3571
3571
3572 The action taken locally when pulling depends on the
3572 The action taken locally when pulling depends on the
3573 status of each bookmark:
3573 status of each bookmark:
3574
3574
3575 :``added``: pull will create it
3575 :``added``: pull will create it
3576 :``advanced``: pull will update it
3576 :``advanced``: pull will update it
3577 :``diverged``: pull will create a divergent bookmark
3577 :``diverged``: pull will create a divergent bookmark
3578 :``changed``: result depends on remote changesets
3578 :``changed``: result depends on remote changesets
3579
3579
3580 From the point of view of pulling behavior, bookmark
3580 From the point of view of pulling behavior, bookmark
3581 existing only in the remote repository are treated as ``added``,
3581 existing only in the remote repository are treated as ``added``,
3582 even if it is in fact locally deleted.
3582 even if it is in fact locally deleted.
3583
3583
3584 .. container:: verbose
3584 .. container:: verbose
3585
3585
3586 For remote repository, using --bundle avoids downloading the
3586 For remote repository, using --bundle avoids downloading the
3587 changesets twice if the incoming is followed by a pull.
3587 changesets twice if the incoming is followed by a pull.
3588
3588
3589 Examples:
3589 Examples:
3590
3590
3591 - show incoming changes with patches and full description::
3591 - show incoming changes with patches and full description::
3592
3592
3593 hg incoming -vp
3593 hg incoming -vp
3594
3594
3595 - show incoming changes excluding merges, store a bundle::
3595 - show incoming changes excluding merges, store a bundle::
3596
3596
3597 hg in -vpM --bundle incoming.hg
3597 hg in -vpM --bundle incoming.hg
3598 hg pull incoming.hg
3598 hg pull incoming.hg
3599
3599
3600 - briefly list changes inside a bundle::
3600 - briefly list changes inside a bundle::
3601
3601
3602 hg in changes.hg -T "{desc|firstline}\\n"
3602 hg in changes.hg -T "{desc|firstline}\\n"
3603
3603
3604 Returns 0 if there are incoming changes, 1 otherwise.
3604 Returns 0 if there are incoming changes, 1 otherwise.
3605 """
3605 """
3606 opts = pycompat.byteskwargs(opts)
3606 opts = pycompat.byteskwargs(opts)
3607 if opts.get('graph'):
3607 if opts.get('graph'):
3608 logcmdutil.checkunsupportedgraphflags([], opts)
3608 logcmdutil.checkunsupportedgraphflags([], opts)
3609 def display(other, chlist, displayer):
3609 def display(other, chlist, displayer):
3610 revdag = logcmdutil.graphrevs(other, chlist, opts)
3610 revdag = logcmdutil.graphrevs(other, chlist, opts)
3611 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3611 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3612 graphmod.asciiedges)
3612 graphmod.asciiedges)
3613
3613
3614 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3614 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3615 return 0
3615 return 0
3616
3616
3617 if opts.get('bundle') and opts.get('subrepos'):
3617 if opts.get('bundle') and opts.get('subrepos'):
3618 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3618 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3619
3619
3620 if opts.get('bookmarks'):
3620 if opts.get('bookmarks'):
3621 source, branches = hg.parseurl(ui.expandpath(source),
3621 source, branches = hg.parseurl(ui.expandpath(source),
3622 opts.get('branch'))
3622 opts.get('branch'))
3623 other = hg.peer(repo, opts, source)
3623 other = hg.peer(repo, opts, source)
3624 if 'bookmarks' not in other.listkeys('namespaces'):
3624 if 'bookmarks' not in other.listkeys('namespaces'):
3625 ui.warn(_("remote doesn't support bookmarks\n"))
3625 ui.warn(_("remote doesn't support bookmarks\n"))
3626 return 0
3626 return 0
3627 ui.pager('incoming')
3627 ui.pager('incoming')
3628 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3628 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3629 return bookmarks.incoming(ui, repo, other)
3629 return bookmarks.incoming(ui, repo, other)
3630
3630
3631 repo._subtoppath = ui.expandpath(source)
3631 repo._subtoppath = ui.expandpath(source)
3632 try:
3632 try:
3633 return hg.incoming(ui, repo, source, opts)
3633 return hg.incoming(ui, repo, source, opts)
3634 finally:
3634 finally:
3635 del repo._subtoppath
3635 del repo._subtoppath
3636
3636
3637
3637
3638 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3638 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3639 helpcategory=command.CATEGORY_REPO_CREATION,
3639 helpcategory=command.CATEGORY_REPO_CREATION,
3640 helpbasic=True, norepo=True)
3640 helpbasic=True, norepo=True)
3641 def init(ui, dest=".", **opts):
3641 def init(ui, dest=".", **opts):
3642 """create a new repository in the given directory
3642 """create a new repository in the given directory
3643
3643
3644 Initialize a new repository in the given directory. If the given
3644 Initialize a new repository in the given directory. If the given
3645 directory does not exist, it will be created.
3645 directory does not exist, it will be created.
3646
3646
3647 If no directory is given, the current directory is used.
3647 If no directory is given, the current directory is used.
3648
3648
3649 It is possible to specify an ``ssh://`` URL as the destination.
3649 It is possible to specify an ``ssh://`` URL as the destination.
3650 See :hg:`help urls` for more information.
3650 See :hg:`help urls` for more information.
3651
3651
3652 Returns 0 on success.
3652 Returns 0 on success.
3653 """
3653 """
3654 opts = pycompat.byteskwargs(opts)
3654 opts = pycompat.byteskwargs(opts)
3655 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3655 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3656
3656
3657 @command('locate',
3657 @command('locate',
3658 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3658 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3659 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3659 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3660 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3660 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3661 ] + walkopts,
3661 ] + walkopts,
3662 _('[OPTION]... [PATTERN]...'),
3662 _('[OPTION]... [PATTERN]...'),
3663 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3663 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3664 def locate(ui, repo, *pats, **opts):
3664 def locate(ui, repo, *pats, **opts):
3665 """locate files matching specific patterns (DEPRECATED)
3665 """locate files matching specific patterns (DEPRECATED)
3666
3666
3667 Print files under Mercurial control in the working directory whose
3667 Print files under Mercurial control in the working directory whose
3668 names match the given patterns.
3668 names match the given patterns.
3669
3669
3670 By default, this command searches all directories in the working
3670 By default, this command searches all directories in the working
3671 directory. To search just the current directory and its
3671 directory. To search just the current directory and its
3672 subdirectories, use "--include .".
3672 subdirectories, use "--include .".
3673
3673
3674 If no patterns are given to match, this command prints the names
3674 If no patterns are given to match, this command prints the names
3675 of all files under Mercurial control in the working directory.
3675 of all files under Mercurial control in the working directory.
3676
3676
3677 If you want to feed the output of this command into the "xargs"
3677 If you want to feed the output of this command into the "xargs"
3678 command, use the -0 option to both this command and "xargs". This
3678 command, use the -0 option to both this command and "xargs". This
3679 will avoid the problem of "xargs" treating single filenames that
3679 will avoid the problem of "xargs" treating single filenames that
3680 contain whitespace as multiple filenames.
3680 contain whitespace as multiple filenames.
3681
3681
3682 See :hg:`help files` for a more versatile command.
3682 See :hg:`help files` for a more versatile command.
3683
3683
3684 Returns 0 if a match is found, 1 otherwise.
3684 Returns 0 if a match is found, 1 otherwise.
3685 """
3685 """
3686 opts = pycompat.byteskwargs(opts)
3686 opts = pycompat.byteskwargs(opts)
3687 if opts.get('print0'):
3687 if opts.get('print0'):
3688 end = '\0'
3688 end = '\0'
3689 else:
3689 else:
3690 end = '\n'
3690 end = '\n'
3691 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3691 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3692
3692
3693 ret = 1
3693 ret = 1
3694 m = scmutil.match(ctx, pats, opts, default='relglob',
3694 m = scmutil.match(ctx, pats, opts, default='relglob',
3695 badfn=lambda x, y: False)
3695 badfn=lambda x, y: False)
3696
3696
3697 ui.pager('locate')
3697 ui.pager('locate')
3698 if ctx.rev() is None:
3698 if ctx.rev() is None:
3699 # When run on the working copy, "locate" includes removed files, so
3699 # When run on the working copy, "locate" includes removed files, so
3700 # we get the list of files from the dirstate.
3700 # we get the list of files from the dirstate.
3701 filesgen = sorted(repo.dirstate.matches(m))
3701 filesgen = sorted(repo.dirstate.matches(m))
3702 else:
3702 else:
3703 filesgen = ctx.matches(m)
3703 filesgen = ctx.matches(m)
3704 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3704 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3705 for abs in filesgen:
3705 for abs in filesgen:
3706 if opts.get('fullpath'):
3706 if opts.get('fullpath'):
3707 ui.write(repo.wjoin(abs), end)
3707 ui.write(repo.wjoin(abs), end)
3708 else:
3708 else:
3709 ui.write(uipathfn(abs), end)
3709 ui.write(uipathfn(abs), end)
3710 ret = 0
3710 ret = 0
3711
3711
3712 return ret
3712 return ret
3713
3713
3714 @command('log|history',
3714 @command('log|history',
3715 [('f', 'follow', None,
3715 [('f', 'follow', None,
3716 _('follow changeset history, or file history across copies and renames')),
3716 _('follow changeset history, or file history across copies and renames')),
3717 ('', 'follow-first', None,
3717 ('', 'follow-first', None,
3718 _('only follow the first parent of merge changesets (DEPRECATED)')),
3718 _('only follow the first parent of merge changesets (DEPRECATED)')),
3719 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3719 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3720 ('C', 'copies', None, _('show copied files')),
3720 ('C', 'copies', None, _('show copied files')),
3721 ('k', 'keyword', [],
3721 ('k', 'keyword', [],
3722 _('do case-insensitive search for a given text'), _('TEXT')),
3722 _('do case-insensitive search for a given text'), _('TEXT')),
3723 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3723 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3724 ('L', 'line-range', [],
3724 ('L', 'line-range', [],
3725 _('follow line range of specified file (EXPERIMENTAL)'),
3725 _('follow line range of specified file (EXPERIMENTAL)'),
3726 _('FILE,RANGE')),
3726 _('FILE,RANGE')),
3727 ('', 'removed', None, _('include revisions where files were removed')),
3727 ('', 'removed', None, _('include revisions where files were removed')),
3728 ('m', 'only-merges', None,
3728 ('m', 'only-merges', None,
3729 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3729 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3730 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3730 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3731 ('', 'only-branch', [],
3731 ('', 'only-branch', [],
3732 _('show only changesets within the given named branch (DEPRECATED)'),
3732 _('show only changesets within the given named branch (DEPRECATED)'),
3733 _('BRANCH')),
3733 _('BRANCH')),
3734 ('b', 'branch', [],
3734 ('b', 'branch', [],
3735 _('show changesets within the given named branch'), _('BRANCH')),
3735 _('show changesets within the given named branch'), _('BRANCH')),
3736 ('P', 'prune', [],
3736 ('P', 'prune', [],
3737 _('do not display revision or any of its ancestors'), _('REV')),
3737 _('do not display revision or any of its ancestors'), _('REV')),
3738 ] + logopts + walkopts,
3738 ] + logopts + walkopts,
3739 _('[OPTION]... [FILE]'),
3739 _('[OPTION]... [FILE]'),
3740 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3740 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3741 helpbasic=True, inferrepo=True,
3741 helpbasic=True, inferrepo=True,
3742 intents={INTENT_READONLY})
3742 intents={INTENT_READONLY})
3743 def log(ui, repo, *pats, **opts):
3743 def log(ui, repo, *pats, **opts):
3744 """show revision history of entire repository or files
3744 """show revision history of entire repository or files
3745
3745
3746 Print the revision history of the specified files or the entire
3746 Print the revision history of the specified files or the entire
3747 project.
3747 project.
3748
3748
3749 If no revision range is specified, the default is ``tip:0`` unless
3749 If no revision range is specified, the default is ``tip:0`` unless
3750 --follow is set, in which case the working directory parent is
3750 --follow is set, in which case the working directory parent is
3751 used as the starting revision.
3751 used as the starting revision.
3752
3752
3753 File history is shown without following rename or copy history of
3753 File history is shown without following rename or copy history of
3754 files. Use -f/--follow with a filename to follow history across
3754 files. Use -f/--follow with a filename to follow history across
3755 renames and copies. --follow without a filename will only show
3755 renames and copies. --follow without a filename will only show
3756 ancestors of the starting revision.
3756 ancestors of the starting revision.
3757
3757
3758 By default this command prints revision number and changeset id,
3758 By default this command prints revision number and changeset id,
3759 tags, non-trivial parents, user, date and time, and a summary for
3759 tags, non-trivial parents, user, date and time, and a summary for
3760 each commit. When the -v/--verbose switch is used, the list of
3760 each commit. When the -v/--verbose switch is used, the list of
3761 changed files and full commit message are shown.
3761 changed files and full commit message are shown.
3762
3762
3763 With --graph the revisions are shown as an ASCII art DAG with the most
3763 With --graph the revisions are shown as an ASCII art DAG with the most
3764 recent changeset at the top.
3764 recent changeset at the top.
3765 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3765 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3766 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3766 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3767 changeset from the lines below is a parent of the 'o' merge on the same
3767 changeset from the lines below is a parent of the 'o' merge on the same
3768 line.
3768 line.
3769 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3769 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3770 of a '|' indicates one or more revisions in a path are omitted.
3770 of a '|' indicates one or more revisions in a path are omitted.
3771
3771
3772 .. container:: verbose
3772 .. container:: verbose
3773
3773
3774 Use -L/--line-range FILE,M:N options to follow the history of lines
3774 Use -L/--line-range FILE,M:N options to follow the history of lines
3775 from M to N in FILE. With -p/--patch only diff hunks affecting
3775 from M to N in FILE. With -p/--patch only diff hunks affecting
3776 specified line range will be shown. This option requires --follow;
3776 specified line range will be shown. This option requires --follow;
3777 it can be specified multiple times. Currently, this option is not
3777 it can be specified multiple times. Currently, this option is not
3778 compatible with --graph. This option is experimental.
3778 compatible with --graph. This option is experimental.
3779
3779
3780 .. note::
3780 .. note::
3781
3781
3782 :hg:`log --patch` may generate unexpected diff output for merge
3782 :hg:`log --patch` may generate unexpected diff output for merge
3783 changesets, as it will only compare the merge changeset against
3783 changesets, as it will only compare the merge changeset against
3784 its first parent. Also, only files different from BOTH parents
3784 its first parent. Also, only files different from BOTH parents
3785 will appear in files:.
3785 will appear in files:.
3786
3786
3787 .. note::
3787 .. note::
3788
3788
3789 For performance reasons, :hg:`log FILE` may omit duplicate changes
3789 For performance reasons, :hg:`log FILE` may omit duplicate changes
3790 made on branches and will not show removals or mode changes. To
3790 made on branches and will not show removals or mode changes. To
3791 see all such changes, use the --removed switch.
3791 see all such changes, use the --removed switch.
3792
3792
3793 .. container:: verbose
3793 .. container:: verbose
3794
3794
3795 .. note::
3795 .. note::
3796
3796
3797 The history resulting from -L/--line-range options depends on diff
3797 The history resulting from -L/--line-range options depends on diff
3798 options; for instance if white-spaces are ignored, respective changes
3798 options; for instance if white-spaces are ignored, respective changes
3799 with only white-spaces in specified line range will not be listed.
3799 with only white-spaces in specified line range will not be listed.
3800
3800
3801 .. container:: verbose
3801 .. container:: verbose
3802
3802
3803 Some examples:
3803 Some examples:
3804
3804
3805 - changesets with full descriptions and file lists::
3805 - changesets with full descriptions and file lists::
3806
3806
3807 hg log -v
3807 hg log -v
3808
3808
3809 - changesets ancestral to the working directory::
3809 - changesets ancestral to the working directory::
3810
3810
3811 hg log -f
3811 hg log -f
3812
3812
3813 - last 10 commits on the current branch::
3813 - last 10 commits on the current branch::
3814
3814
3815 hg log -l 10 -b .
3815 hg log -l 10 -b .
3816
3816
3817 - changesets showing all modifications of a file, including removals::
3817 - changesets showing all modifications of a file, including removals::
3818
3818
3819 hg log --removed file.c
3819 hg log --removed file.c
3820
3820
3821 - all changesets that touch a directory, with diffs, excluding merges::
3821 - all changesets that touch a directory, with diffs, excluding merges::
3822
3822
3823 hg log -Mp lib/
3823 hg log -Mp lib/
3824
3824
3825 - all revision numbers that match a keyword::
3825 - all revision numbers that match a keyword::
3826
3826
3827 hg log -k bug --template "{rev}\\n"
3827 hg log -k bug --template "{rev}\\n"
3828
3828
3829 - the full hash identifier of the working directory parent::
3829 - the full hash identifier of the working directory parent::
3830
3830
3831 hg log -r . --template "{node}\\n"
3831 hg log -r . --template "{node}\\n"
3832
3832
3833 - list available log templates::
3833 - list available log templates::
3834
3834
3835 hg log -T list
3835 hg log -T list
3836
3836
3837 - check if a given changeset is included in a tagged release::
3837 - check if a given changeset is included in a tagged release::
3838
3838
3839 hg log -r "a21ccf and ancestor(1.9)"
3839 hg log -r "a21ccf and ancestor(1.9)"
3840
3840
3841 - find all changesets by some user in a date range::
3841 - find all changesets by some user in a date range::
3842
3842
3843 hg log -k alice -d "may 2008 to jul 2008"
3843 hg log -k alice -d "may 2008 to jul 2008"
3844
3844
3845 - summary of all changesets after the last tag::
3845 - summary of all changesets after the last tag::
3846
3846
3847 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3847 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3848
3848
3849 - changesets touching lines 13 to 23 for file.c::
3849 - changesets touching lines 13 to 23 for file.c::
3850
3850
3851 hg log -L file.c,13:23
3851 hg log -L file.c,13:23
3852
3852
3853 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3853 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3854 main.c with patch::
3854 main.c with patch::
3855
3855
3856 hg log -L file.c,13:23 -L main.c,2:6 -p
3856 hg log -L file.c,13:23 -L main.c,2:6 -p
3857
3857
3858 See :hg:`help dates` for a list of formats valid for -d/--date.
3858 See :hg:`help dates` for a list of formats valid for -d/--date.
3859
3859
3860 See :hg:`help revisions` for more about specifying and ordering
3860 See :hg:`help revisions` for more about specifying and ordering
3861 revisions.
3861 revisions.
3862
3862
3863 See :hg:`help templates` for more about pre-packaged styles and
3863 See :hg:`help templates` for more about pre-packaged styles and
3864 specifying custom templates. The default template used by the log
3864 specifying custom templates. The default template used by the log
3865 command can be customized via the ``ui.logtemplate`` configuration
3865 command can be customized via the ``ui.logtemplate`` configuration
3866 setting.
3866 setting.
3867
3867
3868 Returns 0 on success.
3868 Returns 0 on success.
3869
3869
3870 """
3870 """
3871 opts = pycompat.byteskwargs(opts)
3871 opts = pycompat.byteskwargs(opts)
3872 linerange = opts.get('line_range')
3872 linerange = opts.get('line_range')
3873
3873
3874 if linerange and not opts.get('follow'):
3874 if linerange and not opts.get('follow'):
3875 raise error.Abort(_('--line-range requires --follow'))
3875 raise error.Abort(_('--line-range requires --follow'))
3876
3876
3877 if linerange and pats:
3877 if linerange and pats:
3878 # TODO: take pats as patterns with no line-range filter
3878 # TODO: take pats as patterns with no line-range filter
3879 raise error.Abort(
3879 raise error.Abort(
3880 _('FILE arguments are not compatible with --line-range option')
3880 _('FILE arguments are not compatible with --line-range option')
3881 )
3881 )
3882
3882
3883 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3883 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3884 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3884 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3885 if linerange:
3885 if linerange:
3886 # TODO: should follow file history from logcmdutil._initialrevs(),
3886 # TODO: should follow file history from logcmdutil._initialrevs(),
3887 # then filter the result by logcmdutil._makerevset() and --limit
3887 # then filter the result by logcmdutil._makerevset() and --limit
3888 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3888 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3889
3889
3890 getrenamed = None
3890 getcopies = None
3891 if opts.get('copies'):
3891 if opts.get('copies'):
3892 endrev = None
3892 endrev = None
3893 if revs:
3893 if revs:
3894 endrev = revs.max() + 1
3894 endrev = revs.max() + 1
3895 getrenamed = scmutil.getrenamedfn(repo, endrev=endrev)
3895 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
3896
3896
3897 ui.pager('log')
3897 ui.pager('log')
3898 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3898 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3899 buffered=True)
3899 buffered=True)
3900 if opts.get('graph'):
3900 if opts.get('graph'):
3901 displayfn = logcmdutil.displaygraphrevs
3901 displayfn = logcmdutil.displaygraphrevs
3902 else:
3902 else:
3903 displayfn = logcmdutil.displayrevs
3903 displayfn = logcmdutil.displayrevs
3904 displayfn(ui, repo, revs, displayer, getrenamed)
3904 displayfn(ui, repo, revs, displayer, getcopies)
3905
3905
3906 @command('manifest',
3906 @command('manifest',
3907 [('r', 'rev', '', _('revision to display'), _('REV')),
3907 [('r', 'rev', '', _('revision to display'), _('REV')),
3908 ('', 'all', False, _("list files from all revisions"))]
3908 ('', 'all', False, _("list files from all revisions"))]
3909 + formatteropts,
3909 + formatteropts,
3910 _('[-r REV]'),
3910 _('[-r REV]'),
3911 helpcategory=command.CATEGORY_MAINTENANCE,
3911 helpcategory=command.CATEGORY_MAINTENANCE,
3912 intents={INTENT_READONLY})
3912 intents={INTENT_READONLY})
3913 def manifest(ui, repo, node=None, rev=None, **opts):
3913 def manifest(ui, repo, node=None, rev=None, **opts):
3914 """output the current or given revision of the project manifest
3914 """output the current or given revision of the project manifest
3915
3915
3916 Print a list of version controlled files for the given revision.
3916 Print a list of version controlled files for the given revision.
3917 If no revision is given, the first parent of the working directory
3917 If no revision is given, the first parent of the working directory
3918 is used, or the null revision if no revision is checked out.
3918 is used, or the null revision if no revision is checked out.
3919
3919
3920 With -v, print file permissions, symlink and executable bits.
3920 With -v, print file permissions, symlink and executable bits.
3921 With --debug, print file revision hashes.
3921 With --debug, print file revision hashes.
3922
3922
3923 If option --all is specified, the list of all files from all revisions
3923 If option --all is specified, the list of all files from all revisions
3924 is printed. This includes deleted and renamed files.
3924 is printed. This includes deleted and renamed files.
3925
3925
3926 Returns 0 on success.
3926 Returns 0 on success.
3927 """
3927 """
3928 opts = pycompat.byteskwargs(opts)
3928 opts = pycompat.byteskwargs(opts)
3929 fm = ui.formatter('manifest', opts)
3929 fm = ui.formatter('manifest', opts)
3930
3930
3931 if opts.get('all'):
3931 if opts.get('all'):
3932 if rev or node:
3932 if rev or node:
3933 raise error.Abort(_("can't specify a revision with --all"))
3933 raise error.Abort(_("can't specify a revision with --all"))
3934
3934
3935 res = set()
3935 res = set()
3936 for rev in repo:
3936 for rev in repo:
3937 ctx = repo[rev]
3937 ctx = repo[rev]
3938 res |= set(ctx.files())
3938 res |= set(ctx.files())
3939
3939
3940 ui.pager('manifest')
3940 ui.pager('manifest')
3941 for f in sorted(res):
3941 for f in sorted(res):
3942 fm.startitem()
3942 fm.startitem()
3943 fm.write("path", '%s\n', f)
3943 fm.write("path", '%s\n', f)
3944 fm.end()
3944 fm.end()
3945 return
3945 return
3946
3946
3947 if rev and node:
3947 if rev and node:
3948 raise error.Abort(_("please specify just one revision"))
3948 raise error.Abort(_("please specify just one revision"))
3949
3949
3950 if not node:
3950 if not node:
3951 node = rev
3951 node = rev
3952
3952
3953 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3953 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3954 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3954 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3955 if node:
3955 if node:
3956 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3956 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3957 ctx = scmutil.revsingle(repo, node)
3957 ctx = scmutil.revsingle(repo, node)
3958 mf = ctx.manifest()
3958 mf = ctx.manifest()
3959 ui.pager('manifest')
3959 ui.pager('manifest')
3960 for f in ctx:
3960 for f in ctx:
3961 fm.startitem()
3961 fm.startitem()
3962 fm.context(ctx=ctx)
3962 fm.context(ctx=ctx)
3963 fl = ctx[f].flags()
3963 fl = ctx[f].flags()
3964 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3964 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3965 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3965 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3966 fm.write('path', '%s\n', f)
3966 fm.write('path', '%s\n', f)
3967 fm.end()
3967 fm.end()
3968
3968
3969 @command('merge',
3969 @command('merge',
3970 [('f', 'force', None,
3970 [('f', 'force', None,
3971 _('force a merge including outstanding changes (DEPRECATED)')),
3971 _('force a merge including outstanding changes (DEPRECATED)')),
3972 ('r', 'rev', '', _('revision to merge'), _('REV')),
3972 ('r', 'rev', '', _('revision to merge'), _('REV')),
3973 ('P', 'preview', None,
3973 ('P', 'preview', None,
3974 _('review revisions to merge (no merge is performed)')),
3974 _('review revisions to merge (no merge is performed)')),
3975 ('', 'abort', None, _('abort the ongoing merge')),
3975 ('', 'abort', None, _('abort the ongoing merge')),
3976 ] + mergetoolopts,
3976 ] + mergetoolopts,
3977 _('[-P] [[-r] REV]'),
3977 _('[-P] [[-r] REV]'),
3978 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3978 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3979 def merge(ui, repo, node=None, **opts):
3979 def merge(ui, repo, node=None, **opts):
3980 """merge another revision into working directory
3980 """merge another revision into working directory
3981
3981
3982 The current working directory is updated with all changes made in
3982 The current working directory is updated with all changes made in
3983 the requested revision since the last common predecessor revision.
3983 the requested revision since the last common predecessor revision.
3984
3984
3985 Files that changed between either parent are marked as changed for
3985 Files that changed between either parent are marked as changed for
3986 the next commit and a commit must be performed before any further
3986 the next commit and a commit must be performed before any further
3987 updates to the repository are allowed. The next commit will have
3987 updates to the repository are allowed. The next commit will have
3988 two parents.
3988 two parents.
3989
3989
3990 ``--tool`` can be used to specify the merge tool used for file
3990 ``--tool`` can be used to specify the merge tool used for file
3991 merges. It overrides the HGMERGE environment variable and your
3991 merges. It overrides the HGMERGE environment variable and your
3992 configuration files. See :hg:`help merge-tools` for options.
3992 configuration files. See :hg:`help merge-tools` for options.
3993
3993
3994 If no revision is specified, the working directory's parent is a
3994 If no revision is specified, the working directory's parent is a
3995 head revision, and the current branch contains exactly one other
3995 head revision, and the current branch contains exactly one other
3996 head, the other head is merged with by default. Otherwise, an
3996 head, the other head is merged with by default. Otherwise, an
3997 explicit revision with which to merge must be provided.
3997 explicit revision with which to merge must be provided.
3998
3998
3999 See :hg:`help resolve` for information on handling file conflicts.
3999 See :hg:`help resolve` for information on handling file conflicts.
4000
4000
4001 To undo an uncommitted merge, use :hg:`merge --abort` which
4001 To undo an uncommitted merge, use :hg:`merge --abort` which
4002 will check out a clean copy of the original merge parent, losing
4002 will check out a clean copy of the original merge parent, losing
4003 all changes.
4003 all changes.
4004
4004
4005 Returns 0 on success, 1 if there are unresolved files.
4005 Returns 0 on success, 1 if there are unresolved files.
4006 """
4006 """
4007
4007
4008 opts = pycompat.byteskwargs(opts)
4008 opts = pycompat.byteskwargs(opts)
4009 abort = opts.get('abort')
4009 abort = opts.get('abort')
4010 if abort and repo.dirstate.p2() == nullid:
4010 if abort and repo.dirstate.p2() == nullid:
4011 cmdutil.wrongtooltocontinue(repo, _('merge'))
4011 cmdutil.wrongtooltocontinue(repo, _('merge'))
4012 if abort:
4012 if abort:
4013 if node:
4013 if node:
4014 raise error.Abort(_("cannot specify a node with --abort"))
4014 raise error.Abort(_("cannot specify a node with --abort"))
4015 if opts.get('rev'):
4015 if opts.get('rev'):
4016 raise error.Abort(_("cannot specify both --rev and --abort"))
4016 raise error.Abort(_("cannot specify both --rev and --abort"))
4017 if opts.get('preview'):
4017 if opts.get('preview'):
4018 raise error.Abort(_("cannot specify --preview with --abort"))
4018 raise error.Abort(_("cannot specify --preview with --abort"))
4019 if opts.get('rev') and node:
4019 if opts.get('rev') and node:
4020 raise error.Abort(_("please specify just one revision"))
4020 raise error.Abort(_("please specify just one revision"))
4021 if not node:
4021 if not node:
4022 node = opts.get('rev')
4022 node = opts.get('rev')
4023
4023
4024 if node:
4024 if node:
4025 node = scmutil.revsingle(repo, node).node()
4025 node = scmutil.revsingle(repo, node).node()
4026
4026
4027 if not node and not abort:
4027 if not node and not abort:
4028 node = repo[destutil.destmerge(repo)].node()
4028 node = repo[destutil.destmerge(repo)].node()
4029
4029
4030 if opts.get('preview'):
4030 if opts.get('preview'):
4031 # find nodes that are ancestors of p2 but not of p1
4031 # find nodes that are ancestors of p2 but not of p1
4032 p1 = repo.lookup('.')
4032 p1 = repo.lookup('.')
4033 p2 = node
4033 p2 = node
4034 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4034 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4035
4035
4036 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4036 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4037 for node in nodes:
4037 for node in nodes:
4038 displayer.show(repo[node])
4038 displayer.show(repo[node])
4039 displayer.close()
4039 displayer.close()
4040 return 0
4040 return 0
4041
4041
4042 # ui.forcemerge is an internal variable, do not document
4042 # ui.forcemerge is an internal variable, do not document
4043 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4043 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4044 with ui.configoverride(overrides, 'merge'):
4044 with ui.configoverride(overrides, 'merge'):
4045 force = opts.get('force')
4045 force = opts.get('force')
4046 labels = ['working copy', 'merge rev']
4046 labels = ['working copy', 'merge rev']
4047 return hg.merge(repo, node, force=force, mergeforce=force,
4047 return hg.merge(repo, node, force=force, mergeforce=force,
4048 labels=labels, abort=abort)
4048 labels=labels, abort=abort)
4049
4049
4050 @command('outgoing|out',
4050 @command('outgoing|out',
4051 [('f', 'force', None, _('run even when the destination is unrelated')),
4051 [('f', 'force', None, _('run even when the destination is unrelated')),
4052 ('r', 'rev', [],
4052 ('r', 'rev', [],
4053 _('a changeset intended to be included in the destination'), _('REV')),
4053 _('a changeset intended to be included in the destination'), _('REV')),
4054 ('n', 'newest-first', None, _('show newest record first')),
4054 ('n', 'newest-first', None, _('show newest record first')),
4055 ('B', 'bookmarks', False, _('compare bookmarks')),
4055 ('B', 'bookmarks', False, _('compare bookmarks')),
4056 ('b', 'branch', [], _('a specific branch you would like to push'),
4056 ('b', 'branch', [], _('a specific branch you would like to push'),
4057 _('BRANCH')),
4057 _('BRANCH')),
4058 ] + logopts + remoteopts + subrepoopts,
4058 ] + logopts + remoteopts + subrepoopts,
4059 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4059 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4060 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4060 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4061 def outgoing(ui, repo, dest=None, **opts):
4061 def outgoing(ui, repo, dest=None, **opts):
4062 """show changesets not found in the destination
4062 """show changesets not found in the destination
4063
4063
4064 Show changesets not found in the specified destination repository
4064 Show changesets not found in the specified destination repository
4065 or the default push location. These are the changesets that would
4065 or the default push location. These are the changesets that would
4066 be pushed if a push was requested.
4066 be pushed if a push was requested.
4067
4067
4068 See pull for details of valid destination formats.
4068 See pull for details of valid destination formats.
4069
4069
4070 .. container:: verbose
4070 .. container:: verbose
4071
4071
4072 With -B/--bookmarks, the result of bookmark comparison between
4072 With -B/--bookmarks, the result of bookmark comparison between
4073 local and remote repositories is displayed. With -v/--verbose,
4073 local and remote repositories is displayed. With -v/--verbose,
4074 status is also displayed for each bookmark like below::
4074 status is also displayed for each bookmark like below::
4075
4075
4076 BM1 01234567890a added
4076 BM1 01234567890a added
4077 BM2 deleted
4077 BM2 deleted
4078 BM3 234567890abc advanced
4078 BM3 234567890abc advanced
4079 BM4 34567890abcd diverged
4079 BM4 34567890abcd diverged
4080 BM5 4567890abcde changed
4080 BM5 4567890abcde changed
4081
4081
4082 The action taken when pushing depends on the
4082 The action taken when pushing depends on the
4083 status of each bookmark:
4083 status of each bookmark:
4084
4084
4085 :``added``: push with ``-B`` will create it
4085 :``added``: push with ``-B`` will create it
4086 :``deleted``: push with ``-B`` will delete it
4086 :``deleted``: push with ``-B`` will delete it
4087 :``advanced``: push will update it
4087 :``advanced``: push will update it
4088 :``diverged``: push with ``-B`` will update it
4088 :``diverged``: push with ``-B`` will update it
4089 :``changed``: push with ``-B`` will update it
4089 :``changed``: push with ``-B`` will update it
4090
4090
4091 From the point of view of pushing behavior, bookmarks
4091 From the point of view of pushing behavior, bookmarks
4092 existing only in the remote repository are treated as
4092 existing only in the remote repository are treated as
4093 ``deleted``, even if it is in fact added remotely.
4093 ``deleted``, even if it is in fact added remotely.
4094
4094
4095 Returns 0 if there are outgoing changes, 1 otherwise.
4095 Returns 0 if there are outgoing changes, 1 otherwise.
4096 """
4096 """
4097 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4097 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4098 # style URLs, so don't overwrite dest.
4098 # style URLs, so don't overwrite dest.
4099 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4099 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4100 if not path:
4100 if not path:
4101 raise error.Abort(_('default repository not configured!'),
4101 raise error.Abort(_('default repository not configured!'),
4102 hint=_("see 'hg help config.paths'"))
4102 hint=_("see 'hg help config.paths'"))
4103
4103
4104 opts = pycompat.byteskwargs(opts)
4104 opts = pycompat.byteskwargs(opts)
4105 if opts.get('graph'):
4105 if opts.get('graph'):
4106 logcmdutil.checkunsupportedgraphflags([], opts)
4106 logcmdutil.checkunsupportedgraphflags([], opts)
4107 o, other = hg._outgoing(ui, repo, dest, opts)
4107 o, other = hg._outgoing(ui, repo, dest, opts)
4108 if not o:
4108 if not o:
4109 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4109 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4110 return
4110 return
4111
4111
4112 revdag = logcmdutil.graphrevs(repo, o, opts)
4112 revdag = logcmdutil.graphrevs(repo, o, opts)
4113 ui.pager('outgoing')
4113 ui.pager('outgoing')
4114 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4114 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4115 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4115 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4116 graphmod.asciiedges)
4116 graphmod.asciiedges)
4117 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4117 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4118 return 0
4118 return 0
4119
4119
4120 if opts.get('bookmarks'):
4120 if opts.get('bookmarks'):
4121 dest = path.pushloc or path.loc
4121 dest = path.pushloc or path.loc
4122 other = hg.peer(repo, opts, dest)
4122 other = hg.peer(repo, opts, dest)
4123 if 'bookmarks' not in other.listkeys('namespaces'):
4123 if 'bookmarks' not in other.listkeys('namespaces'):
4124 ui.warn(_("remote doesn't support bookmarks\n"))
4124 ui.warn(_("remote doesn't support bookmarks\n"))
4125 return 0
4125 return 0
4126 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4126 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4127 ui.pager('outgoing')
4127 ui.pager('outgoing')
4128 return bookmarks.outgoing(ui, repo, other)
4128 return bookmarks.outgoing(ui, repo, other)
4129
4129
4130 repo._subtoppath = path.pushloc or path.loc
4130 repo._subtoppath = path.pushloc or path.loc
4131 try:
4131 try:
4132 return hg.outgoing(ui, repo, dest, opts)
4132 return hg.outgoing(ui, repo, dest, opts)
4133 finally:
4133 finally:
4134 del repo._subtoppath
4134 del repo._subtoppath
4135
4135
4136 @command('parents',
4136 @command('parents',
4137 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4137 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4138 ] + templateopts,
4138 ] + templateopts,
4139 _('[-r REV] [FILE]'),
4139 _('[-r REV] [FILE]'),
4140 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4140 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4141 inferrepo=True)
4141 inferrepo=True)
4142 def parents(ui, repo, file_=None, **opts):
4142 def parents(ui, repo, file_=None, **opts):
4143 """show the parents of the working directory or revision (DEPRECATED)
4143 """show the parents of the working directory or revision (DEPRECATED)
4144
4144
4145 Print the working directory's parent revisions. If a revision is
4145 Print the working directory's parent revisions. If a revision is
4146 given via -r/--rev, the parent of that revision will be printed.
4146 given via -r/--rev, the parent of that revision will be printed.
4147 If a file argument is given, the revision in which the file was
4147 If a file argument is given, the revision in which the file was
4148 last changed (before the working directory revision or the
4148 last changed (before the working directory revision or the
4149 argument to --rev if given) is printed.
4149 argument to --rev if given) is printed.
4150
4150
4151 This command is equivalent to::
4151 This command is equivalent to::
4152
4152
4153 hg log -r "p1()+p2()" or
4153 hg log -r "p1()+p2()" or
4154 hg log -r "p1(REV)+p2(REV)" or
4154 hg log -r "p1(REV)+p2(REV)" or
4155 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4155 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4156 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4156 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4157
4157
4158 See :hg:`summary` and :hg:`help revsets` for related information.
4158 See :hg:`summary` and :hg:`help revsets` for related information.
4159
4159
4160 Returns 0 on success.
4160 Returns 0 on success.
4161 """
4161 """
4162
4162
4163 opts = pycompat.byteskwargs(opts)
4163 opts = pycompat.byteskwargs(opts)
4164 rev = opts.get('rev')
4164 rev = opts.get('rev')
4165 if rev:
4165 if rev:
4166 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4166 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4167 ctx = scmutil.revsingle(repo, rev, None)
4167 ctx = scmutil.revsingle(repo, rev, None)
4168
4168
4169 if file_:
4169 if file_:
4170 m = scmutil.match(ctx, (file_,), opts)
4170 m = scmutil.match(ctx, (file_,), opts)
4171 if m.anypats() or len(m.files()) != 1:
4171 if m.anypats() or len(m.files()) != 1:
4172 raise error.Abort(_('can only specify an explicit filename'))
4172 raise error.Abort(_('can only specify an explicit filename'))
4173 file_ = m.files()[0]
4173 file_ = m.files()[0]
4174 filenodes = []
4174 filenodes = []
4175 for cp in ctx.parents():
4175 for cp in ctx.parents():
4176 if not cp:
4176 if not cp:
4177 continue
4177 continue
4178 try:
4178 try:
4179 filenodes.append(cp.filenode(file_))
4179 filenodes.append(cp.filenode(file_))
4180 except error.LookupError:
4180 except error.LookupError:
4181 pass
4181 pass
4182 if not filenodes:
4182 if not filenodes:
4183 raise error.Abort(_("'%s' not found in manifest!") % file_)
4183 raise error.Abort(_("'%s' not found in manifest!") % file_)
4184 p = []
4184 p = []
4185 for fn in filenodes:
4185 for fn in filenodes:
4186 fctx = repo.filectx(file_, fileid=fn)
4186 fctx = repo.filectx(file_, fileid=fn)
4187 p.append(fctx.node())
4187 p.append(fctx.node())
4188 else:
4188 else:
4189 p = [cp.node() for cp in ctx.parents()]
4189 p = [cp.node() for cp in ctx.parents()]
4190
4190
4191 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4191 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4192 for n in p:
4192 for n in p:
4193 if n != nullid:
4193 if n != nullid:
4194 displayer.show(repo[n])
4194 displayer.show(repo[n])
4195 displayer.close()
4195 displayer.close()
4196
4196
4197 @command('paths', formatteropts, _('[NAME]'),
4197 @command('paths', formatteropts, _('[NAME]'),
4198 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4198 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4199 optionalrepo=True, intents={INTENT_READONLY})
4199 optionalrepo=True, intents={INTENT_READONLY})
4200 def paths(ui, repo, search=None, **opts):
4200 def paths(ui, repo, search=None, **opts):
4201 """show aliases for remote repositories
4201 """show aliases for remote repositories
4202
4202
4203 Show definition of symbolic path name NAME. If no name is given,
4203 Show definition of symbolic path name NAME. If no name is given,
4204 show definition of all available names.
4204 show definition of all available names.
4205
4205
4206 Option -q/--quiet suppresses all output when searching for NAME
4206 Option -q/--quiet suppresses all output when searching for NAME
4207 and shows only the path names when listing all definitions.
4207 and shows only the path names when listing all definitions.
4208
4208
4209 Path names are defined in the [paths] section of your
4209 Path names are defined in the [paths] section of your
4210 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4210 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4211 repository, ``.hg/hgrc`` is used, too.
4211 repository, ``.hg/hgrc`` is used, too.
4212
4212
4213 The path names ``default`` and ``default-push`` have a special
4213 The path names ``default`` and ``default-push`` have a special
4214 meaning. When performing a push or pull operation, they are used
4214 meaning. When performing a push or pull operation, they are used
4215 as fallbacks if no location is specified on the command-line.
4215 as fallbacks if no location is specified on the command-line.
4216 When ``default-push`` is set, it will be used for push and
4216 When ``default-push`` is set, it will be used for push and
4217 ``default`` will be used for pull; otherwise ``default`` is used
4217 ``default`` will be used for pull; otherwise ``default`` is used
4218 as the fallback for both. When cloning a repository, the clone
4218 as the fallback for both. When cloning a repository, the clone
4219 source is written as ``default`` in ``.hg/hgrc``.
4219 source is written as ``default`` in ``.hg/hgrc``.
4220
4220
4221 .. note::
4221 .. note::
4222
4222
4223 ``default`` and ``default-push`` apply to all inbound (e.g.
4223 ``default`` and ``default-push`` apply to all inbound (e.g.
4224 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4224 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4225 and :hg:`bundle`) operations.
4225 and :hg:`bundle`) operations.
4226
4226
4227 See :hg:`help urls` for more information.
4227 See :hg:`help urls` for more information.
4228
4228
4229 .. container:: verbose
4229 .. container:: verbose
4230
4230
4231 Template:
4231 Template:
4232
4232
4233 The following keywords are supported. See also :hg:`help templates`.
4233 The following keywords are supported. See also :hg:`help templates`.
4234
4234
4235 :name: String. Symbolic name of the path alias.
4235 :name: String. Symbolic name of the path alias.
4236 :pushurl: String. URL for push operations.
4236 :pushurl: String. URL for push operations.
4237 :url: String. URL or directory path for the other operations.
4237 :url: String. URL or directory path for the other operations.
4238
4238
4239 Returns 0 on success.
4239 Returns 0 on success.
4240 """
4240 """
4241
4241
4242 opts = pycompat.byteskwargs(opts)
4242 opts = pycompat.byteskwargs(opts)
4243 ui.pager('paths')
4243 ui.pager('paths')
4244 if search:
4244 if search:
4245 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4245 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4246 if name == search]
4246 if name == search]
4247 else:
4247 else:
4248 pathitems = sorted(ui.paths.iteritems())
4248 pathitems = sorted(ui.paths.iteritems())
4249
4249
4250 fm = ui.formatter('paths', opts)
4250 fm = ui.formatter('paths', opts)
4251 if fm.isplain():
4251 if fm.isplain():
4252 hidepassword = util.hidepassword
4252 hidepassword = util.hidepassword
4253 else:
4253 else:
4254 hidepassword = bytes
4254 hidepassword = bytes
4255 if ui.quiet:
4255 if ui.quiet:
4256 namefmt = '%s\n'
4256 namefmt = '%s\n'
4257 else:
4257 else:
4258 namefmt = '%s = '
4258 namefmt = '%s = '
4259 showsubopts = not search and not ui.quiet
4259 showsubopts = not search and not ui.quiet
4260
4260
4261 for name, path in pathitems:
4261 for name, path in pathitems:
4262 fm.startitem()
4262 fm.startitem()
4263 fm.condwrite(not search, 'name', namefmt, name)
4263 fm.condwrite(not search, 'name', namefmt, name)
4264 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4264 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4265 for subopt, value in sorted(path.suboptions.items()):
4265 for subopt, value in sorted(path.suboptions.items()):
4266 assert subopt not in ('name', 'url')
4266 assert subopt not in ('name', 'url')
4267 if showsubopts:
4267 if showsubopts:
4268 fm.plain('%s:%s = ' % (name, subopt))
4268 fm.plain('%s:%s = ' % (name, subopt))
4269 fm.condwrite(showsubopts, subopt, '%s\n', value)
4269 fm.condwrite(showsubopts, subopt, '%s\n', value)
4270
4270
4271 fm.end()
4271 fm.end()
4272
4272
4273 if search and not pathitems:
4273 if search and not pathitems:
4274 if not ui.quiet:
4274 if not ui.quiet:
4275 ui.warn(_("not found!\n"))
4275 ui.warn(_("not found!\n"))
4276 return 1
4276 return 1
4277 else:
4277 else:
4278 return 0
4278 return 0
4279
4279
4280 @command('phase',
4280 @command('phase',
4281 [('p', 'public', False, _('set changeset phase to public')),
4281 [('p', 'public', False, _('set changeset phase to public')),
4282 ('d', 'draft', False, _('set changeset phase to draft')),
4282 ('d', 'draft', False, _('set changeset phase to draft')),
4283 ('s', 'secret', False, _('set changeset phase to secret')),
4283 ('s', 'secret', False, _('set changeset phase to secret')),
4284 ('f', 'force', False, _('allow to move boundary backward')),
4284 ('f', 'force', False, _('allow to move boundary backward')),
4285 ('r', 'rev', [], _('target revision'), _('REV')),
4285 ('r', 'rev', [], _('target revision'), _('REV')),
4286 ],
4286 ],
4287 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4287 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4288 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4288 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4289 def phase(ui, repo, *revs, **opts):
4289 def phase(ui, repo, *revs, **opts):
4290 """set or show the current phase name
4290 """set or show the current phase name
4291
4291
4292 With no argument, show the phase name of the current revision(s).
4292 With no argument, show the phase name of the current revision(s).
4293
4293
4294 With one of -p/--public, -d/--draft or -s/--secret, change the
4294 With one of -p/--public, -d/--draft or -s/--secret, change the
4295 phase value of the specified revisions.
4295 phase value of the specified revisions.
4296
4296
4297 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4297 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4298 lower phase to a higher phase. Phases are ordered as follows::
4298 lower phase to a higher phase. Phases are ordered as follows::
4299
4299
4300 public < draft < secret
4300 public < draft < secret
4301
4301
4302 Returns 0 on success, 1 if some phases could not be changed.
4302 Returns 0 on success, 1 if some phases could not be changed.
4303
4303
4304 (For more information about the phases concept, see :hg:`help phases`.)
4304 (For more information about the phases concept, see :hg:`help phases`.)
4305 """
4305 """
4306 opts = pycompat.byteskwargs(opts)
4306 opts = pycompat.byteskwargs(opts)
4307 # search for a unique phase argument
4307 # search for a unique phase argument
4308 targetphase = None
4308 targetphase = None
4309 for idx, name in enumerate(phases.cmdphasenames):
4309 for idx, name in enumerate(phases.cmdphasenames):
4310 if opts[name]:
4310 if opts[name]:
4311 if targetphase is not None:
4311 if targetphase is not None:
4312 raise error.Abort(_('only one phase can be specified'))
4312 raise error.Abort(_('only one phase can be specified'))
4313 targetphase = idx
4313 targetphase = idx
4314
4314
4315 # look for specified revision
4315 # look for specified revision
4316 revs = list(revs)
4316 revs = list(revs)
4317 revs.extend(opts['rev'])
4317 revs.extend(opts['rev'])
4318 if not revs:
4318 if not revs:
4319 # display both parents as the second parent phase can influence
4319 # display both parents as the second parent phase can influence
4320 # the phase of a merge commit
4320 # the phase of a merge commit
4321 revs = [c.rev() for c in repo[None].parents()]
4321 revs = [c.rev() for c in repo[None].parents()]
4322
4322
4323 revs = scmutil.revrange(repo, revs)
4323 revs = scmutil.revrange(repo, revs)
4324
4324
4325 ret = 0
4325 ret = 0
4326 if targetphase is None:
4326 if targetphase is None:
4327 # display
4327 # display
4328 for r in revs:
4328 for r in revs:
4329 ctx = repo[r]
4329 ctx = repo[r]
4330 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4330 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4331 else:
4331 else:
4332 with repo.lock(), repo.transaction("phase") as tr:
4332 with repo.lock(), repo.transaction("phase") as tr:
4333 # set phase
4333 # set phase
4334 if not revs:
4334 if not revs:
4335 raise error.Abort(_('empty revision set'))
4335 raise error.Abort(_('empty revision set'))
4336 nodes = [repo[r].node() for r in revs]
4336 nodes = [repo[r].node() for r in revs]
4337 # moving revision from public to draft may hide them
4337 # moving revision from public to draft may hide them
4338 # We have to check result on an unfiltered repository
4338 # We have to check result on an unfiltered repository
4339 unfi = repo.unfiltered()
4339 unfi = repo.unfiltered()
4340 getphase = unfi._phasecache.phase
4340 getphase = unfi._phasecache.phase
4341 olddata = [getphase(unfi, r) for r in unfi]
4341 olddata = [getphase(unfi, r) for r in unfi]
4342 phases.advanceboundary(repo, tr, targetphase, nodes)
4342 phases.advanceboundary(repo, tr, targetphase, nodes)
4343 if opts['force']:
4343 if opts['force']:
4344 phases.retractboundary(repo, tr, targetphase, nodes)
4344 phases.retractboundary(repo, tr, targetphase, nodes)
4345 getphase = unfi._phasecache.phase
4345 getphase = unfi._phasecache.phase
4346 newdata = [getphase(unfi, r) for r in unfi]
4346 newdata = [getphase(unfi, r) for r in unfi]
4347 changes = sum(newdata[r] != olddata[r] for r in unfi)
4347 changes = sum(newdata[r] != olddata[r] for r in unfi)
4348 cl = unfi.changelog
4348 cl = unfi.changelog
4349 rejected = [n for n in nodes
4349 rejected = [n for n in nodes
4350 if newdata[cl.rev(n)] < targetphase]
4350 if newdata[cl.rev(n)] < targetphase]
4351 if rejected:
4351 if rejected:
4352 ui.warn(_('cannot move %i changesets to a higher '
4352 ui.warn(_('cannot move %i changesets to a higher '
4353 'phase, use --force\n') % len(rejected))
4353 'phase, use --force\n') % len(rejected))
4354 ret = 1
4354 ret = 1
4355 if changes:
4355 if changes:
4356 msg = _('phase changed for %i changesets\n') % changes
4356 msg = _('phase changed for %i changesets\n') % changes
4357 if ret:
4357 if ret:
4358 ui.status(msg)
4358 ui.status(msg)
4359 else:
4359 else:
4360 ui.note(msg)
4360 ui.note(msg)
4361 else:
4361 else:
4362 ui.warn(_('no phases changed\n'))
4362 ui.warn(_('no phases changed\n'))
4363 return ret
4363 return ret
4364
4364
4365 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4365 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4366 """Run after a changegroup has been added via pull/unbundle
4366 """Run after a changegroup has been added via pull/unbundle
4367
4367
4368 This takes arguments below:
4368 This takes arguments below:
4369
4369
4370 :modheads: change of heads by pull/unbundle
4370 :modheads: change of heads by pull/unbundle
4371 :optupdate: updating working directory is needed or not
4371 :optupdate: updating working directory is needed or not
4372 :checkout: update destination revision (or None to default destination)
4372 :checkout: update destination revision (or None to default destination)
4373 :brev: a name, which might be a bookmark to be activated after updating
4373 :brev: a name, which might be a bookmark to be activated after updating
4374 """
4374 """
4375 if modheads == 0:
4375 if modheads == 0:
4376 return
4376 return
4377 if optupdate:
4377 if optupdate:
4378 try:
4378 try:
4379 return hg.updatetotally(ui, repo, checkout, brev)
4379 return hg.updatetotally(ui, repo, checkout, brev)
4380 except error.UpdateAbort as inst:
4380 except error.UpdateAbort as inst:
4381 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4381 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4382 hint = inst.hint
4382 hint = inst.hint
4383 raise error.UpdateAbort(msg, hint=hint)
4383 raise error.UpdateAbort(msg, hint=hint)
4384 if modheads is not None and modheads > 1:
4384 if modheads is not None and modheads > 1:
4385 currentbranchheads = len(repo.branchheads())
4385 currentbranchheads = len(repo.branchheads())
4386 if currentbranchheads == modheads:
4386 if currentbranchheads == modheads:
4387 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4387 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4388 elif currentbranchheads > 1:
4388 elif currentbranchheads > 1:
4389 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4389 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4390 "merge)\n"))
4390 "merge)\n"))
4391 else:
4391 else:
4392 ui.status(_("(run 'hg heads' to see heads)\n"))
4392 ui.status(_("(run 'hg heads' to see heads)\n"))
4393 elif not ui.configbool('commands', 'update.requiredest'):
4393 elif not ui.configbool('commands', 'update.requiredest'):
4394 ui.status(_("(run 'hg update' to get a working copy)\n"))
4394 ui.status(_("(run 'hg update' to get a working copy)\n"))
4395
4395
4396 @command('pull',
4396 @command('pull',
4397 [('u', 'update', None,
4397 [('u', 'update', None,
4398 _('update to new branch head if new descendants were pulled')),
4398 _('update to new branch head if new descendants were pulled')),
4399 ('f', 'force', None, _('run even when remote repository is unrelated')),
4399 ('f', 'force', None, _('run even when remote repository is unrelated')),
4400 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4400 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4401 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4401 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4402 ('b', 'branch', [], _('a specific branch you would like to pull'),
4402 ('b', 'branch', [], _('a specific branch you would like to pull'),
4403 _('BRANCH')),
4403 _('BRANCH')),
4404 ] + remoteopts,
4404 ] + remoteopts,
4405 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4405 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4406 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4406 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4407 helpbasic=True)
4407 helpbasic=True)
4408 def pull(ui, repo, source="default", **opts):
4408 def pull(ui, repo, source="default", **opts):
4409 """pull changes from the specified source
4409 """pull changes from the specified source
4410
4410
4411 Pull changes from a remote repository to a local one.
4411 Pull changes from a remote repository to a local one.
4412
4412
4413 This finds all changes from the repository at the specified path
4413 This finds all changes from the repository at the specified path
4414 or URL and adds them to a local repository (the current one unless
4414 or URL and adds them to a local repository (the current one unless
4415 -R is specified). By default, this does not update the copy of the
4415 -R is specified). By default, this does not update the copy of the
4416 project in the working directory.
4416 project in the working directory.
4417
4417
4418 When cloning from servers that support it, Mercurial may fetch
4418 When cloning from servers that support it, Mercurial may fetch
4419 pre-generated data. When this is done, hooks operating on incoming
4419 pre-generated data. When this is done, hooks operating on incoming
4420 changesets and changegroups may fire more than once, once for each
4420 changesets and changegroups may fire more than once, once for each
4421 pre-generated bundle and as well as for any additional remaining
4421 pre-generated bundle and as well as for any additional remaining
4422 data. See :hg:`help -e clonebundles` for more.
4422 data. See :hg:`help -e clonebundles` for more.
4423
4423
4424 Use :hg:`incoming` if you want to see what would have been added
4424 Use :hg:`incoming` if you want to see what would have been added
4425 by a pull at the time you issued this command. If you then decide
4425 by a pull at the time you issued this command. If you then decide
4426 to add those changes to the repository, you should use :hg:`pull
4426 to add those changes to the repository, you should use :hg:`pull
4427 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4427 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4428
4428
4429 If SOURCE is omitted, the 'default' path will be used.
4429 If SOURCE is omitted, the 'default' path will be used.
4430 See :hg:`help urls` for more information.
4430 See :hg:`help urls` for more information.
4431
4431
4432 Specifying bookmark as ``.`` is equivalent to specifying the active
4432 Specifying bookmark as ``.`` is equivalent to specifying the active
4433 bookmark's name.
4433 bookmark's name.
4434
4434
4435 Returns 0 on success, 1 if an update had unresolved files.
4435 Returns 0 on success, 1 if an update had unresolved files.
4436 """
4436 """
4437
4437
4438 opts = pycompat.byteskwargs(opts)
4438 opts = pycompat.byteskwargs(opts)
4439 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4439 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4440 msg = _('update destination required by configuration')
4440 msg = _('update destination required by configuration')
4441 hint = _('use hg pull followed by hg update DEST')
4441 hint = _('use hg pull followed by hg update DEST')
4442 raise error.Abort(msg, hint=hint)
4442 raise error.Abort(msg, hint=hint)
4443
4443
4444 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4444 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4445 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4445 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4446 other = hg.peer(repo, opts, source)
4446 other = hg.peer(repo, opts, source)
4447 try:
4447 try:
4448 revs, checkout = hg.addbranchrevs(repo, other, branches,
4448 revs, checkout = hg.addbranchrevs(repo, other, branches,
4449 opts.get('rev'))
4449 opts.get('rev'))
4450
4450
4451 pullopargs = {}
4451 pullopargs = {}
4452
4452
4453 nodes = None
4453 nodes = None
4454 if opts.get('bookmark') or revs:
4454 if opts.get('bookmark') or revs:
4455 # The list of bookmark used here is the same used to actually update
4455 # The list of bookmark used here is the same used to actually update
4456 # the bookmark names, to avoid the race from issue 4689 and we do
4456 # the bookmark names, to avoid the race from issue 4689 and we do
4457 # all lookup and bookmark queries in one go so they see the same
4457 # all lookup and bookmark queries in one go so they see the same
4458 # version of the server state (issue 4700).
4458 # version of the server state (issue 4700).
4459 nodes = []
4459 nodes = []
4460 fnodes = []
4460 fnodes = []
4461 revs = revs or []
4461 revs = revs or []
4462 if revs and not other.capable('lookup'):
4462 if revs and not other.capable('lookup'):
4463 err = _("other repository doesn't support revision lookup, "
4463 err = _("other repository doesn't support revision lookup, "
4464 "so a rev cannot be specified.")
4464 "so a rev cannot be specified.")
4465 raise error.Abort(err)
4465 raise error.Abort(err)
4466 with other.commandexecutor() as e:
4466 with other.commandexecutor() as e:
4467 fremotebookmarks = e.callcommand('listkeys', {
4467 fremotebookmarks = e.callcommand('listkeys', {
4468 'namespace': 'bookmarks'
4468 'namespace': 'bookmarks'
4469 })
4469 })
4470 for r in revs:
4470 for r in revs:
4471 fnodes.append(e.callcommand('lookup', {'key': r}))
4471 fnodes.append(e.callcommand('lookup', {'key': r}))
4472 remotebookmarks = fremotebookmarks.result()
4472 remotebookmarks = fremotebookmarks.result()
4473 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4473 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4474 pullopargs['remotebookmarks'] = remotebookmarks
4474 pullopargs['remotebookmarks'] = remotebookmarks
4475 for b in opts.get('bookmark', []):
4475 for b in opts.get('bookmark', []):
4476 b = repo._bookmarks.expandname(b)
4476 b = repo._bookmarks.expandname(b)
4477 if b not in remotebookmarks:
4477 if b not in remotebookmarks:
4478 raise error.Abort(_('remote bookmark %s not found!') % b)
4478 raise error.Abort(_('remote bookmark %s not found!') % b)
4479 nodes.append(remotebookmarks[b])
4479 nodes.append(remotebookmarks[b])
4480 for i, rev in enumerate(revs):
4480 for i, rev in enumerate(revs):
4481 node = fnodes[i].result()
4481 node = fnodes[i].result()
4482 nodes.append(node)
4482 nodes.append(node)
4483 if rev == checkout:
4483 if rev == checkout:
4484 checkout = node
4484 checkout = node
4485
4485
4486 wlock = util.nullcontextmanager()
4486 wlock = util.nullcontextmanager()
4487 if opts.get('update'):
4487 if opts.get('update'):
4488 wlock = repo.wlock()
4488 wlock = repo.wlock()
4489 with wlock:
4489 with wlock:
4490 pullopargs.update(opts.get('opargs', {}))
4490 pullopargs.update(opts.get('opargs', {}))
4491 modheads = exchange.pull(repo, other, heads=nodes,
4491 modheads = exchange.pull(repo, other, heads=nodes,
4492 force=opts.get('force'),
4492 force=opts.get('force'),
4493 bookmarks=opts.get('bookmark', ()),
4493 bookmarks=opts.get('bookmark', ()),
4494 opargs=pullopargs).cgresult
4494 opargs=pullopargs).cgresult
4495
4495
4496 # brev is a name, which might be a bookmark to be activated at
4496 # brev is a name, which might be a bookmark to be activated at
4497 # the end of the update. In other words, it is an explicit
4497 # the end of the update. In other words, it is an explicit
4498 # destination of the update
4498 # destination of the update
4499 brev = None
4499 brev = None
4500
4500
4501 if checkout:
4501 if checkout:
4502 checkout = repo.unfiltered().changelog.rev(checkout)
4502 checkout = repo.unfiltered().changelog.rev(checkout)
4503
4503
4504 # order below depends on implementation of
4504 # order below depends on implementation of
4505 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4505 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4506 # because 'checkout' is determined without it.
4506 # because 'checkout' is determined without it.
4507 if opts.get('rev'):
4507 if opts.get('rev'):
4508 brev = opts['rev'][0]
4508 brev = opts['rev'][0]
4509 elif opts.get('branch'):
4509 elif opts.get('branch'):
4510 brev = opts['branch'][0]
4510 brev = opts['branch'][0]
4511 else:
4511 else:
4512 brev = branches[0]
4512 brev = branches[0]
4513 repo._subtoppath = source
4513 repo._subtoppath = source
4514 try:
4514 try:
4515 ret = postincoming(ui, repo, modheads, opts.get('update'),
4515 ret = postincoming(ui, repo, modheads, opts.get('update'),
4516 checkout, brev)
4516 checkout, brev)
4517 except error.FilteredRepoLookupError as exc:
4517 except error.FilteredRepoLookupError as exc:
4518 msg = _('cannot update to target: %s') % exc.args[0]
4518 msg = _('cannot update to target: %s') % exc.args[0]
4519 exc.args = (msg,) + exc.args[1:]
4519 exc.args = (msg,) + exc.args[1:]
4520 raise
4520 raise
4521 finally:
4521 finally:
4522 del repo._subtoppath
4522 del repo._subtoppath
4523
4523
4524 finally:
4524 finally:
4525 other.close()
4525 other.close()
4526 return ret
4526 return ret
4527
4527
4528 @command('push',
4528 @command('push',
4529 [('f', 'force', None, _('force push')),
4529 [('f', 'force', None, _('force push')),
4530 ('r', 'rev', [],
4530 ('r', 'rev', [],
4531 _('a changeset intended to be included in the destination'),
4531 _('a changeset intended to be included in the destination'),
4532 _('REV')),
4532 _('REV')),
4533 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4533 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4534 ('b', 'branch', [],
4534 ('b', 'branch', [],
4535 _('a specific branch you would like to push'), _('BRANCH')),
4535 _('a specific branch you would like to push'), _('BRANCH')),
4536 ('', 'new-branch', False, _('allow pushing a new branch')),
4536 ('', 'new-branch', False, _('allow pushing a new branch')),
4537 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4537 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4538 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4538 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4539 ] + remoteopts,
4539 ] + remoteopts,
4540 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4540 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4541 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4541 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4542 helpbasic=True)
4542 helpbasic=True)
4543 def push(ui, repo, dest=None, **opts):
4543 def push(ui, repo, dest=None, **opts):
4544 """push changes to the specified destination
4544 """push changes to the specified destination
4545
4545
4546 Push changesets from the local repository to the specified
4546 Push changesets from the local repository to the specified
4547 destination.
4547 destination.
4548
4548
4549 This operation is symmetrical to pull: it is identical to a pull
4549 This operation is symmetrical to pull: it is identical to a pull
4550 in the destination repository from the current one.
4550 in the destination repository from the current one.
4551
4551
4552 By default, push will not allow creation of new heads at the
4552 By default, push will not allow creation of new heads at the
4553 destination, since multiple heads would make it unclear which head
4553 destination, since multiple heads would make it unclear which head
4554 to use. In this situation, it is recommended to pull and merge
4554 to use. In this situation, it is recommended to pull and merge
4555 before pushing.
4555 before pushing.
4556
4556
4557 Use --new-branch if you want to allow push to create a new named
4557 Use --new-branch if you want to allow push to create a new named
4558 branch that is not present at the destination. This allows you to
4558 branch that is not present at the destination. This allows you to
4559 only create a new branch without forcing other changes.
4559 only create a new branch without forcing other changes.
4560
4560
4561 .. note::
4561 .. note::
4562
4562
4563 Extra care should be taken with the -f/--force option,
4563 Extra care should be taken with the -f/--force option,
4564 which will push all new heads on all branches, an action which will
4564 which will push all new heads on all branches, an action which will
4565 almost always cause confusion for collaborators.
4565 almost always cause confusion for collaborators.
4566
4566
4567 If -r/--rev is used, the specified revision and all its ancestors
4567 If -r/--rev is used, the specified revision and all its ancestors
4568 will be pushed to the remote repository.
4568 will be pushed to the remote repository.
4569
4569
4570 If -B/--bookmark is used, the specified bookmarked revision, its
4570 If -B/--bookmark is used, the specified bookmarked revision, its
4571 ancestors, and the bookmark will be pushed to the remote
4571 ancestors, and the bookmark will be pushed to the remote
4572 repository. Specifying ``.`` is equivalent to specifying the active
4572 repository. Specifying ``.`` is equivalent to specifying the active
4573 bookmark's name.
4573 bookmark's name.
4574
4574
4575 Please see :hg:`help urls` for important details about ``ssh://``
4575 Please see :hg:`help urls` for important details about ``ssh://``
4576 URLs. If DESTINATION is omitted, a default path will be used.
4576 URLs. If DESTINATION is omitted, a default path will be used.
4577
4577
4578 .. container:: verbose
4578 .. container:: verbose
4579
4579
4580 The --pushvars option sends strings to the server that become
4580 The --pushvars option sends strings to the server that become
4581 environment variables prepended with ``HG_USERVAR_``. For example,
4581 environment variables prepended with ``HG_USERVAR_``. For example,
4582 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4582 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4583 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4583 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4584
4584
4585 pushvars can provide for user-overridable hooks as well as set debug
4585 pushvars can provide for user-overridable hooks as well as set debug
4586 levels. One example is having a hook that blocks commits containing
4586 levels. One example is having a hook that blocks commits containing
4587 conflict markers, but enables the user to override the hook if the file
4587 conflict markers, but enables the user to override the hook if the file
4588 is using conflict markers for testing purposes or the file format has
4588 is using conflict markers for testing purposes or the file format has
4589 strings that look like conflict markers.
4589 strings that look like conflict markers.
4590
4590
4591 By default, servers will ignore `--pushvars`. To enable it add the
4591 By default, servers will ignore `--pushvars`. To enable it add the
4592 following to your configuration file::
4592 following to your configuration file::
4593
4593
4594 [push]
4594 [push]
4595 pushvars.server = true
4595 pushvars.server = true
4596
4596
4597 Returns 0 if push was successful, 1 if nothing to push.
4597 Returns 0 if push was successful, 1 if nothing to push.
4598 """
4598 """
4599
4599
4600 opts = pycompat.byteskwargs(opts)
4600 opts = pycompat.byteskwargs(opts)
4601 if opts.get('bookmark'):
4601 if opts.get('bookmark'):
4602 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4602 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4603 for b in opts['bookmark']:
4603 for b in opts['bookmark']:
4604 # translate -B options to -r so changesets get pushed
4604 # translate -B options to -r so changesets get pushed
4605 b = repo._bookmarks.expandname(b)
4605 b = repo._bookmarks.expandname(b)
4606 if b in repo._bookmarks:
4606 if b in repo._bookmarks:
4607 opts.setdefault('rev', []).append(b)
4607 opts.setdefault('rev', []).append(b)
4608 else:
4608 else:
4609 # if we try to push a deleted bookmark, translate it to null
4609 # if we try to push a deleted bookmark, translate it to null
4610 # this lets simultaneous -r, -b options continue working
4610 # this lets simultaneous -r, -b options continue working
4611 opts.setdefault('rev', []).append("null")
4611 opts.setdefault('rev', []).append("null")
4612
4612
4613 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4613 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4614 if not path:
4614 if not path:
4615 raise error.Abort(_('default repository not configured!'),
4615 raise error.Abort(_('default repository not configured!'),
4616 hint=_("see 'hg help config.paths'"))
4616 hint=_("see 'hg help config.paths'"))
4617 dest = path.pushloc or path.loc
4617 dest = path.pushloc or path.loc
4618 branches = (path.branch, opts.get('branch') or [])
4618 branches = (path.branch, opts.get('branch') or [])
4619 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4619 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4620 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4620 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4621 other = hg.peer(repo, opts, dest)
4621 other = hg.peer(repo, opts, dest)
4622
4622
4623 if revs:
4623 if revs:
4624 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4624 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4625 if not revs:
4625 if not revs:
4626 raise error.Abort(_("specified revisions evaluate to an empty set"),
4626 raise error.Abort(_("specified revisions evaluate to an empty set"),
4627 hint=_("use different revision arguments"))
4627 hint=_("use different revision arguments"))
4628 elif path.pushrev:
4628 elif path.pushrev:
4629 # It doesn't make any sense to specify ancestor revisions. So limit
4629 # It doesn't make any sense to specify ancestor revisions. So limit
4630 # to DAG heads to make discovery simpler.
4630 # to DAG heads to make discovery simpler.
4631 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4631 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4632 revs = scmutil.revrange(repo, [expr])
4632 revs = scmutil.revrange(repo, [expr])
4633 revs = [repo[rev].node() for rev in revs]
4633 revs = [repo[rev].node() for rev in revs]
4634 if not revs:
4634 if not revs:
4635 raise error.Abort(_('default push revset for path evaluates to an '
4635 raise error.Abort(_('default push revset for path evaluates to an '
4636 'empty set'))
4636 'empty set'))
4637
4637
4638 repo._subtoppath = dest
4638 repo._subtoppath = dest
4639 try:
4639 try:
4640 # push subrepos depth-first for coherent ordering
4640 # push subrepos depth-first for coherent ordering
4641 c = repo['.']
4641 c = repo['.']
4642 subs = c.substate # only repos that are committed
4642 subs = c.substate # only repos that are committed
4643 for s in sorted(subs):
4643 for s in sorted(subs):
4644 result = c.sub(s).push(opts)
4644 result = c.sub(s).push(opts)
4645 if result == 0:
4645 if result == 0:
4646 return not result
4646 return not result
4647 finally:
4647 finally:
4648 del repo._subtoppath
4648 del repo._subtoppath
4649
4649
4650 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4650 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4651 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4651 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4652
4652
4653 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4653 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4654 newbranch=opts.get('new_branch'),
4654 newbranch=opts.get('new_branch'),
4655 bookmarks=opts.get('bookmark', ()),
4655 bookmarks=opts.get('bookmark', ()),
4656 publish=opts.get('publish'),
4656 publish=opts.get('publish'),
4657 opargs=opargs)
4657 opargs=opargs)
4658
4658
4659 result = not pushop.cgresult
4659 result = not pushop.cgresult
4660
4660
4661 if pushop.bkresult is not None:
4661 if pushop.bkresult is not None:
4662 if pushop.bkresult == 2:
4662 if pushop.bkresult == 2:
4663 result = 2
4663 result = 2
4664 elif not result and pushop.bkresult:
4664 elif not result and pushop.bkresult:
4665 result = 2
4665 result = 2
4666
4666
4667 return result
4667 return result
4668
4668
4669 @command('recover',
4669 @command('recover',
4670 [('','verify', True, "run `hg verify` after succesful recover"),
4670 [('','verify', True, "run `hg verify` after succesful recover"),
4671 ],
4671 ],
4672 helpcategory=command.CATEGORY_MAINTENANCE)
4672 helpcategory=command.CATEGORY_MAINTENANCE)
4673 def recover(ui, repo, **opts):
4673 def recover(ui, repo, **opts):
4674 """roll back an interrupted transaction
4674 """roll back an interrupted transaction
4675
4675
4676 Recover from an interrupted commit or pull.
4676 Recover from an interrupted commit or pull.
4677
4677
4678 This command tries to fix the repository status after an
4678 This command tries to fix the repository status after an
4679 interrupted operation. It should only be necessary when Mercurial
4679 interrupted operation. It should only be necessary when Mercurial
4680 suggests it.
4680 suggests it.
4681
4681
4682 Returns 0 if successful, 1 if nothing to recover or verify fails.
4682 Returns 0 if successful, 1 if nothing to recover or verify fails.
4683 """
4683 """
4684 ret = repo.recover()
4684 ret = repo.recover()
4685 if ret:
4685 if ret:
4686 if opts[r'verify']:
4686 if opts[r'verify']:
4687 return hg.verify(repo)
4687 return hg.verify(repo)
4688 else:
4688 else:
4689 msg = _("(verify step skipped, run `hg verify` to check your "
4689 msg = _("(verify step skipped, run `hg verify` to check your "
4690 "repository content)\n")
4690 "repository content)\n")
4691 ui.warn(msg)
4691 ui.warn(msg)
4692 return 0
4692 return 0
4693 return 1
4693 return 1
4694
4694
4695 @command('remove|rm',
4695 @command('remove|rm',
4696 [('A', 'after', None, _('record delete for missing files')),
4696 [('A', 'after', None, _('record delete for missing files')),
4697 ('f', 'force', None,
4697 ('f', 'force', None,
4698 _('forget added files, delete modified files')),
4698 _('forget added files, delete modified files')),
4699 ] + subrepoopts + walkopts + dryrunopts,
4699 ] + subrepoopts + walkopts + dryrunopts,
4700 _('[OPTION]... FILE...'),
4700 _('[OPTION]... FILE...'),
4701 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4701 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4702 helpbasic=True, inferrepo=True)
4702 helpbasic=True, inferrepo=True)
4703 def remove(ui, repo, *pats, **opts):
4703 def remove(ui, repo, *pats, **opts):
4704 """remove the specified files on the next commit
4704 """remove the specified files on the next commit
4705
4705
4706 Schedule the indicated files for removal from the current branch.
4706 Schedule the indicated files for removal from the current branch.
4707
4707
4708 This command schedules the files to be removed at the next commit.
4708 This command schedules the files to be removed at the next commit.
4709 To undo a remove before that, see :hg:`revert`. To undo added
4709 To undo a remove before that, see :hg:`revert`. To undo added
4710 files, see :hg:`forget`.
4710 files, see :hg:`forget`.
4711
4711
4712 .. container:: verbose
4712 .. container:: verbose
4713
4713
4714 -A/--after can be used to remove only files that have already
4714 -A/--after can be used to remove only files that have already
4715 been deleted, -f/--force can be used to force deletion, and -Af
4715 been deleted, -f/--force can be used to force deletion, and -Af
4716 can be used to remove files from the next revision without
4716 can be used to remove files from the next revision without
4717 deleting them from the working directory.
4717 deleting them from the working directory.
4718
4718
4719 The following table details the behavior of remove for different
4719 The following table details the behavior of remove for different
4720 file states (columns) and option combinations (rows). The file
4720 file states (columns) and option combinations (rows). The file
4721 states are Added [A], Clean [C], Modified [M] and Missing [!]
4721 states are Added [A], Clean [C], Modified [M] and Missing [!]
4722 (as reported by :hg:`status`). The actions are Warn, Remove
4722 (as reported by :hg:`status`). The actions are Warn, Remove
4723 (from branch) and Delete (from disk):
4723 (from branch) and Delete (from disk):
4724
4724
4725 ========= == == == ==
4725 ========= == == == ==
4726 opt/state A C M !
4726 opt/state A C M !
4727 ========= == == == ==
4727 ========= == == == ==
4728 none W RD W R
4728 none W RD W R
4729 -f R RD RD R
4729 -f R RD RD R
4730 -A W W W R
4730 -A W W W R
4731 -Af R R R R
4731 -Af R R R R
4732 ========= == == == ==
4732 ========= == == == ==
4733
4733
4734 .. note::
4734 .. note::
4735
4735
4736 :hg:`remove` never deletes files in Added [A] state from the
4736 :hg:`remove` never deletes files in Added [A] state from the
4737 working directory, not even if ``--force`` is specified.
4737 working directory, not even if ``--force`` is specified.
4738
4738
4739 Returns 0 on success, 1 if any warnings encountered.
4739 Returns 0 on success, 1 if any warnings encountered.
4740 """
4740 """
4741
4741
4742 opts = pycompat.byteskwargs(opts)
4742 opts = pycompat.byteskwargs(opts)
4743 after, force = opts.get('after'), opts.get('force')
4743 after, force = opts.get('after'), opts.get('force')
4744 dryrun = opts.get('dry_run')
4744 dryrun = opts.get('dry_run')
4745 if not pats and not after:
4745 if not pats and not after:
4746 raise error.Abort(_('no files specified'))
4746 raise error.Abort(_('no files specified'))
4747
4747
4748 m = scmutil.match(repo[None], pats, opts)
4748 m = scmutil.match(repo[None], pats, opts)
4749 subrepos = opts.get('subrepos')
4749 subrepos = opts.get('subrepos')
4750 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4750 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4751 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4751 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4752 dryrun=dryrun)
4752 dryrun=dryrun)
4753
4753
4754 @command('rename|move|mv',
4754 @command('rename|move|mv',
4755 [('A', 'after', None, _('record a rename that has already occurred')),
4755 [('A', 'after', None, _('record a rename that has already occurred')),
4756 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4756 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4757 ] + walkopts + dryrunopts,
4757 ] + walkopts + dryrunopts,
4758 _('[OPTION]... SOURCE... DEST'),
4758 _('[OPTION]... SOURCE... DEST'),
4759 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4759 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4760 def rename(ui, repo, *pats, **opts):
4760 def rename(ui, repo, *pats, **opts):
4761 """rename files; equivalent of copy + remove
4761 """rename files; equivalent of copy + remove
4762
4762
4763 Mark dest as copies of sources; mark sources for deletion. If dest
4763 Mark dest as copies of sources; mark sources for deletion. If dest
4764 is a directory, copies are put in that directory. If dest is a
4764 is a directory, copies are put in that directory. If dest is a
4765 file, there can only be one source.
4765 file, there can only be one source.
4766
4766
4767 By default, this command copies the contents of files as they
4767 By default, this command copies the contents of files as they
4768 exist in the working directory. If invoked with -A/--after, the
4768 exist in the working directory. If invoked with -A/--after, the
4769 operation is recorded, but no copying is performed.
4769 operation is recorded, but no copying is performed.
4770
4770
4771 This command takes effect at the next commit. To undo a rename
4771 This command takes effect at the next commit. To undo a rename
4772 before that, see :hg:`revert`.
4772 before that, see :hg:`revert`.
4773
4773
4774 Returns 0 on success, 1 if errors are encountered.
4774 Returns 0 on success, 1 if errors are encountered.
4775 """
4775 """
4776 opts = pycompat.byteskwargs(opts)
4776 opts = pycompat.byteskwargs(opts)
4777 with repo.wlock(False):
4777 with repo.wlock(False):
4778 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4778 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4779
4779
4780 @command('resolve',
4780 @command('resolve',
4781 [('a', 'all', None, _('select all unresolved files')),
4781 [('a', 'all', None, _('select all unresolved files')),
4782 ('l', 'list', None, _('list state of files needing merge')),
4782 ('l', 'list', None, _('list state of files needing merge')),
4783 ('m', 'mark', None, _('mark files as resolved')),
4783 ('m', 'mark', None, _('mark files as resolved')),
4784 ('u', 'unmark', None, _('mark files as unresolved')),
4784 ('u', 'unmark', None, _('mark files as unresolved')),
4785 ('n', 'no-status', None, _('hide status prefix')),
4785 ('n', 'no-status', None, _('hide status prefix')),
4786 ('', 're-merge', None, _('re-merge files'))]
4786 ('', 're-merge', None, _('re-merge files'))]
4787 + mergetoolopts + walkopts + formatteropts,
4787 + mergetoolopts + walkopts + formatteropts,
4788 _('[OPTION]... [FILE]...'),
4788 _('[OPTION]... [FILE]...'),
4789 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4789 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4790 inferrepo=True)
4790 inferrepo=True)
4791 def resolve(ui, repo, *pats, **opts):
4791 def resolve(ui, repo, *pats, **opts):
4792 """redo merges or set/view the merge status of files
4792 """redo merges or set/view the merge status of files
4793
4793
4794 Merges with unresolved conflicts are often the result of
4794 Merges with unresolved conflicts are often the result of
4795 non-interactive merging using the ``internal:merge`` configuration
4795 non-interactive merging using the ``internal:merge`` configuration
4796 setting, or a command-line merge tool like ``diff3``. The resolve
4796 setting, or a command-line merge tool like ``diff3``. The resolve
4797 command is used to manage the files involved in a merge, after
4797 command is used to manage the files involved in a merge, after
4798 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4798 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4799 working directory must have two parents). See :hg:`help
4799 working directory must have two parents). See :hg:`help
4800 merge-tools` for information on configuring merge tools.
4800 merge-tools` for information on configuring merge tools.
4801
4801
4802 The resolve command can be used in the following ways:
4802 The resolve command can be used in the following ways:
4803
4803
4804 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4804 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4805 the specified files, discarding any previous merge attempts. Re-merging
4805 the specified files, discarding any previous merge attempts. Re-merging
4806 is not performed for files already marked as resolved. Use ``--all/-a``
4806 is not performed for files already marked as resolved. Use ``--all/-a``
4807 to select all unresolved files. ``--tool`` can be used to specify
4807 to select all unresolved files. ``--tool`` can be used to specify
4808 the merge tool used for the given files. It overrides the HGMERGE
4808 the merge tool used for the given files. It overrides the HGMERGE
4809 environment variable and your configuration files. Previous file
4809 environment variable and your configuration files. Previous file
4810 contents are saved with a ``.orig`` suffix.
4810 contents are saved with a ``.orig`` suffix.
4811
4811
4812 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4812 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4813 (e.g. after having manually fixed-up the files). The default is
4813 (e.g. after having manually fixed-up the files). The default is
4814 to mark all unresolved files.
4814 to mark all unresolved files.
4815
4815
4816 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4816 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4817 default is to mark all resolved files.
4817 default is to mark all resolved files.
4818
4818
4819 - :hg:`resolve -l`: list files which had or still have conflicts.
4819 - :hg:`resolve -l`: list files which had or still have conflicts.
4820 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4820 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4821 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4821 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4822 the list. See :hg:`help filesets` for details.
4822 the list. See :hg:`help filesets` for details.
4823
4823
4824 .. note::
4824 .. note::
4825
4825
4826 Mercurial will not let you commit files with unresolved merge
4826 Mercurial will not let you commit files with unresolved merge
4827 conflicts. You must use :hg:`resolve -m ...` before you can
4827 conflicts. You must use :hg:`resolve -m ...` before you can
4828 commit after a conflicting merge.
4828 commit after a conflicting merge.
4829
4829
4830 .. container:: verbose
4830 .. container:: verbose
4831
4831
4832 Template:
4832 Template:
4833
4833
4834 The following keywords are supported in addition to the common template
4834 The following keywords are supported in addition to the common template
4835 keywords and functions. See also :hg:`help templates`.
4835 keywords and functions. See also :hg:`help templates`.
4836
4836
4837 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4837 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4838 :path: String. Repository-absolute path of the file.
4838 :path: String. Repository-absolute path of the file.
4839
4839
4840 Returns 0 on success, 1 if any files fail a resolve attempt.
4840 Returns 0 on success, 1 if any files fail a resolve attempt.
4841 """
4841 """
4842
4842
4843 opts = pycompat.byteskwargs(opts)
4843 opts = pycompat.byteskwargs(opts)
4844 confirm = ui.configbool('commands', 'resolve.confirm')
4844 confirm = ui.configbool('commands', 'resolve.confirm')
4845 flaglist = 'all mark unmark list no_status re_merge'.split()
4845 flaglist = 'all mark unmark list no_status re_merge'.split()
4846 all, mark, unmark, show, nostatus, remerge = [
4846 all, mark, unmark, show, nostatus, remerge = [
4847 opts.get(o) for o in flaglist]
4847 opts.get(o) for o in flaglist]
4848
4848
4849 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4849 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4850 if actioncount > 1:
4850 if actioncount > 1:
4851 raise error.Abort(_("too many actions specified"))
4851 raise error.Abort(_("too many actions specified"))
4852 elif (actioncount == 0
4852 elif (actioncount == 0
4853 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4853 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4854 hint = _('use --mark, --unmark, --list or --re-merge')
4854 hint = _('use --mark, --unmark, --list or --re-merge')
4855 raise error.Abort(_('no action specified'), hint=hint)
4855 raise error.Abort(_('no action specified'), hint=hint)
4856 if pats and all:
4856 if pats and all:
4857 raise error.Abort(_("can't specify --all and patterns"))
4857 raise error.Abort(_("can't specify --all and patterns"))
4858 if not (all or pats or show or mark or unmark):
4858 if not (all or pats or show or mark or unmark):
4859 raise error.Abort(_('no files or directories specified'),
4859 raise error.Abort(_('no files or directories specified'),
4860 hint=('use --all to re-merge all unresolved files'))
4860 hint=('use --all to re-merge all unresolved files'))
4861
4861
4862 if confirm:
4862 if confirm:
4863 if all:
4863 if all:
4864 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4864 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4865 b'$$ &Yes $$ &No')):
4865 b'$$ &Yes $$ &No')):
4866 raise error.Abort(_('user quit'))
4866 raise error.Abort(_('user quit'))
4867 if mark and not pats:
4867 if mark and not pats:
4868 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4868 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4869 b'$$ &Yes $$ &No')):
4869 b'$$ &Yes $$ &No')):
4870 raise error.Abort(_('user quit'))
4870 raise error.Abort(_('user quit'))
4871 if unmark and not pats:
4871 if unmark and not pats:
4872 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4872 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4873 b'$$ &Yes $$ &No')):
4873 b'$$ &Yes $$ &No')):
4874 raise error.Abort(_('user quit'))
4874 raise error.Abort(_('user quit'))
4875
4875
4876 uipathfn = scmutil.getuipathfn(repo)
4876 uipathfn = scmutil.getuipathfn(repo)
4877
4877
4878 if show:
4878 if show:
4879 ui.pager('resolve')
4879 ui.pager('resolve')
4880 fm = ui.formatter('resolve', opts)
4880 fm = ui.formatter('resolve', opts)
4881 ms = mergemod.mergestate.read(repo)
4881 ms = mergemod.mergestate.read(repo)
4882 wctx = repo[None]
4882 wctx = repo[None]
4883 m = scmutil.match(wctx, pats, opts)
4883 m = scmutil.match(wctx, pats, opts)
4884
4884
4885 # Labels and keys based on merge state. Unresolved path conflicts show
4885 # Labels and keys based on merge state. Unresolved path conflicts show
4886 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4886 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4887 # resolved conflicts.
4887 # resolved conflicts.
4888 mergestateinfo = {
4888 mergestateinfo = {
4889 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4889 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4890 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4890 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4891 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4891 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4892 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4892 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4893 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4893 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4894 'D'),
4894 'D'),
4895 }
4895 }
4896
4896
4897 for f in ms:
4897 for f in ms:
4898 if not m(f):
4898 if not m(f):
4899 continue
4899 continue
4900
4900
4901 label, key = mergestateinfo[ms[f]]
4901 label, key = mergestateinfo[ms[f]]
4902 fm.startitem()
4902 fm.startitem()
4903 fm.context(ctx=wctx)
4903 fm.context(ctx=wctx)
4904 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4904 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4905 fm.data(path=f)
4905 fm.data(path=f)
4906 fm.plain('%s\n' % uipathfn(f), label=label)
4906 fm.plain('%s\n' % uipathfn(f), label=label)
4907 fm.end()
4907 fm.end()
4908 return 0
4908 return 0
4909
4909
4910 with repo.wlock():
4910 with repo.wlock():
4911 ms = mergemod.mergestate.read(repo)
4911 ms = mergemod.mergestate.read(repo)
4912
4912
4913 if not (ms.active() or repo.dirstate.p2() != nullid):
4913 if not (ms.active() or repo.dirstate.p2() != nullid):
4914 raise error.Abort(
4914 raise error.Abort(
4915 _('resolve command not applicable when not merging'))
4915 _('resolve command not applicable when not merging'))
4916
4916
4917 wctx = repo[None]
4917 wctx = repo[None]
4918
4918
4919 if (ms.mergedriver
4919 if (ms.mergedriver
4920 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4920 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4921 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4921 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4922 ms.commit()
4922 ms.commit()
4923 # allow mark and unmark to go through
4923 # allow mark and unmark to go through
4924 if not mark and not unmark and not proceed:
4924 if not mark and not unmark and not proceed:
4925 return 1
4925 return 1
4926
4926
4927 m = scmutil.match(wctx, pats, opts)
4927 m = scmutil.match(wctx, pats, opts)
4928 ret = 0
4928 ret = 0
4929 didwork = False
4929 didwork = False
4930 runconclude = False
4930 runconclude = False
4931
4931
4932 tocomplete = []
4932 tocomplete = []
4933 hasconflictmarkers = []
4933 hasconflictmarkers = []
4934 if mark:
4934 if mark:
4935 markcheck = ui.config('commands', 'resolve.mark-check')
4935 markcheck = ui.config('commands', 'resolve.mark-check')
4936 if markcheck not in ['warn', 'abort']:
4936 if markcheck not in ['warn', 'abort']:
4937 # Treat all invalid / unrecognized values as 'none'.
4937 # Treat all invalid / unrecognized values as 'none'.
4938 markcheck = False
4938 markcheck = False
4939 for f in ms:
4939 for f in ms:
4940 if not m(f):
4940 if not m(f):
4941 continue
4941 continue
4942
4942
4943 didwork = True
4943 didwork = True
4944
4944
4945 # don't let driver-resolved files be marked, and run the conclude
4945 # don't let driver-resolved files be marked, and run the conclude
4946 # step if asked to resolve
4946 # step if asked to resolve
4947 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4947 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4948 exact = m.exact(f)
4948 exact = m.exact(f)
4949 if mark:
4949 if mark:
4950 if exact:
4950 if exact:
4951 ui.warn(_('not marking %s as it is driver-resolved\n')
4951 ui.warn(_('not marking %s as it is driver-resolved\n')
4952 % uipathfn(f))
4952 % uipathfn(f))
4953 elif unmark:
4953 elif unmark:
4954 if exact:
4954 if exact:
4955 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4955 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4956 % uipathfn(f))
4956 % uipathfn(f))
4957 else:
4957 else:
4958 runconclude = True
4958 runconclude = True
4959 continue
4959 continue
4960
4960
4961 # path conflicts must be resolved manually
4961 # path conflicts must be resolved manually
4962 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4962 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4963 mergemod.MERGE_RECORD_RESOLVED_PATH):
4963 mergemod.MERGE_RECORD_RESOLVED_PATH):
4964 if mark:
4964 if mark:
4965 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4965 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4966 elif unmark:
4966 elif unmark:
4967 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4967 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4968 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4968 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4969 ui.warn(_('%s: path conflict must be resolved manually\n')
4969 ui.warn(_('%s: path conflict must be resolved manually\n')
4970 % uipathfn(f))
4970 % uipathfn(f))
4971 continue
4971 continue
4972
4972
4973 if mark:
4973 if mark:
4974 if markcheck:
4974 if markcheck:
4975 fdata = repo.wvfs.tryread(f)
4975 fdata = repo.wvfs.tryread(f)
4976 if (filemerge.hasconflictmarkers(fdata) and
4976 if (filemerge.hasconflictmarkers(fdata) and
4977 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4977 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4978 hasconflictmarkers.append(f)
4978 hasconflictmarkers.append(f)
4979 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4979 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4980 elif unmark:
4980 elif unmark:
4981 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4981 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4982 else:
4982 else:
4983 # backup pre-resolve (merge uses .orig for its own purposes)
4983 # backup pre-resolve (merge uses .orig for its own purposes)
4984 a = repo.wjoin(f)
4984 a = repo.wjoin(f)
4985 try:
4985 try:
4986 util.copyfile(a, a + ".resolve")
4986 util.copyfile(a, a + ".resolve")
4987 except (IOError, OSError) as inst:
4987 except (IOError, OSError) as inst:
4988 if inst.errno != errno.ENOENT:
4988 if inst.errno != errno.ENOENT:
4989 raise
4989 raise
4990
4990
4991 try:
4991 try:
4992 # preresolve file
4992 # preresolve file
4993 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4993 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4994 with ui.configoverride(overrides, 'resolve'):
4994 with ui.configoverride(overrides, 'resolve'):
4995 complete, r = ms.preresolve(f, wctx)
4995 complete, r = ms.preresolve(f, wctx)
4996 if not complete:
4996 if not complete:
4997 tocomplete.append(f)
4997 tocomplete.append(f)
4998 elif r:
4998 elif r:
4999 ret = 1
4999 ret = 1
5000 finally:
5000 finally:
5001 ms.commit()
5001 ms.commit()
5002
5002
5003 # replace filemerge's .orig file with our resolve file, but only
5003 # replace filemerge's .orig file with our resolve file, but only
5004 # for merges that are complete
5004 # for merges that are complete
5005 if complete:
5005 if complete:
5006 try:
5006 try:
5007 util.rename(a + ".resolve",
5007 util.rename(a + ".resolve",
5008 scmutil.backuppath(ui, repo, f))
5008 scmutil.backuppath(ui, repo, f))
5009 except OSError as inst:
5009 except OSError as inst:
5010 if inst.errno != errno.ENOENT:
5010 if inst.errno != errno.ENOENT:
5011 raise
5011 raise
5012
5012
5013 if hasconflictmarkers:
5013 if hasconflictmarkers:
5014 ui.warn(_('warning: the following files still have conflict '
5014 ui.warn(_('warning: the following files still have conflict '
5015 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
5015 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
5016 for f in hasconflictmarkers))
5016 for f in hasconflictmarkers))
5017 if markcheck == 'abort' and not all and not pats:
5017 if markcheck == 'abort' and not all and not pats:
5018 raise error.Abort(_('conflict markers detected'),
5018 raise error.Abort(_('conflict markers detected'),
5019 hint=_('use --all to mark anyway'))
5019 hint=_('use --all to mark anyway'))
5020
5020
5021 for f in tocomplete:
5021 for f in tocomplete:
5022 try:
5022 try:
5023 # resolve file
5023 # resolve file
5024 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5024 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5025 with ui.configoverride(overrides, 'resolve'):
5025 with ui.configoverride(overrides, 'resolve'):
5026 r = ms.resolve(f, wctx)
5026 r = ms.resolve(f, wctx)
5027 if r:
5027 if r:
5028 ret = 1
5028 ret = 1
5029 finally:
5029 finally:
5030 ms.commit()
5030 ms.commit()
5031
5031
5032 # replace filemerge's .orig file with our resolve file
5032 # replace filemerge's .orig file with our resolve file
5033 a = repo.wjoin(f)
5033 a = repo.wjoin(f)
5034 try:
5034 try:
5035 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5035 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5036 except OSError as inst:
5036 except OSError as inst:
5037 if inst.errno != errno.ENOENT:
5037 if inst.errno != errno.ENOENT:
5038 raise
5038 raise
5039
5039
5040 ms.commit()
5040 ms.commit()
5041 ms.recordactions()
5041 ms.recordactions()
5042
5042
5043 if not didwork and pats:
5043 if not didwork and pats:
5044 hint = None
5044 hint = None
5045 if not any([p for p in pats if p.find(':') >= 0]):
5045 if not any([p for p in pats if p.find(':') >= 0]):
5046 pats = ['path:%s' % p for p in pats]
5046 pats = ['path:%s' % p for p in pats]
5047 m = scmutil.match(wctx, pats, opts)
5047 m = scmutil.match(wctx, pats, opts)
5048 for f in ms:
5048 for f in ms:
5049 if not m(f):
5049 if not m(f):
5050 continue
5050 continue
5051 def flag(o):
5051 def flag(o):
5052 if o == 're_merge':
5052 if o == 're_merge':
5053 return '--re-merge '
5053 return '--re-merge '
5054 return '-%s ' % o[0:1]
5054 return '-%s ' % o[0:1]
5055 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5055 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5056 hint = _("(try: hg resolve %s%s)\n") % (
5056 hint = _("(try: hg resolve %s%s)\n") % (
5057 flags,
5057 flags,
5058 ' '.join(pats))
5058 ' '.join(pats))
5059 break
5059 break
5060 ui.warn(_("arguments do not match paths that need resolving\n"))
5060 ui.warn(_("arguments do not match paths that need resolving\n"))
5061 if hint:
5061 if hint:
5062 ui.warn(hint)
5062 ui.warn(hint)
5063 elif ms.mergedriver and ms.mdstate() != 's':
5063 elif ms.mergedriver and ms.mdstate() != 's':
5064 # run conclude step when either a driver-resolved file is requested
5064 # run conclude step when either a driver-resolved file is requested
5065 # or there are no driver-resolved files
5065 # or there are no driver-resolved files
5066 # we can't use 'ret' to determine whether any files are unresolved
5066 # we can't use 'ret' to determine whether any files are unresolved
5067 # because we might not have tried to resolve some
5067 # because we might not have tried to resolve some
5068 if ((runconclude or not list(ms.driverresolved()))
5068 if ((runconclude or not list(ms.driverresolved()))
5069 and not list(ms.unresolved())):
5069 and not list(ms.unresolved())):
5070 proceed = mergemod.driverconclude(repo, ms, wctx)
5070 proceed = mergemod.driverconclude(repo, ms, wctx)
5071 ms.commit()
5071 ms.commit()
5072 if not proceed:
5072 if not proceed:
5073 return 1
5073 return 1
5074
5074
5075 # Nudge users into finishing an unfinished operation
5075 # Nudge users into finishing an unfinished operation
5076 unresolvedf = list(ms.unresolved())
5076 unresolvedf = list(ms.unresolved())
5077 driverresolvedf = list(ms.driverresolved())
5077 driverresolvedf = list(ms.driverresolved())
5078 if not unresolvedf and not driverresolvedf:
5078 if not unresolvedf and not driverresolvedf:
5079 ui.status(_('(no more unresolved files)\n'))
5079 ui.status(_('(no more unresolved files)\n'))
5080 cmdutil.checkafterresolved(repo)
5080 cmdutil.checkafterresolved(repo)
5081 elif not unresolvedf:
5081 elif not unresolvedf:
5082 ui.status(_('(no more unresolved files -- '
5082 ui.status(_('(no more unresolved files -- '
5083 'run "hg resolve --all" to conclude)\n'))
5083 'run "hg resolve --all" to conclude)\n'))
5084
5084
5085 return ret
5085 return ret
5086
5086
5087 @command('revert',
5087 @command('revert',
5088 [('a', 'all', None, _('revert all changes when no arguments given')),
5088 [('a', 'all', None, _('revert all changes when no arguments given')),
5089 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5089 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5090 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5090 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5091 ('C', 'no-backup', None, _('do not save backup copies of files')),
5091 ('C', 'no-backup', None, _('do not save backup copies of files')),
5092 ('i', 'interactive', None, _('interactively select the changes')),
5092 ('i', 'interactive', None, _('interactively select the changes')),
5093 ] + walkopts + dryrunopts,
5093 ] + walkopts + dryrunopts,
5094 _('[OPTION]... [-r REV] [NAME]...'),
5094 _('[OPTION]... [-r REV] [NAME]...'),
5095 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5095 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5096 def revert(ui, repo, *pats, **opts):
5096 def revert(ui, repo, *pats, **opts):
5097 """restore files to their checkout state
5097 """restore files to their checkout state
5098
5098
5099 .. note::
5099 .. note::
5100
5100
5101 To check out earlier revisions, you should use :hg:`update REV`.
5101 To check out earlier revisions, you should use :hg:`update REV`.
5102 To cancel an uncommitted merge (and lose your changes),
5102 To cancel an uncommitted merge (and lose your changes),
5103 use :hg:`merge --abort`.
5103 use :hg:`merge --abort`.
5104
5104
5105 With no revision specified, revert the specified files or directories
5105 With no revision specified, revert the specified files or directories
5106 to the contents they had in the parent of the working directory.
5106 to the contents they had in the parent of the working directory.
5107 This restores the contents of files to an unmodified
5107 This restores the contents of files to an unmodified
5108 state and unschedules adds, removes, copies, and renames. If the
5108 state and unschedules adds, removes, copies, and renames. If the
5109 working directory has two parents, you must explicitly specify a
5109 working directory has two parents, you must explicitly specify a
5110 revision.
5110 revision.
5111
5111
5112 Using the -r/--rev or -d/--date options, revert the given files or
5112 Using the -r/--rev or -d/--date options, revert the given files or
5113 directories to their states as of a specific revision. Because
5113 directories to their states as of a specific revision. Because
5114 revert does not change the working directory parents, this will
5114 revert does not change the working directory parents, this will
5115 cause these files to appear modified. This can be helpful to "back
5115 cause these files to appear modified. This can be helpful to "back
5116 out" some or all of an earlier change. See :hg:`backout` for a
5116 out" some or all of an earlier change. See :hg:`backout` for a
5117 related method.
5117 related method.
5118
5118
5119 Modified files are saved with a .orig suffix before reverting.
5119 Modified files are saved with a .orig suffix before reverting.
5120 To disable these backups, use --no-backup. It is possible to store
5120 To disable these backups, use --no-backup. It is possible to store
5121 the backup files in a custom directory relative to the root of the
5121 the backup files in a custom directory relative to the root of the
5122 repository by setting the ``ui.origbackuppath`` configuration
5122 repository by setting the ``ui.origbackuppath`` configuration
5123 option.
5123 option.
5124
5124
5125 See :hg:`help dates` for a list of formats valid for -d/--date.
5125 See :hg:`help dates` for a list of formats valid for -d/--date.
5126
5126
5127 See :hg:`help backout` for a way to reverse the effect of an
5127 See :hg:`help backout` for a way to reverse the effect of an
5128 earlier changeset.
5128 earlier changeset.
5129
5129
5130 Returns 0 on success.
5130 Returns 0 on success.
5131 """
5131 """
5132
5132
5133 opts = pycompat.byteskwargs(opts)
5133 opts = pycompat.byteskwargs(opts)
5134 if opts.get("date"):
5134 if opts.get("date"):
5135 if opts.get("rev"):
5135 if opts.get("rev"):
5136 raise error.Abort(_("you can't specify a revision and a date"))
5136 raise error.Abort(_("you can't specify a revision and a date"))
5137 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5137 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5138
5138
5139 parent, p2 = repo.dirstate.parents()
5139 parent, p2 = repo.dirstate.parents()
5140 if not opts.get('rev') and p2 != nullid:
5140 if not opts.get('rev') and p2 != nullid:
5141 # revert after merge is a trap for new users (issue2915)
5141 # revert after merge is a trap for new users (issue2915)
5142 raise error.Abort(_('uncommitted merge with no revision specified'),
5142 raise error.Abort(_('uncommitted merge with no revision specified'),
5143 hint=_("use 'hg update' or see 'hg help revert'"))
5143 hint=_("use 'hg update' or see 'hg help revert'"))
5144
5144
5145 rev = opts.get('rev')
5145 rev = opts.get('rev')
5146 if rev:
5146 if rev:
5147 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5147 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5148 ctx = scmutil.revsingle(repo, rev)
5148 ctx = scmutil.revsingle(repo, rev)
5149
5149
5150 if (not (pats or opts.get('include') or opts.get('exclude') or
5150 if (not (pats or opts.get('include') or opts.get('exclude') or
5151 opts.get('all') or opts.get('interactive'))):
5151 opts.get('all') or opts.get('interactive'))):
5152 msg = _("no files or directories specified")
5152 msg = _("no files or directories specified")
5153 if p2 != nullid:
5153 if p2 != nullid:
5154 hint = _("uncommitted merge, use --all to discard all changes,"
5154 hint = _("uncommitted merge, use --all to discard all changes,"
5155 " or 'hg update -C .' to abort the merge")
5155 " or 'hg update -C .' to abort the merge")
5156 raise error.Abort(msg, hint=hint)
5156 raise error.Abort(msg, hint=hint)
5157 dirty = any(repo.status())
5157 dirty = any(repo.status())
5158 node = ctx.node()
5158 node = ctx.node()
5159 if node != parent:
5159 if node != parent:
5160 if dirty:
5160 if dirty:
5161 hint = _("uncommitted changes, use --all to discard all"
5161 hint = _("uncommitted changes, use --all to discard all"
5162 " changes, or 'hg update %d' to update") % ctx.rev()
5162 " changes, or 'hg update %d' to update") % ctx.rev()
5163 else:
5163 else:
5164 hint = _("use --all to revert all files,"
5164 hint = _("use --all to revert all files,"
5165 " or 'hg update %d' to update") % ctx.rev()
5165 " or 'hg update %d' to update") % ctx.rev()
5166 elif dirty:
5166 elif dirty:
5167 hint = _("uncommitted changes, use --all to discard all changes")
5167 hint = _("uncommitted changes, use --all to discard all changes")
5168 else:
5168 else:
5169 hint = _("use --all to revert all files")
5169 hint = _("use --all to revert all files")
5170 raise error.Abort(msg, hint=hint)
5170 raise error.Abort(msg, hint=hint)
5171
5171
5172 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5172 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5173 **pycompat.strkwargs(opts))
5173 **pycompat.strkwargs(opts))
5174
5174
5175 @command(
5175 @command(
5176 'rollback',
5176 'rollback',
5177 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5177 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5178 helpcategory=command.CATEGORY_MAINTENANCE)
5178 helpcategory=command.CATEGORY_MAINTENANCE)
5179 def rollback(ui, repo, **opts):
5179 def rollback(ui, repo, **opts):
5180 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5180 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5181
5181
5182 Please use :hg:`commit --amend` instead of rollback to correct
5182 Please use :hg:`commit --amend` instead of rollback to correct
5183 mistakes in the last commit.
5183 mistakes in the last commit.
5184
5184
5185 This command should be used with care. There is only one level of
5185 This command should be used with care. There is only one level of
5186 rollback, and there is no way to undo a rollback. It will also
5186 rollback, and there is no way to undo a rollback. It will also
5187 restore the dirstate at the time of the last transaction, losing
5187 restore the dirstate at the time of the last transaction, losing
5188 any dirstate changes since that time. This command does not alter
5188 any dirstate changes since that time. This command does not alter
5189 the working directory.
5189 the working directory.
5190
5190
5191 Transactions are used to encapsulate the effects of all commands
5191 Transactions are used to encapsulate the effects of all commands
5192 that create new changesets or propagate existing changesets into a
5192 that create new changesets or propagate existing changesets into a
5193 repository.
5193 repository.
5194
5194
5195 .. container:: verbose
5195 .. container:: verbose
5196
5196
5197 For example, the following commands are transactional, and their
5197 For example, the following commands are transactional, and their
5198 effects can be rolled back:
5198 effects can be rolled back:
5199
5199
5200 - commit
5200 - commit
5201 - import
5201 - import
5202 - pull
5202 - pull
5203 - push (with this repository as the destination)
5203 - push (with this repository as the destination)
5204 - unbundle
5204 - unbundle
5205
5205
5206 To avoid permanent data loss, rollback will refuse to rollback a
5206 To avoid permanent data loss, rollback will refuse to rollback a
5207 commit transaction if it isn't checked out. Use --force to
5207 commit transaction if it isn't checked out. Use --force to
5208 override this protection.
5208 override this protection.
5209
5209
5210 The rollback command can be entirely disabled by setting the
5210 The rollback command can be entirely disabled by setting the
5211 ``ui.rollback`` configuration setting to false. If you're here
5211 ``ui.rollback`` configuration setting to false. If you're here
5212 because you want to use rollback and it's disabled, you can
5212 because you want to use rollback and it's disabled, you can
5213 re-enable the command by setting ``ui.rollback`` to true.
5213 re-enable the command by setting ``ui.rollback`` to true.
5214
5214
5215 This command is not intended for use on public repositories. Once
5215 This command is not intended for use on public repositories. Once
5216 changes are visible for pull by other users, rolling a transaction
5216 changes are visible for pull by other users, rolling a transaction
5217 back locally is ineffective (someone else may already have pulled
5217 back locally is ineffective (someone else may already have pulled
5218 the changes). Furthermore, a race is possible with readers of the
5218 the changes). Furthermore, a race is possible with readers of the
5219 repository; for example an in-progress pull from the repository
5219 repository; for example an in-progress pull from the repository
5220 may fail if a rollback is performed.
5220 may fail if a rollback is performed.
5221
5221
5222 Returns 0 on success, 1 if no rollback data is available.
5222 Returns 0 on success, 1 if no rollback data is available.
5223 """
5223 """
5224 if not ui.configbool('ui', 'rollback'):
5224 if not ui.configbool('ui', 'rollback'):
5225 raise error.Abort(_('rollback is disabled because it is unsafe'),
5225 raise error.Abort(_('rollback is disabled because it is unsafe'),
5226 hint=('see `hg help -v rollback` for information'))
5226 hint=('see `hg help -v rollback` for information'))
5227 return repo.rollback(dryrun=opts.get(r'dry_run'),
5227 return repo.rollback(dryrun=opts.get(r'dry_run'),
5228 force=opts.get(r'force'))
5228 force=opts.get(r'force'))
5229
5229
5230 @command(
5230 @command(
5231 'root', [] + formatteropts, intents={INTENT_READONLY},
5231 'root', [] + formatteropts, intents={INTENT_READONLY},
5232 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5232 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5233 def root(ui, repo, **opts):
5233 def root(ui, repo, **opts):
5234 """print the root (top) of the current working directory
5234 """print the root (top) of the current working directory
5235
5235
5236 Print the root directory of the current repository.
5236 Print the root directory of the current repository.
5237
5237
5238 .. container:: verbose
5238 .. container:: verbose
5239
5239
5240 Template:
5240 Template:
5241
5241
5242 The following keywords are supported in addition to the common template
5242 The following keywords are supported in addition to the common template
5243 keywords and functions. See also :hg:`help templates`.
5243 keywords and functions. See also :hg:`help templates`.
5244
5244
5245 :hgpath: String. Path to the .hg directory.
5245 :hgpath: String. Path to the .hg directory.
5246 :storepath: String. Path to the directory holding versioned data.
5246 :storepath: String. Path to the directory holding versioned data.
5247
5247
5248 Returns 0 on success.
5248 Returns 0 on success.
5249 """
5249 """
5250 opts = pycompat.byteskwargs(opts)
5250 opts = pycompat.byteskwargs(opts)
5251 with ui.formatter('root', opts) as fm:
5251 with ui.formatter('root', opts) as fm:
5252 fm.startitem()
5252 fm.startitem()
5253 fm.write('reporoot', '%s\n', repo.root)
5253 fm.write('reporoot', '%s\n', repo.root)
5254 fm.data(hgpath=repo.path, storepath=repo.spath)
5254 fm.data(hgpath=repo.path, storepath=repo.spath)
5255
5255
5256 @command('serve',
5256 @command('serve',
5257 [('A', 'accesslog', '', _('name of access log file to write to'),
5257 [('A', 'accesslog', '', _('name of access log file to write to'),
5258 _('FILE')),
5258 _('FILE')),
5259 ('d', 'daemon', None, _('run server in background')),
5259 ('d', 'daemon', None, _('run server in background')),
5260 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5260 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5261 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5261 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5262 # use string type, then we can check if something was passed
5262 # use string type, then we can check if something was passed
5263 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5263 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5264 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5264 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5265 _('ADDR')),
5265 _('ADDR')),
5266 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5266 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5267 _('PREFIX')),
5267 _('PREFIX')),
5268 ('n', 'name', '',
5268 ('n', 'name', '',
5269 _('name to show in web pages (default: working directory)'), _('NAME')),
5269 _('name to show in web pages (default: working directory)'), _('NAME')),
5270 ('', 'web-conf', '',
5270 ('', 'web-conf', '',
5271 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5271 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5272 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5272 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5273 _('FILE')),
5273 _('FILE')),
5274 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5274 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5275 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5275 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5276 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5276 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5277 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5277 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5278 ('', 'style', '', _('template style to use'), _('STYLE')),
5278 ('', 'style', '', _('template style to use'), _('STYLE')),
5279 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5279 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5280 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5280 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5281 ('', 'print-url', None, _('start and print only the URL'))]
5281 ('', 'print-url', None, _('start and print only the URL'))]
5282 + subrepoopts,
5282 + subrepoopts,
5283 _('[OPTION]...'),
5283 _('[OPTION]...'),
5284 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5284 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5285 helpbasic=True, optionalrepo=True)
5285 helpbasic=True, optionalrepo=True)
5286 def serve(ui, repo, **opts):
5286 def serve(ui, repo, **opts):
5287 """start stand-alone webserver
5287 """start stand-alone webserver
5288
5288
5289 Start a local HTTP repository browser and pull server. You can use
5289 Start a local HTTP repository browser and pull server. You can use
5290 this for ad-hoc sharing and browsing of repositories. It is
5290 this for ad-hoc sharing and browsing of repositories. It is
5291 recommended to use a real web server to serve a repository for
5291 recommended to use a real web server to serve a repository for
5292 longer periods of time.
5292 longer periods of time.
5293
5293
5294 Please note that the server does not implement access control.
5294 Please note that the server does not implement access control.
5295 This means that, by default, anybody can read from the server and
5295 This means that, by default, anybody can read from the server and
5296 nobody can write to it by default. Set the ``web.allow-push``
5296 nobody can write to it by default. Set the ``web.allow-push``
5297 option to ``*`` to allow everybody to push to the server. You
5297 option to ``*`` to allow everybody to push to the server. You
5298 should use a real web server if you need to authenticate users.
5298 should use a real web server if you need to authenticate users.
5299
5299
5300 By default, the server logs accesses to stdout and errors to
5300 By default, the server logs accesses to stdout and errors to
5301 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5301 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5302 files.
5302 files.
5303
5303
5304 To have the server choose a free port number to listen on, specify
5304 To have the server choose a free port number to listen on, specify
5305 a port number of 0; in this case, the server will print the port
5305 a port number of 0; in this case, the server will print the port
5306 number it uses.
5306 number it uses.
5307
5307
5308 Returns 0 on success.
5308 Returns 0 on success.
5309 """
5309 """
5310
5310
5311 opts = pycompat.byteskwargs(opts)
5311 opts = pycompat.byteskwargs(opts)
5312 if opts["stdio"] and opts["cmdserver"]:
5312 if opts["stdio"] and opts["cmdserver"]:
5313 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5313 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5314 if opts["print_url"] and ui.verbose:
5314 if opts["print_url"] and ui.verbose:
5315 raise error.Abort(_("cannot use --print-url with --verbose"))
5315 raise error.Abort(_("cannot use --print-url with --verbose"))
5316
5316
5317 if opts["stdio"]:
5317 if opts["stdio"]:
5318 if repo is None:
5318 if repo is None:
5319 raise error.RepoError(_("there is no Mercurial repository here"
5319 raise error.RepoError(_("there is no Mercurial repository here"
5320 " (.hg not found)"))
5320 " (.hg not found)"))
5321 s = wireprotoserver.sshserver(ui, repo)
5321 s = wireprotoserver.sshserver(ui, repo)
5322 s.serve_forever()
5322 s.serve_forever()
5323
5323
5324 service = server.createservice(ui, repo, opts)
5324 service = server.createservice(ui, repo, opts)
5325 return server.runservice(opts, initfn=service.init, runfn=service.run)
5325 return server.runservice(opts, initfn=service.init, runfn=service.run)
5326
5326
5327 _NOTTERSE = 'nothing'
5327 _NOTTERSE = 'nothing'
5328
5328
5329 @command('status|st',
5329 @command('status|st',
5330 [('A', 'all', None, _('show status of all files')),
5330 [('A', 'all', None, _('show status of all files')),
5331 ('m', 'modified', None, _('show only modified files')),
5331 ('m', 'modified', None, _('show only modified files')),
5332 ('a', 'added', None, _('show only added files')),
5332 ('a', 'added', None, _('show only added files')),
5333 ('r', 'removed', None, _('show only removed files')),
5333 ('r', 'removed', None, _('show only removed files')),
5334 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5334 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5335 ('c', 'clean', None, _('show only files without changes')),
5335 ('c', 'clean', None, _('show only files without changes')),
5336 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5336 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5337 ('i', 'ignored', None, _('show only ignored files')),
5337 ('i', 'ignored', None, _('show only ignored files')),
5338 ('n', 'no-status', None, _('hide status prefix')),
5338 ('n', 'no-status', None, _('hide status prefix')),
5339 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5339 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5340 ('C', 'copies', None, _('show source of copied files')),
5340 ('C', 'copies', None, _('show source of copied files')),
5341 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5341 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5342 ('', 'rev', [], _('show difference from revision'), _('REV')),
5342 ('', 'rev', [], _('show difference from revision'), _('REV')),
5343 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5343 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5344 ] + walkopts + subrepoopts + formatteropts,
5344 ] + walkopts + subrepoopts + formatteropts,
5345 _('[OPTION]... [FILE]...'),
5345 _('[OPTION]... [FILE]...'),
5346 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5346 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5347 helpbasic=True, inferrepo=True,
5347 helpbasic=True, inferrepo=True,
5348 intents={INTENT_READONLY})
5348 intents={INTENT_READONLY})
5349 def status(ui, repo, *pats, **opts):
5349 def status(ui, repo, *pats, **opts):
5350 """show changed files in the working directory
5350 """show changed files in the working directory
5351
5351
5352 Show status of files in the repository. If names are given, only
5352 Show status of files in the repository. If names are given, only
5353 files that match are shown. Files that are clean or ignored or
5353 files that match are shown. Files that are clean or ignored or
5354 the source of a copy/move operation, are not listed unless
5354 the source of a copy/move operation, are not listed unless
5355 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5355 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5356 Unless options described with "show only ..." are given, the
5356 Unless options described with "show only ..." are given, the
5357 options -mardu are used.
5357 options -mardu are used.
5358
5358
5359 Option -q/--quiet hides untracked (unknown and ignored) files
5359 Option -q/--quiet hides untracked (unknown and ignored) files
5360 unless explicitly requested with -u/--unknown or -i/--ignored.
5360 unless explicitly requested with -u/--unknown or -i/--ignored.
5361
5361
5362 .. note::
5362 .. note::
5363
5363
5364 :hg:`status` may appear to disagree with diff if permissions have
5364 :hg:`status` may appear to disagree with diff if permissions have
5365 changed or a merge has occurred. The standard diff format does
5365 changed or a merge has occurred. The standard diff format does
5366 not report permission changes and diff only reports changes
5366 not report permission changes and diff only reports changes
5367 relative to one merge parent.
5367 relative to one merge parent.
5368
5368
5369 If one revision is given, it is used as the base revision.
5369 If one revision is given, it is used as the base revision.
5370 If two revisions are given, the differences between them are
5370 If two revisions are given, the differences between them are
5371 shown. The --change option can also be used as a shortcut to list
5371 shown. The --change option can also be used as a shortcut to list
5372 the changed files of a revision from its first parent.
5372 the changed files of a revision from its first parent.
5373
5373
5374 The codes used to show the status of files are::
5374 The codes used to show the status of files are::
5375
5375
5376 M = modified
5376 M = modified
5377 A = added
5377 A = added
5378 R = removed
5378 R = removed
5379 C = clean
5379 C = clean
5380 ! = missing (deleted by non-hg command, but still tracked)
5380 ! = missing (deleted by non-hg command, but still tracked)
5381 ? = not tracked
5381 ? = not tracked
5382 I = ignored
5382 I = ignored
5383 = origin of the previous file (with --copies)
5383 = origin of the previous file (with --copies)
5384
5384
5385 .. container:: verbose
5385 .. container:: verbose
5386
5386
5387 The -t/--terse option abbreviates the output by showing only the directory
5387 The -t/--terse option abbreviates the output by showing only the directory
5388 name if all the files in it share the same status. The option takes an
5388 name if all the files in it share the same status. The option takes an
5389 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5389 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5390 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5390 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5391 for 'ignored' and 'c' for clean.
5391 for 'ignored' and 'c' for clean.
5392
5392
5393 It abbreviates only those statuses which are passed. Note that clean and
5393 It abbreviates only those statuses which are passed. Note that clean and
5394 ignored files are not displayed with '--terse ic' unless the -c/--clean
5394 ignored files are not displayed with '--terse ic' unless the -c/--clean
5395 and -i/--ignored options are also used.
5395 and -i/--ignored options are also used.
5396
5396
5397 The -v/--verbose option shows information when the repository is in an
5397 The -v/--verbose option shows information when the repository is in an
5398 unfinished merge, shelve, rebase state etc. You can have this behavior
5398 unfinished merge, shelve, rebase state etc. You can have this behavior
5399 turned on by default by enabling the ``commands.status.verbose`` option.
5399 turned on by default by enabling the ``commands.status.verbose`` option.
5400
5400
5401 You can skip displaying some of these states by setting
5401 You can skip displaying some of these states by setting
5402 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5402 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5403 'histedit', 'merge', 'rebase', or 'unshelve'.
5403 'histedit', 'merge', 'rebase', or 'unshelve'.
5404
5404
5405 Template:
5405 Template:
5406
5406
5407 The following keywords are supported in addition to the common template
5407 The following keywords are supported in addition to the common template
5408 keywords and functions. See also :hg:`help templates`.
5408 keywords and functions. See also :hg:`help templates`.
5409
5409
5410 :path: String. Repository-absolute path of the file.
5410 :path: String. Repository-absolute path of the file.
5411 :source: String. Repository-absolute path of the file originated from.
5411 :source: String. Repository-absolute path of the file originated from.
5412 Available if ``--copies`` is specified.
5412 Available if ``--copies`` is specified.
5413 :status: String. Character denoting file's status.
5413 :status: String. Character denoting file's status.
5414
5414
5415 Examples:
5415 Examples:
5416
5416
5417 - show changes in the working directory relative to a
5417 - show changes in the working directory relative to a
5418 changeset::
5418 changeset::
5419
5419
5420 hg status --rev 9353
5420 hg status --rev 9353
5421
5421
5422 - show changes in the working directory relative to the
5422 - show changes in the working directory relative to the
5423 current directory (see :hg:`help patterns` for more information)::
5423 current directory (see :hg:`help patterns` for more information)::
5424
5424
5425 hg status re:
5425 hg status re:
5426
5426
5427 - show all changes including copies in an existing changeset::
5427 - show all changes including copies in an existing changeset::
5428
5428
5429 hg status --copies --change 9353
5429 hg status --copies --change 9353
5430
5430
5431 - get a NUL separated list of added files, suitable for xargs::
5431 - get a NUL separated list of added files, suitable for xargs::
5432
5432
5433 hg status -an0
5433 hg status -an0
5434
5434
5435 - show more information about the repository status, abbreviating
5435 - show more information about the repository status, abbreviating
5436 added, removed, modified, deleted, and untracked paths::
5436 added, removed, modified, deleted, and untracked paths::
5437
5437
5438 hg status -v -t mardu
5438 hg status -v -t mardu
5439
5439
5440 Returns 0 on success.
5440 Returns 0 on success.
5441
5441
5442 """
5442 """
5443
5443
5444 opts = pycompat.byteskwargs(opts)
5444 opts = pycompat.byteskwargs(opts)
5445 revs = opts.get('rev')
5445 revs = opts.get('rev')
5446 change = opts.get('change')
5446 change = opts.get('change')
5447 terse = opts.get('terse')
5447 terse = opts.get('terse')
5448 if terse is _NOTTERSE:
5448 if terse is _NOTTERSE:
5449 if revs:
5449 if revs:
5450 terse = ''
5450 terse = ''
5451 else:
5451 else:
5452 terse = ui.config('commands', 'status.terse')
5452 terse = ui.config('commands', 'status.terse')
5453
5453
5454 if revs and change:
5454 if revs and change:
5455 msg = _('cannot specify --rev and --change at the same time')
5455 msg = _('cannot specify --rev and --change at the same time')
5456 raise error.Abort(msg)
5456 raise error.Abort(msg)
5457 elif revs and terse:
5457 elif revs and terse:
5458 msg = _('cannot use --terse with --rev')
5458 msg = _('cannot use --terse with --rev')
5459 raise error.Abort(msg)
5459 raise error.Abort(msg)
5460 elif change:
5460 elif change:
5461 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5461 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5462 ctx2 = scmutil.revsingle(repo, change, None)
5462 ctx2 = scmutil.revsingle(repo, change, None)
5463 ctx1 = ctx2.p1()
5463 ctx1 = ctx2.p1()
5464 else:
5464 else:
5465 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5465 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5466 ctx1, ctx2 = scmutil.revpair(repo, revs)
5466 ctx1, ctx2 = scmutil.revpair(repo, revs)
5467
5467
5468 forcerelativevalue = None
5468 forcerelativevalue = None
5469 if ui.hasconfig('commands', 'status.relative'):
5469 if ui.hasconfig('commands', 'status.relative'):
5470 forcerelativevalue = ui.configbool('commands', 'status.relative')
5470 forcerelativevalue = ui.configbool('commands', 'status.relative')
5471 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5471 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5472 forcerelativevalue=forcerelativevalue)
5472 forcerelativevalue=forcerelativevalue)
5473
5473
5474 if opts.get('print0'):
5474 if opts.get('print0'):
5475 end = '\0'
5475 end = '\0'
5476 else:
5476 else:
5477 end = '\n'
5477 end = '\n'
5478 copy = {}
5478 copy = {}
5479 states = 'modified added removed deleted unknown ignored clean'.split()
5479 states = 'modified added removed deleted unknown ignored clean'.split()
5480 show = [k for k in states if opts.get(k)]
5480 show = [k for k in states if opts.get(k)]
5481 if opts.get('all'):
5481 if opts.get('all'):
5482 show += ui.quiet and (states[:4] + ['clean']) or states
5482 show += ui.quiet and (states[:4] + ['clean']) or states
5483
5483
5484 if not show:
5484 if not show:
5485 if ui.quiet:
5485 if ui.quiet:
5486 show = states[:4]
5486 show = states[:4]
5487 else:
5487 else:
5488 show = states[:5]
5488 show = states[:5]
5489
5489
5490 m = scmutil.match(ctx2, pats, opts)
5490 m = scmutil.match(ctx2, pats, opts)
5491 if terse:
5491 if terse:
5492 # we need to compute clean and unknown to terse
5492 # we need to compute clean and unknown to terse
5493 stat = repo.status(ctx1.node(), ctx2.node(), m,
5493 stat = repo.status(ctx1.node(), ctx2.node(), m,
5494 'ignored' in show or 'i' in terse,
5494 'ignored' in show or 'i' in terse,
5495 clean=True, unknown=True,
5495 clean=True, unknown=True,
5496 listsubrepos=opts.get('subrepos'))
5496 listsubrepos=opts.get('subrepos'))
5497
5497
5498 stat = cmdutil.tersedir(stat, terse)
5498 stat = cmdutil.tersedir(stat, terse)
5499 else:
5499 else:
5500 stat = repo.status(ctx1.node(), ctx2.node(), m,
5500 stat = repo.status(ctx1.node(), ctx2.node(), m,
5501 'ignored' in show, 'clean' in show,
5501 'ignored' in show, 'clean' in show,
5502 'unknown' in show, opts.get('subrepos'))
5502 'unknown' in show, opts.get('subrepos'))
5503
5503
5504 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5504 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5505
5505
5506 if (opts.get('all') or opts.get('copies')
5506 if (opts.get('all') or opts.get('copies')
5507 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5507 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5508 copy = copies.pathcopies(ctx1, ctx2, m)
5508 copy = copies.pathcopies(ctx1, ctx2, m)
5509
5509
5510 ui.pager('status')
5510 ui.pager('status')
5511 fm = ui.formatter('status', opts)
5511 fm = ui.formatter('status', opts)
5512 fmt = '%s' + end
5512 fmt = '%s' + end
5513 showchar = not opts.get('no_status')
5513 showchar = not opts.get('no_status')
5514
5514
5515 for state, char, files in changestates:
5515 for state, char, files in changestates:
5516 if state in show:
5516 if state in show:
5517 label = 'status.' + state
5517 label = 'status.' + state
5518 for f in files:
5518 for f in files:
5519 fm.startitem()
5519 fm.startitem()
5520 fm.context(ctx=ctx2)
5520 fm.context(ctx=ctx2)
5521 fm.data(path=f)
5521 fm.data(path=f)
5522 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5522 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5523 fm.plain(fmt % uipathfn(f), label=label)
5523 fm.plain(fmt % uipathfn(f), label=label)
5524 if f in copy:
5524 if f in copy:
5525 fm.data(source=copy[f])
5525 fm.data(source=copy[f])
5526 fm.plain((' %s' + end) % uipathfn(copy[f]),
5526 fm.plain((' %s' + end) % uipathfn(copy[f]),
5527 label='status.copied')
5527 label='status.copied')
5528
5528
5529 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5529 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5530 and not ui.plain()):
5530 and not ui.plain()):
5531 cmdutil.morestatus(repo, fm)
5531 cmdutil.morestatus(repo, fm)
5532 fm.end()
5532 fm.end()
5533
5533
5534 @command('summary|sum',
5534 @command('summary|sum',
5535 [('', 'remote', None, _('check for push and pull'))],
5535 [('', 'remote', None, _('check for push and pull'))],
5536 '[--remote]',
5536 '[--remote]',
5537 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5537 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5538 helpbasic=True,
5538 helpbasic=True,
5539 intents={INTENT_READONLY})
5539 intents={INTENT_READONLY})
5540 def summary(ui, repo, **opts):
5540 def summary(ui, repo, **opts):
5541 """summarize working directory state
5541 """summarize working directory state
5542
5542
5543 This generates a brief summary of the working directory state,
5543 This generates a brief summary of the working directory state,
5544 including parents, branch, commit status, phase and available updates.
5544 including parents, branch, commit status, phase and available updates.
5545
5545
5546 With the --remote option, this will check the default paths for
5546 With the --remote option, this will check the default paths for
5547 incoming and outgoing changes. This can be time-consuming.
5547 incoming and outgoing changes. This can be time-consuming.
5548
5548
5549 Returns 0 on success.
5549 Returns 0 on success.
5550 """
5550 """
5551
5551
5552 opts = pycompat.byteskwargs(opts)
5552 opts = pycompat.byteskwargs(opts)
5553 ui.pager('summary')
5553 ui.pager('summary')
5554 ctx = repo[None]
5554 ctx = repo[None]
5555 parents = ctx.parents()
5555 parents = ctx.parents()
5556 pnode = parents[0].node()
5556 pnode = parents[0].node()
5557 marks = []
5557 marks = []
5558
5558
5559 try:
5559 try:
5560 ms = mergemod.mergestate.read(repo)
5560 ms = mergemod.mergestate.read(repo)
5561 except error.UnsupportedMergeRecords as e:
5561 except error.UnsupportedMergeRecords as e:
5562 s = ' '.join(e.recordtypes)
5562 s = ' '.join(e.recordtypes)
5563 ui.warn(
5563 ui.warn(
5564 _('warning: merge state has unsupported record types: %s\n') % s)
5564 _('warning: merge state has unsupported record types: %s\n') % s)
5565 unresolved = []
5565 unresolved = []
5566 else:
5566 else:
5567 unresolved = list(ms.unresolved())
5567 unresolved = list(ms.unresolved())
5568
5568
5569 for p in parents:
5569 for p in parents:
5570 # label with log.changeset (instead of log.parent) since this
5570 # label with log.changeset (instead of log.parent) since this
5571 # shows a working directory parent *changeset*:
5571 # shows a working directory parent *changeset*:
5572 # i18n: column positioning for "hg summary"
5572 # i18n: column positioning for "hg summary"
5573 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5573 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5574 label=logcmdutil.changesetlabels(p))
5574 label=logcmdutil.changesetlabels(p))
5575 ui.write(' '.join(p.tags()), label='log.tag')
5575 ui.write(' '.join(p.tags()), label='log.tag')
5576 if p.bookmarks():
5576 if p.bookmarks():
5577 marks.extend(p.bookmarks())
5577 marks.extend(p.bookmarks())
5578 if p.rev() == -1:
5578 if p.rev() == -1:
5579 if not len(repo):
5579 if not len(repo):
5580 ui.write(_(' (empty repository)'))
5580 ui.write(_(' (empty repository)'))
5581 else:
5581 else:
5582 ui.write(_(' (no revision checked out)'))
5582 ui.write(_(' (no revision checked out)'))
5583 if p.obsolete():
5583 if p.obsolete():
5584 ui.write(_(' (obsolete)'))
5584 ui.write(_(' (obsolete)'))
5585 if p.isunstable():
5585 if p.isunstable():
5586 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5586 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5587 for instability in p.instabilities())
5587 for instability in p.instabilities())
5588 ui.write(' ('
5588 ui.write(' ('
5589 + ', '.join(instabilities)
5589 + ', '.join(instabilities)
5590 + ')')
5590 + ')')
5591 ui.write('\n')
5591 ui.write('\n')
5592 if p.description():
5592 if p.description():
5593 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5593 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5594 label='log.summary')
5594 label='log.summary')
5595
5595
5596 branch = ctx.branch()
5596 branch = ctx.branch()
5597 bheads = repo.branchheads(branch)
5597 bheads = repo.branchheads(branch)
5598 # i18n: column positioning for "hg summary"
5598 # i18n: column positioning for "hg summary"
5599 m = _('branch: %s\n') % branch
5599 m = _('branch: %s\n') % branch
5600 if branch != 'default':
5600 if branch != 'default':
5601 ui.write(m, label='log.branch')
5601 ui.write(m, label='log.branch')
5602 else:
5602 else:
5603 ui.status(m, label='log.branch')
5603 ui.status(m, label='log.branch')
5604
5604
5605 if marks:
5605 if marks:
5606 active = repo._activebookmark
5606 active = repo._activebookmark
5607 # i18n: column positioning for "hg summary"
5607 # i18n: column positioning for "hg summary"
5608 ui.write(_('bookmarks:'), label='log.bookmark')
5608 ui.write(_('bookmarks:'), label='log.bookmark')
5609 if active is not None:
5609 if active is not None:
5610 if active in marks:
5610 if active in marks:
5611 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5611 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5612 marks.remove(active)
5612 marks.remove(active)
5613 else:
5613 else:
5614 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5614 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5615 for m in marks:
5615 for m in marks:
5616 ui.write(' ' + m, label='log.bookmark')
5616 ui.write(' ' + m, label='log.bookmark')
5617 ui.write('\n', label='log.bookmark')
5617 ui.write('\n', label='log.bookmark')
5618
5618
5619 status = repo.status(unknown=True)
5619 status = repo.status(unknown=True)
5620
5620
5621 c = repo.dirstate.copies()
5621 c = repo.dirstate.copies()
5622 copied, renamed = [], []
5622 copied, renamed = [], []
5623 for d, s in c.iteritems():
5623 for d, s in c.iteritems():
5624 if s in status.removed:
5624 if s in status.removed:
5625 status.removed.remove(s)
5625 status.removed.remove(s)
5626 renamed.append(d)
5626 renamed.append(d)
5627 else:
5627 else:
5628 copied.append(d)
5628 copied.append(d)
5629 if d in status.added:
5629 if d in status.added:
5630 status.added.remove(d)
5630 status.added.remove(d)
5631
5631
5632 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5632 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5633
5633
5634 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5634 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5635 (ui.label(_('%d added'), 'status.added'), status.added),
5635 (ui.label(_('%d added'), 'status.added'), status.added),
5636 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5636 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5637 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5637 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5638 (ui.label(_('%d copied'), 'status.copied'), copied),
5638 (ui.label(_('%d copied'), 'status.copied'), copied),
5639 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5639 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5640 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5640 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5641 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5641 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5642 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5642 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5643 t = []
5643 t = []
5644 for l, s in labels:
5644 for l, s in labels:
5645 if s:
5645 if s:
5646 t.append(l % len(s))
5646 t.append(l % len(s))
5647
5647
5648 t = ', '.join(t)
5648 t = ', '.join(t)
5649 cleanworkdir = False
5649 cleanworkdir = False
5650
5650
5651 if repo.vfs.exists('graftstate'):
5651 if repo.vfs.exists('graftstate'):
5652 t += _(' (graft in progress)')
5652 t += _(' (graft in progress)')
5653 if repo.vfs.exists('updatestate'):
5653 if repo.vfs.exists('updatestate'):
5654 t += _(' (interrupted update)')
5654 t += _(' (interrupted update)')
5655 elif len(parents) > 1:
5655 elif len(parents) > 1:
5656 t += _(' (merge)')
5656 t += _(' (merge)')
5657 elif branch != parents[0].branch():
5657 elif branch != parents[0].branch():
5658 t += _(' (new branch)')
5658 t += _(' (new branch)')
5659 elif (parents[0].closesbranch() and
5659 elif (parents[0].closesbranch() and
5660 pnode in repo.branchheads(branch, closed=True)):
5660 pnode in repo.branchheads(branch, closed=True)):
5661 t += _(' (head closed)')
5661 t += _(' (head closed)')
5662 elif not (status.modified or status.added or status.removed or renamed or
5662 elif not (status.modified or status.added or status.removed or renamed or
5663 copied or subs):
5663 copied or subs):
5664 t += _(' (clean)')
5664 t += _(' (clean)')
5665 cleanworkdir = True
5665 cleanworkdir = True
5666 elif pnode not in bheads:
5666 elif pnode not in bheads:
5667 t += _(' (new branch head)')
5667 t += _(' (new branch head)')
5668
5668
5669 if parents:
5669 if parents:
5670 pendingphase = max(p.phase() for p in parents)
5670 pendingphase = max(p.phase() for p in parents)
5671 else:
5671 else:
5672 pendingphase = phases.public
5672 pendingphase = phases.public
5673
5673
5674 if pendingphase > phases.newcommitphase(ui):
5674 if pendingphase > phases.newcommitphase(ui):
5675 t += ' (%s)' % phases.phasenames[pendingphase]
5675 t += ' (%s)' % phases.phasenames[pendingphase]
5676
5676
5677 if cleanworkdir:
5677 if cleanworkdir:
5678 # i18n: column positioning for "hg summary"
5678 # i18n: column positioning for "hg summary"
5679 ui.status(_('commit: %s\n') % t.strip())
5679 ui.status(_('commit: %s\n') % t.strip())
5680 else:
5680 else:
5681 # i18n: column positioning for "hg summary"
5681 # i18n: column positioning for "hg summary"
5682 ui.write(_('commit: %s\n') % t.strip())
5682 ui.write(_('commit: %s\n') % t.strip())
5683
5683
5684 # all ancestors of branch heads - all ancestors of parent = new csets
5684 # all ancestors of branch heads - all ancestors of parent = new csets
5685 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5685 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5686 bheads))
5686 bheads))
5687
5687
5688 if new == 0:
5688 if new == 0:
5689 # i18n: column positioning for "hg summary"
5689 # i18n: column positioning for "hg summary"
5690 ui.status(_('update: (current)\n'))
5690 ui.status(_('update: (current)\n'))
5691 elif pnode not in bheads:
5691 elif pnode not in bheads:
5692 # i18n: column positioning for "hg summary"
5692 # i18n: column positioning for "hg summary"
5693 ui.write(_('update: %d new changesets (update)\n') % new)
5693 ui.write(_('update: %d new changesets (update)\n') % new)
5694 else:
5694 else:
5695 # i18n: column positioning for "hg summary"
5695 # i18n: column positioning for "hg summary"
5696 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5696 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5697 (new, len(bheads)))
5697 (new, len(bheads)))
5698
5698
5699 t = []
5699 t = []
5700 draft = len(repo.revs('draft()'))
5700 draft = len(repo.revs('draft()'))
5701 if draft:
5701 if draft:
5702 t.append(_('%d draft') % draft)
5702 t.append(_('%d draft') % draft)
5703 secret = len(repo.revs('secret()'))
5703 secret = len(repo.revs('secret()'))
5704 if secret:
5704 if secret:
5705 t.append(_('%d secret') % secret)
5705 t.append(_('%d secret') % secret)
5706
5706
5707 if draft or secret:
5707 if draft or secret:
5708 ui.status(_('phases: %s\n') % ', '.join(t))
5708 ui.status(_('phases: %s\n') % ', '.join(t))
5709
5709
5710 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5710 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5711 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5711 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5712 numtrouble = len(repo.revs(trouble + "()"))
5712 numtrouble = len(repo.revs(trouble + "()"))
5713 # We write all the possibilities to ease translation
5713 # We write all the possibilities to ease translation
5714 troublemsg = {
5714 troublemsg = {
5715 "orphan": _("orphan: %d changesets"),
5715 "orphan": _("orphan: %d changesets"),
5716 "contentdivergent": _("content-divergent: %d changesets"),
5716 "contentdivergent": _("content-divergent: %d changesets"),
5717 "phasedivergent": _("phase-divergent: %d changesets"),
5717 "phasedivergent": _("phase-divergent: %d changesets"),
5718 }
5718 }
5719 if numtrouble > 0:
5719 if numtrouble > 0:
5720 ui.status(troublemsg[trouble] % numtrouble + "\n")
5720 ui.status(troublemsg[trouble] % numtrouble + "\n")
5721
5721
5722 cmdutil.summaryhooks(ui, repo)
5722 cmdutil.summaryhooks(ui, repo)
5723
5723
5724 if opts.get('remote'):
5724 if opts.get('remote'):
5725 needsincoming, needsoutgoing = True, True
5725 needsincoming, needsoutgoing = True, True
5726 else:
5726 else:
5727 needsincoming, needsoutgoing = False, False
5727 needsincoming, needsoutgoing = False, False
5728 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5728 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5729 if i:
5729 if i:
5730 needsincoming = True
5730 needsincoming = True
5731 if o:
5731 if o:
5732 needsoutgoing = True
5732 needsoutgoing = True
5733 if not needsincoming and not needsoutgoing:
5733 if not needsincoming and not needsoutgoing:
5734 return
5734 return
5735
5735
5736 def getincoming():
5736 def getincoming():
5737 source, branches = hg.parseurl(ui.expandpath('default'))
5737 source, branches = hg.parseurl(ui.expandpath('default'))
5738 sbranch = branches[0]
5738 sbranch = branches[0]
5739 try:
5739 try:
5740 other = hg.peer(repo, {}, source)
5740 other = hg.peer(repo, {}, source)
5741 except error.RepoError:
5741 except error.RepoError:
5742 if opts.get('remote'):
5742 if opts.get('remote'):
5743 raise
5743 raise
5744 return source, sbranch, None, None, None
5744 return source, sbranch, None, None, None
5745 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5745 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5746 if revs:
5746 if revs:
5747 revs = [other.lookup(rev) for rev in revs]
5747 revs = [other.lookup(rev) for rev in revs]
5748 ui.debug('comparing with %s\n' % util.hidepassword(source))
5748 ui.debug('comparing with %s\n' % util.hidepassword(source))
5749 repo.ui.pushbuffer()
5749 repo.ui.pushbuffer()
5750 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5750 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5751 repo.ui.popbuffer()
5751 repo.ui.popbuffer()
5752 return source, sbranch, other, commoninc, commoninc[1]
5752 return source, sbranch, other, commoninc, commoninc[1]
5753
5753
5754 if needsincoming:
5754 if needsincoming:
5755 source, sbranch, sother, commoninc, incoming = getincoming()
5755 source, sbranch, sother, commoninc, incoming = getincoming()
5756 else:
5756 else:
5757 source = sbranch = sother = commoninc = incoming = None
5757 source = sbranch = sother = commoninc = incoming = None
5758
5758
5759 def getoutgoing():
5759 def getoutgoing():
5760 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5760 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5761 dbranch = branches[0]
5761 dbranch = branches[0]
5762 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5762 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5763 if source != dest:
5763 if source != dest:
5764 try:
5764 try:
5765 dother = hg.peer(repo, {}, dest)
5765 dother = hg.peer(repo, {}, dest)
5766 except error.RepoError:
5766 except error.RepoError:
5767 if opts.get('remote'):
5767 if opts.get('remote'):
5768 raise
5768 raise
5769 return dest, dbranch, None, None
5769 return dest, dbranch, None, None
5770 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5770 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5771 elif sother is None:
5771 elif sother is None:
5772 # there is no explicit destination peer, but source one is invalid
5772 # there is no explicit destination peer, but source one is invalid
5773 return dest, dbranch, None, None
5773 return dest, dbranch, None, None
5774 else:
5774 else:
5775 dother = sother
5775 dother = sother
5776 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5776 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5777 common = None
5777 common = None
5778 else:
5778 else:
5779 common = commoninc
5779 common = commoninc
5780 if revs:
5780 if revs:
5781 revs = [repo.lookup(rev) for rev in revs]
5781 revs = [repo.lookup(rev) for rev in revs]
5782 repo.ui.pushbuffer()
5782 repo.ui.pushbuffer()
5783 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5783 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5784 commoninc=common)
5784 commoninc=common)
5785 repo.ui.popbuffer()
5785 repo.ui.popbuffer()
5786 return dest, dbranch, dother, outgoing
5786 return dest, dbranch, dother, outgoing
5787
5787
5788 if needsoutgoing:
5788 if needsoutgoing:
5789 dest, dbranch, dother, outgoing = getoutgoing()
5789 dest, dbranch, dother, outgoing = getoutgoing()
5790 else:
5790 else:
5791 dest = dbranch = dother = outgoing = None
5791 dest = dbranch = dother = outgoing = None
5792
5792
5793 if opts.get('remote'):
5793 if opts.get('remote'):
5794 t = []
5794 t = []
5795 if incoming:
5795 if incoming:
5796 t.append(_('1 or more incoming'))
5796 t.append(_('1 or more incoming'))
5797 o = outgoing.missing
5797 o = outgoing.missing
5798 if o:
5798 if o:
5799 t.append(_('%d outgoing') % len(o))
5799 t.append(_('%d outgoing') % len(o))
5800 other = dother or sother
5800 other = dother or sother
5801 if 'bookmarks' in other.listkeys('namespaces'):
5801 if 'bookmarks' in other.listkeys('namespaces'):
5802 counts = bookmarks.summary(repo, other)
5802 counts = bookmarks.summary(repo, other)
5803 if counts[0] > 0:
5803 if counts[0] > 0:
5804 t.append(_('%d incoming bookmarks') % counts[0])
5804 t.append(_('%d incoming bookmarks') % counts[0])
5805 if counts[1] > 0:
5805 if counts[1] > 0:
5806 t.append(_('%d outgoing bookmarks') % counts[1])
5806 t.append(_('%d outgoing bookmarks') % counts[1])
5807
5807
5808 if t:
5808 if t:
5809 # i18n: column positioning for "hg summary"
5809 # i18n: column positioning for "hg summary"
5810 ui.write(_('remote: %s\n') % (', '.join(t)))
5810 ui.write(_('remote: %s\n') % (', '.join(t)))
5811 else:
5811 else:
5812 # i18n: column positioning for "hg summary"
5812 # i18n: column positioning for "hg summary"
5813 ui.status(_('remote: (synced)\n'))
5813 ui.status(_('remote: (synced)\n'))
5814
5814
5815 cmdutil.summaryremotehooks(ui, repo, opts,
5815 cmdutil.summaryremotehooks(ui, repo, opts,
5816 ((source, sbranch, sother, commoninc),
5816 ((source, sbranch, sother, commoninc),
5817 (dest, dbranch, dother, outgoing)))
5817 (dest, dbranch, dother, outgoing)))
5818
5818
5819 @command('tag',
5819 @command('tag',
5820 [('f', 'force', None, _('force tag')),
5820 [('f', 'force', None, _('force tag')),
5821 ('l', 'local', None, _('make the tag local')),
5821 ('l', 'local', None, _('make the tag local')),
5822 ('r', 'rev', '', _('revision to tag'), _('REV')),
5822 ('r', 'rev', '', _('revision to tag'), _('REV')),
5823 ('', 'remove', None, _('remove a tag')),
5823 ('', 'remove', None, _('remove a tag')),
5824 # -l/--local is already there, commitopts cannot be used
5824 # -l/--local is already there, commitopts cannot be used
5825 ('e', 'edit', None, _('invoke editor on commit messages')),
5825 ('e', 'edit', None, _('invoke editor on commit messages')),
5826 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5826 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5827 ] + commitopts2,
5827 ] + commitopts2,
5828 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5828 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5829 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5829 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5830 def tag(ui, repo, name1, *names, **opts):
5830 def tag(ui, repo, name1, *names, **opts):
5831 """add one or more tags for the current or given revision
5831 """add one or more tags for the current or given revision
5832
5832
5833 Name a particular revision using <name>.
5833 Name a particular revision using <name>.
5834
5834
5835 Tags are used to name particular revisions of the repository and are
5835 Tags are used to name particular revisions of the repository and are
5836 very useful to compare different revisions, to go back to significant
5836 very useful to compare different revisions, to go back to significant
5837 earlier versions or to mark branch points as releases, etc. Changing
5837 earlier versions or to mark branch points as releases, etc. Changing
5838 an existing tag is normally disallowed; use -f/--force to override.
5838 an existing tag is normally disallowed; use -f/--force to override.
5839
5839
5840 If no revision is given, the parent of the working directory is
5840 If no revision is given, the parent of the working directory is
5841 used.
5841 used.
5842
5842
5843 To facilitate version control, distribution, and merging of tags,
5843 To facilitate version control, distribution, and merging of tags,
5844 they are stored as a file named ".hgtags" which is managed similarly
5844 they are stored as a file named ".hgtags" which is managed similarly
5845 to other project files and can be hand-edited if necessary. This
5845 to other project files and can be hand-edited if necessary. This
5846 also means that tagging creates a new commit. The file
5846 also means that tagging creates a new commit. The file
5847 ".hg/localtags" is used for local tags (not shared among
5847 ".hg/localtags" is used for local tags (not shared among
5848 repositories).
5848 repositories).
5849
5849
5850 Tag commits are usually made at the head of a branch. If the parent
5850 Tag commits are usually made at the head of a branch. If the parent
5851 of the working directory is not a branch head, :hg:`tag` aborts; use
5851 of the working directory is not a branch head, :hg:`tag` aborts; use
5852 -f/--force to force the tag commit to be based on a non-head
5852 -f/--force to force the tag commit to be based on a non-head
5853 changeset.
5853 changeset.
5854
5854
5855 See :hg:`help dates` for a list of formats valid for -d/--date.
5855 See :hg:`help dates` for a list of formats valid for -d/--date.
5856
5856
5857 Since tag names have priority over branch names during revision
5857 Since tag names have priority over branch names during revision
5858 lookup, using an existing branch name as a tag name is discouraged.
5858 lookup, using an existing branch name as a tag name is discouraged.
5859
5859
5860 Returns 0 on success.
5860 Returns 0 on success.
5861 """
5861 """
5862 opts = pycompat.byteskwargs(opts)
5862 opts = pycompat.byteskwargs(opts)
5863 with repo.wlock(), repo.lock():
5863 with repo.wlock(), repo.lock():
5864 rev_ = "."
5864 rev_ = "."
5865 names = [t.strip() for t in (name1,) + names]
5865 names = [t.strip() for t in (name1,) + names]
5866 if len(names) != len(set(names)):
5866 if len(names) != len(set(names)):
5867 raise error.Abort(_('tag names must be unique'))
5867 raise error.Abort(_('tag names must be unique'))
5868 for n in names:
5868 for n in names:
5869 scmutil.checknewlabel(repo, n, 'tag')
5869 scmutil.checknewlabel(repo, n, 'tag')
5870 if not n:
5870 if not n:
5871 raise error.Abort(_('tag names cannot consist entirely of '
5871 raise error.Abort(_('tag names cannot consist entirely of '
5872 'whitespace'))
5872 'whitespace'))
5873 if opts.get('rev') and opts.get('remove'):
5873 if opts.get('rev') and opts.get('remove'):
5874 raise error.Abort(_("--rev and --remove are incompatible"))
5874 raise error.Abort(_("--rev and --remove are incompatible"))
5875 if opts.get('rev'):
5875 if opts.get('rev'):
5876 rev_ = opts['rev']
5876 rev_ = opts['rev']
5877 message = opts.get('message')
5877 message = opts.get('message')
5878 if opts.get('remove'):
5878 if opts.get('remove'):
5879 if opts.get('local'):
5879 if opts.get('local'):
5880 expectedtype = 'local'
5880 expectedtype = 'local'
5881 else:
5881 else:
5882 expectedtype = 'global'
5882 expectedtype = 'global'
5883
5883
5884 for n in names:
5884 for n in names:
5885 if repo.tagtype(n) == 'global':
5885 if repo.tagtype(n) == 'global':
5886 alltags = tagsmod.findglobaltags(ui, repo)
5886 alltags = tagsmod.findglobaltags(ui, repo)
5887 if alltags[n][0] == nullid:
5887 if alltags[n][0] == nullid:
5888 raise error.Abort(_("tag '%s' is already removed") % n)
5888 raise error.Abort(_("tag '%s' is already removed") % n)
5889 if not repo.tagtype(n):
5889 if not repo.tagtype(n):
5890 raise error.Abort(_("tag '%s' does not exist") % n)
5890 raise error.Abort(_("tag '%s' does not exist") % n)
5891 if repo.tagtype(n) != expectedtype:
5891 if repo.tagtype(n) != expectedtype:
5892 if expectedtype == 'global':
5892 if expectedtype == 'global':
5893 raise error.Abort(_("tag '%s' is not a global tag") % n)
5893 raise error.Abort(_("tag '%s' is not a global tag") % n)
5894 else:
5894 else:
5895 raise error.Abort(_("tag '%s' is not a local tag") % n)
5895 raise error.Abort(_("tag '%s' is not a local tag") % n)
5896 rev_ = 'null'
5896 rev_ = 'null'
5897 if not message:
5897 if not message:
5898 # we don't translate commit messages
5898 # we don't translate commit messages
5899 message = 'Removed tag %s' % ', '.join(names)
5899 message = 'Removed tag %s' % ', '.join(names)
5900 elif not opts.get('force'):
5900 elif not opts.get('force'):
5901 for n in names:
5901 for n in names:
5902 if n in repo.tags():
5902 if n in repo.tags():
5903 raise error.Abort(_("tag '%s' already exists "
5903 raise error.Abort(_("tag '%s' already exists "
5904 "(use -f to force)") % n)
5904 "(use -f to force)") % n)
5905 if not opts.get('local'):
5905 if not opts.get('local'):
5906 p1, p2 = repo.dirstate.parents()
5906 p1, p2 = repo.dirstate.parents()
5907 if p2 != nullid:
5907 if p2 != nullid:
5908 raise error.Abort(_('uncommitted merge'))
5908 raise error.Abort(_('uncommitted merge'))
5909 bheads = repo.branchheads()
5909 bheads = repo.branchheads()
5910 if not opts.get('force') and bheads and p1 not in bheads:
5910 if not opts.get('force') and bheads and p1 not in bheads:
5911 raise error.Abort(_('working directory is not at a branch head '
5911 raise error.Abort(_('working directory is not at a branch head '
5912 '(use -f to force)'))
5912 '(use -f to force)'))
5913 node = scmutil.revsingle(repo, rev_).node()
5913 node = scmutil.revsingle(repo, rev_).node()
5914
5914
5915 if not message:
5915 if not message:
5916 # we don't translate commit messages
5916 # we don't translate commit messages
5917 message = ('Added tag %s for changeset %s' %
5917 message = ('Added tag %s for changeset %s' %
5918 (', '.join(names), short(node)))
5918 (', '.join(names), short(node)))
5919
5919
5920 date = opts.get('date')
5920 date = opts.get('date')
5921 if date:
5921 if date:
5922 date = dateutil.parsedate(date)
5922 date = dateutil.parsedate(date)
5923
5923
5924 if opts.get('remove'):
5924 if opts.get('remove'):
5925 editform = 'tag.remove'
5925 editform = 'tag.remove'
5926 else:
5926 else:
5927 editform = 'tag.add'
5927 editform = 'tag.add'
5928 editor = cmdutil.getcommiteditor(editform=editform,
5928 editor = cmdutil.getcommiteditor(editform=editform,
5929 **pycompat.strkwargs(opts))
5929 **pycompat.strkwargs(opts))
5930
5930
5931 # don't allow tagging the null rev
5931 # don't allow tagging the null rev
5932 if (not opts.get('remove') and
5932 if (not opts.get('remove') and
5933 scmutil.revsingle(repo, rev_).rev() == nullrev):
5933 scmutil.revsingle(repo, rev_).rev() == nullrev):
5934 raise error.Abort(_("cannot tag null revision"))
5934 raise error.Abort(_("cannot tag null revision"))
5935
5935
5936 tagsmod.tag(repo, names, node, message, opts.get('local'),
5936 tagsmod.tag(repo, names, node, message, opts.get('local'),
5937 opts.get('user'), date, editor=editor)
5937 opts.get('user'), date, editor=editor)
5938
5938
5939 @command(
5939 @command(
5940 'tags', formatteropts, '',
5940 'tags', formatteropts, '',
5941 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5941 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5942 intents={INTENT_READONLY})
5942 intents={INTENT_READONLY})
5943 def tags(ui, repo, **opts):
5943 def tags(ui, repo, **opts):
5944 """list repository tags
5944 """list repository tags
5945
5945
5946 This lists both regular and local tags. When the -v/--verbose
5946 This lists both regular and local tags. When the -v/--verbose
5947 switch is used, a third column "local" is printed for local tags.
5947 switch is used, a third column "local" is printed for local tags.
5948 When the -q/--quiet switch is used, only the tag name is printed.
5948 When the -q/--quiet switch is used, only the tag name is printed.
5949
5949
5950 .. container:: verbose
5950 .. container:: verbose
5951
5951
5952 Template:
5952 Template:
5953
5953
5954 The following keywords are supported in addition to the common template
5954 The following keywords are supported in addition to the common template
5955 keywords and functions such as ``{tag}``. See also
5955 keywords and functions such as ``{tag}``. See also
5956 :hg:`help templates`.
5956 :hg:`help templates`.
5957
5957
5958 :type: String. ``local`` for local tags.
5958 :type: String. ``local`` for local tags.
5959
5959
5960 Returns 0 on success.
5960 Returns 0 on success.
5961 """
5961 """
5962
5962
5963 opts = pycompat.byteskwargs(opts)
5963 opts = pycompat.byteskwargs(opts)
5964 ui.pager('tags')
5964 ui.pager('tags')
5965 fm = ui.formatter('tags', opts)
5965 fm = ui.formatter('tags', opts)
5966 hexfunc = fm.hexfunc
5966 hexfunc = fm.hexfunc
5967
5967
5968 for t, n in reversed(repo.tagslist()):
5968 for t, n in reversed(repo.tagslist()):
5969 hn = hexfunc(n)
5969 hn = hexfunc(n)
5970 label = 'tags.normal'
5970 label = 'tags.normal'
5971 tagtype = ''
5971 tagtype = ''
5972 if repo.tagtype(t) == 'local':
5972 if repo.tagtype(t) == 'local':
5973 label = 'tags.local'
5973 label = 'tags.local'
5974 tagtype = 'local'
5974 tagtype = 'local'
5975
5975
5976 fm.startitem()
5976 fm.startitem()
5977 fm.context(repo=repo)
5977 fm.context(repo=repo)
5978 fm.write('tag', '%s', t, label=label)
5978 fm.write('tag', '%s', t, label=label)
5979 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5979 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5980 fm.condwrite(not ui.quiet, 'rev node', fmt,
5980 fm.condwrite(not ui.quiet, 'rev node', fmt,
5981 repo.changelog.rev(n), hn, label=label)
5981 repo.changelog.rev(n), hn, label=label)
5982 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5982 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5983 tagtype, label=label)
5983 tagtype, label=label)
5984 fm.plain('\n')
5984 fm.plain('\n')
5985 fm.end()
5985 fm.end()
5986
5986
5987 @command('tip',
5987 @command('tip',
5988 [('p', 'patch', None, _('show patch')),
5988 [('p', 'patch', None, _('show patch')),
5989 ('g', 'git', None, _('use git extended diff format')),
5989 ('g', 'git', None, _('use git extended diff format')),
5990 ] + templateopts,
5990 ] + templateopts,
5991 _('[-p] [-g]'),
5991 _('[-p] [-g]'),
5992 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5992 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5993 def tip(ui, repo, **opts):
5993 def tip(ui, repo, **opts):
5994 """show the tip revision (DEPRECATED)
5994 """show the tip revision (DEPRECATED)
5995
5995
5996 The tip revision (usually just called the tip) is the changeset
5996 The tip revision (usually just called the tip) is the changeset
5997 most recently added to the repository (and therefore the most
5997 most recently added to the repository (and therefore the most
5998 recently changed head).
5998 recently changed head).
5999
5999
6000 If you have just made a commit, that commit will be the tip. If
6000 If you have just made a commit, that commit will be the tip. If
6001 you have just pulled changes from another repository, the tip of
6001 you have just pulled changes from another repository, the tip of
6002 that repository becomes the current tip. The "tip" tag is special
6002 that repository becomes the current tip. The "tip" tag is special
6003 and cannot be renamed or assigned to a different changeset.
6003 and cannot be renamed or assigned to a different changeset.
6004
6004
6005 This command is deprecated, please use :hg:`heads` instead.
6005 This command is deprecated, please use :hg:`heads` instead.
6006
6006
6007 Returns 0 on success.
6007 Returns 0 on success.
6008 """
6008 """
6009 opts = pycompat.byteskwargs(opts)
6009 opts = pycompat.byteskwargs(opts)
6010 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6010 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6011 displayer.show(repo['tip'])
6011 displayer.show(repo['tip'])
6012 displayer.close()
6012 displayer.close()
6013
6013
6014 @command('unbundle',
6014 @command('unbundle',
6015 [('u', 'update', None,
6015 [('u', 'update', None,
6016 _('update to new branch head if changesets were unbundled'))],
6016 _('update to new branch head if changesets were unbundled'))],
6017 _('[-u] FILE...'),
6017 _('[-u] FILE...'),
6018 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6018 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6019 def unbundle(ui, repo, fname1, *fnames, **opts):
6019 def unbundle(ui, repo, fname1, *fnames, **opts):
6020 """apply one or more bundle files
6020 """apply one or more bundle files
6021
6021
6022 Apply one or more bundle files generated by :hg:`bundle`.
6022 Apply one or more bundle files generated by :hg:`bundle`.
6023
6023
6024 Returns 0 on success, 1 if an update has unresolved files.
6024 Returns 0 on success, 1 if an update has unresolved files.
6025 """
6025 """
6026 fnames = (fname1,) + fnames
6026 fnames = (fname1,) + fnames
6027
6027
6028 with repo.lock():
6028 with repo.lock():
6029 for fname in fnames:
6029 for fname in fnames:
6030 f = hg.openpath(ui, fname)
6030 f = hg.openpath(ui, fname)
6031 gen = exchange.readbundle(ui, f, fname)
6031 gen = exchange.readbundle(ui, f, fname)
6032 if isinstance(gen, streamclone.streamcloneapplier):
6032 if isinstance(gen, streamclone.streamcloneapplier):
6033 raise error.Abort(
6033 raise error.Abort(
6034 _('packed bundles cannot be applied with '
6034 _('packed bundles cannot be applied with '
6035 '"hg unbundle"'),
6035 '"hg unbundle"'),
6036 hint=_('use "hg debugapplystreamclonebundle"'))
6036 hint=_('use "hg debugapplystreamclonebundle"'))
6037 url = 'bundle:' + fname
6037 url = 'bundle:' + fname
6038 try:
6038 try:
6039 txnname = 'unbundle'
6039 txnname = 'unbundle'
6040 if not isinstance(gen, bundle2.unbundle20):
6040 if not isinstance(gen, bundle2.unbundle20):
6041 txnname = 'unbundle\n%s' % util.hidepassword(url)
6041 txnname = 'unbundle\n%s' % util.hidepassword(url)
6042 with repo.transaction(txnname) as tr:
6042 with repo.transaction(txnname) as tr:
6043 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6043 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6044 url=url)
6044 url=url)
6045 except error.BundleUnknownFeatureError as exc:
6045 except error.BundleUnknownFeatureError as exc:
6046 raise error.Abort(
6046 raise error.Abort(
6047 _('%s: unknown bundle feature, %s') % (fname, exc),
6047 _('%s: unknown bundle feature, %s') % (fname, exc),
6048 hint=_("see https://mercurial-scm.org/"
6048 hint=_("see https://mercurial-scm.org/"
6049 "wiki/BundleFeature for more "
6049 "wiki/BundleFeature for more "
6050 "information"))
6050 "information"))
6051 modheads = bundle2.combinechangegroupresults(op)
6051 modheads = bundle2.combinechangegroupresults(op)
6052
6052
6053 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6053 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6054
6054
6055 @command('update|up|checkout|co',
6055 @command('update|up|checkout|co',
6056 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6056 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6057 ('c', 'check', None, _('require clean working directory')),
6057 ('c', 'check', None, _('require clean working directory')),
6058 ('m', 'merge', None, _('merge uncommitted changes')),
6058 ('m', 'merge', None, _('merge uncommitted changes')),
6059 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6059 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6060 ('r', 'rev', '', _('revision'), _('REV'))
6060 ('r', 'rev', '', _('revision'), _('REV'))
6061 ] + mergetoolopts,
6061 ] + mergetoolopts,
6062 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6062 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6063 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6063 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6064 helpbasic=True)
6064 helpbasic=True)
6065 def update(ui, repo, node=None, **opts):
6065 def update(ui, repo, node=None, **opts):
6066 """update working directory (or switch revisions)
6066 """update working directory (or switch revisions)
6067
6067
6068 Update the repository's working directory to the specified
6068 Update the repository's working directory to the specified
6069 changeset. If no changeset is specified, update to the tip of the
6069 changeset. If no changeset is specified, update to the tip of the
6070 current named branch and move the active bookmark (see :hg:`help
6070 current named branch and move the active bookmark (see :hg:`help
6071 bookmarks`).
6071 bookmarks`).
6072
6072
6073 Update sets the working directory's parent revision to the specified
6073 Update sets the working directory's parent revision to the specified
6074 changeset (see :hg:`help parents`).
6074 changeset (see :hg:`help parents`).
6075
6075
6076 If the changeset is not a descendant or ancestor of the working
6076 If the changeset is not a descendant or ancestor of the working
6077 directory's parent and there are uncommitted changes, the update is
6077 directory's parent and there are uncommitted changes, the update is
6078 aborted. With the -c/--check option, the working directory is checked
6078 aborted. With the -c/--check option, the working directory is checked
6079 for uncommitted changes; if none are found, the working directory is
6079 for uncommitted changes; if none are found, the working directory is
6080 updated to the specified changeset.
6080 updated to the specified changeset.
6081
6081
6082 .. container:: verbose
6082 .. container:: verbose
6083
6083
6084 The -C/--clean, -c/--check, and -m/--merge options control what
6084 The -C/--clean, -c/--check, and -m/--merge options control what
6085 happens if the working directory contains uncommitted changes.
6085 happens if the working directory contains uncommitted changes.
6086 At most of one of them can be specified.
6086 At most of one of them can be specified.
6087
6087
6088 1. If no option is specified, and if
6088 1. If no option is specified, and if
6089 the requested changeset is an ancestor or descendant of
6089 the requested changeset is an ancestor or descendant of
6090 the working directory's parent, the uncommitted changes
6090 the working directory's parent, the uncommitted changes
6091 are merged into the requested changeset and the merged
6091 are merged into the requested changeset and the merged
6092 result is left uncommitted. If the requested changeset is
6092 result is left uncommitted. If the requested changeset is
6093 not an ancestor or descendant (that is, it is on another
6093 not an ancestor or descendant (that is, it is on another
6094 branch), the update is aborted and the uncommitted changes
6094 branch), the update is aborted and the uncommitted changes
6095 are preserved.
6095 are preserved.
6096
6096
6097 2. With the -m/--merge option, the update is allowed even if the
6097 2. With the -m/--merge option, the update is allowed even if the
6098 requested changeset is not an ancestor or descendant of
6098 requested changeset is not an ancestor or descendant of
6099 the working directory's parent.
6099 the working directory's parent.
6100
6100
6101 3. With the -c/--check option, the update is aborted and the
6101 3. With the -c/--check option, the update is aborted and the
6102 uncommitted changes are preserved.
6102 uncommitted changes are preserved.
6103
6103
6104 4. With the -C/--clean option, uncommitted changes are discarded and
6104 4. With the -C/--clean option, uncommitted changes are discarded and
6105 the working directory is updated to the requested changeset.
6105 the working directory is updated to the requested changeset.
6106
6106
6107 To cancel an uncommitted merge (and lose your changes), use
6107 To cancel an uncommitted merge (and lose your changes), use
6108 :hg:`merge --abort`.
6108 :hg:`merge --abort`.
6109
6109
6110 Use null as the changeset to remove the working directory (like
6110 Use null as the changeset to remove the working directory (like
6111 :hg:`clone -U`).
6111 :hg:`clone -U`).
6112
6112
6113 If you want to revert just one file to an older revision, use
6113 If you want to revert just one file to an older revision, use
6114 :hg:`revert [-r REV] NAME`.
6114 :hg:`revert [-r REV] NAME`.
6115
6115
6116 See :hg:`help dates` for a list of formats valid for -d/--date.
6116 See :hg:`help dates` for a list of formats valid for -d/--date.
6117
6117
6118 Returns 0 on success, 1 if there are unresolved files.
6118 Returns 0 on success, 1 if there are unresolved files.
6119 """
6119 """
6120 rev = opts.get(r'rev')
6120 rev = opts.get(r'rev')
6121 date = opts.get(r'date')
6121 date = opts.get(r'date')
6122 clean = opts.get(r'clean')
6122 clean = opts.get(r'clean')
6123 check = opts.get(r'check')
6123 check = opts.get(r'check')
6124 merge = opts.get(r'merge')
6124 merge = opts.get(r'merge')
6125 if rev and node:
6125 if rev and node:
6126 raise error.Abort(_("please specify just one revision"))
6126 raise error.Abort(_("please specify just one revision"))
6127
6127
6128 if ui.configbool('commands', 'update.requiredest'):
6128 if ui.configbool('commands', 'update.requiredest'):
6129 if not node and not rev and not date:
6129 if not node and not rev and not date:
6130 raise error.Abort(_('you must specify a destination'),
6130 raise error.Abort(_('you must specify a destination'),
6131 hint=_('for example: hg update ".::"'))
6131 hint=_('for example: hg update ".::"'))
6132
6132
6133 if rev is None or rev == '':
6133 if rev is None or rev == '':
6134 rev = node
6134 rev = node
6135
6135
6136 if date and rev is not None:
6136 if date and rev is not None:
6137 raise error.Abort(_("you can't specify a revision and a date"))
6137 raise error.Abort(_("you can't specify a revision and a date"))
6138
6138
6139 if len([x for x in (clean, check, merge) if x]) > 1:
6139 if len([x for x in (clean, check, merge) if x]) > 1:
6140 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6140 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6141 "or -m/--merge"))
6141 "or -m/--merge"))
6142
6142
6143 updatecheck = None
6143 updatecheck = None
6144 if check:
6144 if check:
6145 updatecheck = 'abort'
6145 updatecheck = 'abort'
6146 elif merge:
6146 elif merge:
6147 updatecheck = 'none'
6147 updatecheck = 'none'
6148
6148
6149 with repo.wlock():
6149 with repo.wlock():
6150 cmdutil.clearunfinished(repo)
6150 cmdutil.clearunfinished(repo)
6151
6151
6152 if date:
6152 if date:
6153 rev = cmdutil.finddate(ui, repo, date)
6153 rev = cmdutil.finddate(ui, repo, date)
6154
6154
6155 # if we defined a bookmark, we have to remember the original name
6155 # if we defined a bookmark, we have to remember the original name
6156 brev = rev
6156 brev = rev
6157 if rev:
6157 if rev:
6158 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6158 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6159 ctx = scmutil.revsingle(repo, rev, default=None)
6159 ctx = scmutil.revsingle(repo, rev, default=None)
6160 rev = ctx.rev()
6160 rev = ctx.rev()
6161 hidden = ctx.hidden()
6161 hidden = ctx.hidden()
6162 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6162 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6163 with ui.configoverride(overrides, 'update'):
6163 with ui.configoverride(overrides, 'update'):
6164 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6164 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6165 updatecheck=updatecheck)
6165 updatecheck=updatecheck)
6166 if hidden:
6166 if hidden:
6167 ctxstr = ctx.hex()[:12]
6167 ctxstr = ctx.hex()[:12]
6168 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6168 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6169
6169
6170 if ctx.obsolete():
6170 if ctx.obsolete():
6171 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6171 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6172 ui.warn("(%s)\n" % obsfatemsg)
6172 ui.warn("(%s)\n" % obsfatemsg)
6173 return ret
6173 return ret
6174
6174
6175 @command('verify',
6175 @command('verify',
6176 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6176 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6177 helpcategory=command.CATEGORY_MAINTENANCE)
6177 helpcategory=command.CATEGORY_MAINTENANCE)
6178 def verify(ui, repo, **opts):
6178 def verify(ui, repo, **opts):
6179 """verify the integrity of the repository
6179 """verify the integrity of the repository
6180
6180
6181 Verify the integrity of the current repository.
6181 Verify the integrity of the current repository.
6182
6182
6183 This will perform an extensive check of the repository's
6183 This will perform an extensive check of the repository's
6184 integrity, validating the hashes and checksums of each entry in
6184 integrity, validating the hashes and checksums of each entry in
6185 the changelog, manifest, and tracked files, as well as the
6185 the changelog, manifest, and tracked files, as well as the
6186 integrity of their crosslinks and indices.
6186 integrity of their crosslinks and indices.
6187
6187
6188 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6188 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6189 for more information about recovery from corruption of the
6189 for more information about recovery from corruption of the
6190 repository.
6190 repository.
6191
6191
6192 Returns 0 on success, 1 if errors are encountered.
6192 Returns 0 on success, 1 if errors are encountered.
6193 """
6193 """
6194 opts = pycompat.byteskwargs(opts)
6194 opts = pycompat.byteskwargs(opts)
6195
6195
6196 level = None
6196 level = None
6197 if opts['full']:
6197 if opts['full']:
6198 level = verifymod.VERIFY_FULL
6198 level = verifymod.VERIFY_FULL
6199 return hg.verify(repo, level)
6199 return hg.verify(repo, level)
6200
6200
6201 @command(
6201 @command(
6202 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6202 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6203 norepo=True, intents={INTENT_READONLY})
6203 norepo=True, intents={INTENT_READONLY})
6204 def version_(ui, **opts):
6204 def version_(ui, **opts):
6205 """output version and copyright information
6205 """output version and copyright information
6206
6206
6207 .. container:: verbose
6207 .. container:: verbose
6208
6208
6209 Template:
6209 Template:
6210
6210
6211 The following keywords are supported. See also :hg:`help templates`.
6211 The following keywords are supported. See also :hg:`help templates`.
6212
6212
6213 :extensions: List of extensions.
6213 :extensions: List of extensions.
6214 :ver: String. Version number.
6214 :ver: String. Version number.
6215
6215
6216 And each entry of ``{extensions}`` provides the following sub-keywords
6216 And each entry of ``{extensions}`` provides the following sub-keywords
6217 in addition to ``{ver}``.
6217 in addition to ``{ver}``.
6218
6218
6219 :bundled: Boolean. True if included in the release.
6219 :bundled: Boolean. True if included in the release.
6220 :name: String. Extension name.
6220 :name: String. Extension name.
6221 """
6221 """
6222 opts = pycompat.byteskwargs(opts)
6222 opts = pycompat.byteskwargs(opts)
6223 if ui.verbose:
6223 if ui.verbose:
6224 ui.pager('version')
6224 ui.pager('version')
6225 fm = ui.formatter("version", opts)
6225 fm = ui.formatter("version", opts)
6226 fm.startitem()
6226 fm.startitem()
6227 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6227 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6228 util.version())
6228 util.version())
6229 license = _(
6229 license = _(
6230 "(see https://mercurial-scm.org for more information)\n"
6230 "(see https://mercurial-scm.org for more information)\n"
6231 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6231 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6232 "This is free software; see the source for copying conditions. "
6232 "This is free software; see the source for copying conditions. "
6233 "There is NO\nwarranty; "
6233 "There is NO\nwarranty; "
6234 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6234 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6235 )
6235 )
6236 if not ui.quiet:
6236 if not ui.quiet:
6237 fm.plain(license)
6237 fm.plain(license)
6238
6238
6239 if ui.verbose:
6239 if ui.verbose:
6240 fm.plain(_("\nEnabled extensions:\n\n"))
6240 fm.plain(_("\nEnabled extensions:\n\n"))
6241 # format names and versions into columns
6241 # format names and versions into columns
6242 names = []
6242 names = []
6243 vers = []
6243 vers = []
6244 isinternals = []
6244 isinternals = []
6245 for name, module in extensions.extensions():
6245 for name, module in extensions.extensions():
6246 names.append(name)
6246 names.append(name)
6247 vers.append(extensions.moduleversion(module) or None)
6247 vers.append(extensions.moduleversion(module) or None)
6248 isinternals.append(extensions.ismoduleinternal(module))
6248 isinternals.append(extensions.ismoduleinternal(module))
6249 fn = fm.nested("extensions", tmpl='{name}\n')
6249 fn = fm.nested("extensions", tmpl='{name}\n')
6250 if names:
6250 if names:
6251 namefmt = " %%-%ds " % max(len(n) for n in names)
6251 namefmt = " %%-%ds " % max(len(n) for n in names)
6252 places = [_("external"), _("internal")]
6252 places = [_("external"), _("internal")]
6253 for n, v, p in zip(names, vers, isinternals):
6253 for n, v, p in zip(names, vers, isinternals):
6254 fn.startitem()
6254 fn.startitem()
6255 fn.condwrite(ui.verbose, "name", namefmt, n)
6255 fn.condwrite(ui.verbose, "name", namefmt, n)
6256 if ui.verbose:
6256 if ui.verbose:
6257 fn.plain("%s " % places[p])
6257 fn.plain("%s " % places[p])
6258 fn.data(bundled=p)
6258 fn.data(bundled=p)
6259 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6259 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6260 if ui.verbose:
6260 if ui.verbose:
6261 fn.plain("\n")
6261 fn.plain("\n")
6262 fn.end()
6262 fn.end()
6263 fm.end()
6263 fm.end()
6264
6264
6265 def loadcmdtable(ui, name, cmdtable):
6265 def loadcmdtable(ui, name, cmdtable):
6266 """Load command functions from specified cmdtable
6266 """Load command functions from specified cmdtable
6267 """
6267 """
6268 overrides = [cmd for cmd in cmdtable if cmd in table]
6268 overrides = [cmd for cmd in cmdtable if cmd in table]
6269 if overrides:
6269 if overrides:
6270 ui.warn(_("extension '%s' overrides commands: %s\n")
6270 ui.warn(_("extension '%s' overrides commands: %s\n")
6271 % (name, " ".join(overrides)))
6271 % (name, " ".join(overrides)))
6272 table.update(cmdtable)
6272 table.update(cmdtable)
@@ -1,943 +1,931
1 # logcmdutil.py - utility for log-like commands
1 # logcmdutil.py - utility for log-like commands
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12 import posixpath
12 import posixpath
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 nullid,
16 nullid,
17 wdirid,
17 wdirid,
18 wdirrev,
18 wdirrev,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 dagop,
22 dagop,
23 error,
23 error,
24 formatter,
24 formatter,
25 graphmod,
25 graphmod,
26 match as matchmod,
26 match as matchmod,
27 mdiff,
27 mdiff,
28 patch,
28 patch,
29 pathutil,
29 pathutil,
30 pycompat,
30 pycompat,
31 revset,
31 revset,
32 revsetlang,
32 revsetlang,
33 scmutil,
33 scmutil,
34 smartset,
34 smartset,
35 templatekw,
35 templatekw,
36 templater,
36 templater,
37 util,
37 util,
38 )
38 )
39 from .utils import (
39 from .utils import (
40 dateutil,
40 dateutil,
41 stringutil,
41 stringutil,
42 )
42 )
43
43
44 def getlimit(opts):
44 def getlimit(opts):
45 """get the log limit according to option -l/--limit"""
45 """get the log limit according to option -l/--limit"""
46 limit = opts.get('limit')
46 limit = opts.get('limit')
47 if limit:
47 if limit:
48 try:
48 try:
49 limit = int(limit)
49 limit = int(limit)
50 except ValueError:
50 except ValueError:
51 raise error.Abort(_('limit must be a positive integer'))
51 raise error.Abort(_('limit must be a positive integer'))
52 if limit <= 0:
52 if limit <= 0:
53 raise error.Abort(_('limit must be positive'))
53 raise error.Abort(_('limit must be positive'))
54 else:
54 else:
55 limit = None
55 limit = None
56 return limit
56 return limit
57
57
58 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
58 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
59 changes=None, stat=False, fp=None, graphwidth=0,
59 changes=None, stat=False, fp=None, graphwidth=0,
60 prefix='', root='', listsubrepos=False, hunksfilterfn=None):
60 prefix='', root='', listsubrepos=False, hunksfilterfn=None):
61 '''show diff or diffstat.'''
61 '''show diff or diffstat.'''
62 ctx1 = repo[node1]
62 ctx1 = repo[node1]
63 ctx2 = repo[node2]
63 ctx2 = repo[node2]
64 if root:
64 if root:
65 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
65 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
66 else:
66 else:
67 relroot = ''
67 relroot = ''
68 copysourcematch = None
68 copysourcematch = None
69 def compose(f, g):
69 def compose(f, g):
70 return lambda x: f(g(x))
70 return lambda x: f(g(x))
71 def pathfn(f):
71 def pathfn(f):
72 return posixpath.join(prefix, f)
72 return posixpath.join(prefix, f)
73 if relroot != '':
73 if relroot != '':
74 # XXX relative roots currently don't work if the root is within a
74 # XXX relative roots currently don't work if the root is within a
75 # subrepo
75 # subrepo
76 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
76 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
77 uirelroot = uipathfn(pathfn(relroot))
77 uirelroot = uipathfn(pathfn(relroot))
78 relroot += '/'
78 relroot += '/'
79 for matchroot in match.files():
79 for matchroot in match.files():
80 if not matchroot.startswith(relroot):
80 if not matchroot.startswith(relroot):
81 ui.warn(_('warning: %s not inside relative root %s\n') %
81 ui.warn(_('warning: %s not inside relative root %s\n') %
82 (uipathfn(pathfn(matchroot)), uirelroot))
82 (uipathfn(pathfn(matchroot)), uirelroot))
83
83
84 relrootmatch = scmutil.match(ctx2, pats=[relroot], default='path')
84 relrootmatch = scmutil.match(ctx2, pats=[relroot], default='path')
85 match = matchmod.intersectmatchers(match, relrootmatch)
85 match = matchmod.intersectmatchers(match, relrootmatch)
86 copysourcematch = relrootmatch
86 copysourcematch = relrootmatch
87
87
88 checkroot = (repo.ui.configbool('devel', 'all-warnings') or
88 checkroot = (repo.ui.configbool('devel', 'all-warnings') or
89 repo.ui.configbool('devel', 'check-relroot'))
89 repo.ui.configbool('devel', 'check-relroot'))
90 def relrootpathfn(f):
90 def relrootpathfn(f):
91 if checkroot and not f.startswith(relroot):
91 if checkroot and not f.startswith(relroot):
92 raise AssertionError(
92 raise AssertionError(
93 "file %s doesn't start with relroot %s" % (f, relroot))
93 "file %s doesn't start with relroot %s" % (f, relroot))
94 return f[len(relroot):]
94 return f[len(relroot):]
95 pathfn = compose(relrootpathfn, pathfn)
95 pathfn = compose(relrootpathfn, pathfn)
96
96
97 if stat:
97 if stat:
98 diffopts = diffopts.copy(context=0, noprefix=False)
98 diffopts = diffopts.copy(context=0, noprefix=False)
99 width = 80
99 width = 80
100 if not ui.plain():
100 if not ui.plain():
101 width = ui.termwidth() - graphwidth
101 width = ui.termwidth() - graphwidth
102 # If an explicit --root was given, don't respect ui.relative-paths
102 # If an explicit --root was given, don't respect ui.relative-paths
103 if not relroot:
103 if not relroot:
104 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
104 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
105
105
106 chunks = ctx2.diff(ctx1, match, changes, opts=diffopts, pathfn=pathfn,
106 chunks = ctx2.diff(ctx1, match, changes, opts=diffopts, pathfn=pathfn,
107 copysourcematch=copysourcematch,
107 copysourcematch=copysourcematch,
108 hunksfilterfn=hunksfilterfn)
108 hunksfilterfn=hunksfilterfn)
109
109
110 if fp is not None or ui.canwritewithoutlabels():
110 if fp is not None or ui.canwritewithoutlabels():
111 out = fp or ui
111 out = fp or ui
112 if stat:
112 if stat:
113 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
113 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
114 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
114 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
115 out.write(chunk)
115 out.write(chunk)
116 else:
116 else:
117 if stat:
117 if stat:
118 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
118 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
119 else:
119 else:
120 chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks,
120 chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks,
121 opts=diffopts)
121 opts=diffopts)
122 if ui.canbatchlabeledwrites():
122 if ui.canbatchlabeledwrites():
123 def gen():
123 def gen():
124 for chunk, label in chunks:
124 for chunk, label in chunks:
125 yield ui.label(chunk, label=label)
125 yield ui.label(chunk, label=label)
126 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
126 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
127 ui.write(chunk)
127 ui.write(chunk)
128 else:
128 else:
129 for chunk, label in chunks:
129 for chunk, label in chunks:
130 ui.write(chunk, label=label)
130 ui.write(chunk, label=label)
131
131
132 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
132 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
133 tempnode2 = node2
133 tempnode2 = node2
134 try:
134 try:
135 if node2 is not None:
135 if node2 is not None:
136 tempnode2 = ctx2.substate[subpath][1]
136 tempnode2 = ctx2.substate[subpath][1]
137 except KeyError:
137 except KeyError:
138 # A subrepo that existed in node1 was deleted between node1 and
138 # A subrepo that existed in node1 was deleted between node1 and
139 # node2 (inclusive). Thus, ctx2's substate won't contain that
139 # node2 (inclusive). Thus, ctx2's substate won't contain that
140 # subpath. The best we can do is to ignore it.
140 # subpath. The best we can do is to ignore it.
141 tempnode2 = None
141 tempnode2 = None
142 submatch = matchmod.subdirmatcher(subpath, match)
142 submatch = matchmod.subdirmatcher(subpath, match)
143 subprefix = repo.wvfs.reljoin(prefix, subpath)
143 subprefix = repo.wvfs.reljoin(prefix, subpath)
144 if listsubrepos or match.exact(subpath) or any(submatch.files()):
144 if listsubrepos or match.exact(subpath) or any(submatch.files()):
145 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
145 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
146 stat=stat, fp=fp, prefix=subprefix)
146 stat=stat, fp=fp, prefix=subprefix)
147
147
148 class changesetdiffer(object):
148 class changesetdiffer(object):
149 """Generate diff of changeset with pre-configured filtering functions"""
149 """Generate diff of changeset with pre-configured filtering functions"""
150
150
151 def _makefilematcher(self, ctx):
151 def _makefilematcher(self, ctx):
152 return scmutil.matchall(ctx.repo())
152 return scmutil.matchall(ctx.repo())
153
153
154 def _makehunksfilter(self, ctx):
154 def _makehunksfilter(self, ctx):
155 return None
155 return None
156
156
157 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
157 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
158 repo = ctx.repo()
158 repo = ctx.repo()
159 node = ctx.node()
159 node = ctx.node()
160 prev = ctx.p1().node()
160 prev = ctx.p1().node()
161 diffordiffstat(ui, repo, diffopts, prev, node,
161 diffordiffstat(ui, repo, diffopts, prev, node,
162 match=self._makefilematcher(ctx), stat=stat,
162 match=self._makefilematcher(ctx), stat=stat,
163 graphwidth=graphwidth,
163 graphwidth=graphwidth,
164 hunksfilterfn=self._makehunksfilter(ctx))
164 hunksfilterfn=self._makehunksfilter(ctx))
165
165
166 def changesetlabels(ctx):
166 def changesetlabels(ctx):
167 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
167 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
168 if ctx.obsolete():
168 if ctx.obsolete():
169 labels.append('changeset.obsolete')
169 labels.append('changeset.obsolete')
170 if ctx.isunstable():
170 if ctx.isunstable():
171 labels.append('changeset.unstable')
171 labels.append('changeset.unstable')
172 for instability in ctx.instabilities():
172 for instability in ctx.instabilities():
173 labels.append('instability.%s' % instability)
173 labels.append('instability.%s' % instability)
174 return ' '.join(labels)
174 return ' '.join(labels)
175
175
176 class changesetprinter(object):
176 class changesetprinter(object):
177 '''show changeset information when templating not requested.'''
177 '''show changeset information when templating not requested.'''
178
178
179 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
179 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
180 self.ui = ui
180 self.ui = ui
181 self.repo = repo
181 self.repo = repo
182 self.buffered = buffered
182 self.buffered = buffered
183 self._differ = differ or changesetdiffer()
183 self._differ = differ or changesetdiffer()
184 self._diffopts = patch.diffallopts(ui, diffopts)
184 self._diffopts = patch.diffallopts(ui, diffopts)
185 self._includestat = diffopts and diffopts.get('stat')
185 self._includestat = diffopts and diffopts.get('stat')
186 self._includediff = diffopts and diffopts.get('patch')
186 self._includediff = diffopts and diffopts.get('patch')
187 self.header = {}
187 self.header = {}
188 self.hunk = {}
188 self.hunk = {}
189 self.lastheader = None
189 self.lastheader = None
190 self.footer = None
190 self.footer = None
191 self._columns = templatekw.getlogcolumns()
191 self._columns = templatekw.getlogcolumns()
192
192
193 def flush(self, ctx):
193 def flush(self, ctx):
194 rev = ctx.rev()
194 rev = ctx.rev()
195 if rev in self.header:
195 if rev in self.header:
196 h = self.header[rev]
196 h = self.header[rev]
197 if h != self.lastheader:
197 if h != self.lastheader:
198 self.lastheader = h
198 self.lastheader = h
199 self.ui.write(h)
199 self.ui.write(h)
200 del self.header[rev]
200 del self.header[rev]
201 if rev in self.hunk:
201 if rev in self.hunk:
202 self.ui.write(self.hunk[rev])
202 self.ui.write(self.hunk[rev])
203 del self.hunk[rev]
203 del self.hunk[rev]
204
204
205 def close(self):
205 def close(self):
206 if self.footer:
206 if self.footer:
207 self.ui.write(self.footer)
207 self.ui.write(self.footer)
208
208
209 def show(self, ctx, copies=None, **props):
209 def show(self, ctx, copies=None, **props):
210 props = pycompat.byteskwargs(props)
210 props = pycompat.byteskwargs(props)
211 if self.buffered:
211 if self.buffered:
212 self.ui.pushbuffer(labeled=True)
212 self.ui.pushbuffer(labeled=True)
213 self._show(ctx, copies, props)
213 self._show(ctx, copies, props)
214 self.hunk[ctx.rev()] = self.ui.popbuffer()
214 self.hunk[ctx.rev()] = self.ui.popbuffer()
215 else:
215 else:
216 self._show(ctx, copies, props)
216 self._show(ctx, copies, props)
217
217
218 def _show(self, ctx, copies, props):
218 def _show(self, ctx, copies, props):
219 '''show a single changeset or file revision'''
219 '''show a single changeset or file revision'''
220 changenode = ctx.node()
220 changenode = ctx.node()
221 graphwidth = props.get('graphwidth', 0)
221 graphwidth = props.get('graphwidth', 0)
222
222
223 if self.ui.quiet:
223 if self.ui.quiet:
224 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
224 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
225 label='log.node')
225 label='log.node')
226 return
226 return
227
227
228 columns = self._columns
228 columns = self._columns
229 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
229 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
230 label=changesetlabels(ctx))
230 label=changesetlabels(ctx))
231
231
232 # branches are shown first before any other names due to backwards
232 # branches are shown first before any other names due to backwards
233 # compatibility
233 # compatibility
234 branch = ctx.branch()
234 branch = ctx.branch()
235 # don't show the default branch name
235 # don't show the default branch name
236 if branch != 'default':
236 if branch != 'default':
237 self.ui.write(columns['branch'] % branch, label='log.branch')
237 self.ui.write(columns['branch'] % branch, label='log.branch')
238
238
239 for nsname, ns in self.repo.names.iteritems():
239 for nsname, ns in self.repo.names.iteritems():
240 # branches has special logic already handled above, so here we just
240 # branches has special logic already handled above, so here we just
241 # skip it
241 # skip it
242 if nsname == 'branches':
242 if nsname == 'branches':
243 continue
243 continue
244 # we will use the templatename as the color name since those two
244 # we will use the templatename as the color name since those two
245 # should be the same
245 # should be the same
246 for name in ns.names(self.repo, changenode):
246 for name in ns.names(self.repo, changenode):
247 self.ui.write(ns.logfmt % name,
247 self.ui.write(ns.logfmt % name,
248 label='log.%s' % ns.colorname)
248 label='log.%s' % ns.colorname)
249 if self.ui.debugflag:
249 if self.ui.debugflag:
250 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
250 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
251 for pctx in scmutil.meaningfulparents(self.repo, ctx):
251 for pctx in scmutil.meaningfulparents(self.repo, ctx):
252 label = 'log.parent changeset.%s' % pctx.phasestr()
252 label = 'log.parent changeset.%s' % pctx.phasestr()
253 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
253 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
254 label=label)
254 label=label)
255
255
256 if self.ui.debugflag:
256 if self.ui.debugflag:
257 mnode = ctx.manifestnode()
257 mnode = ctx.manifestnode()
258 if mnode is None:
258 if mnode is None:
259 mnode = wdirid
259 mnode = wdirid
260 mrev = wdirrev
260 mrev = wdirrev
261 else:
261 else:
262 mrev = self.repo.manifestlog.rev(mnode)
262 mrev = self.repo.manifestlog.rev(mnode)
263 self.ui.write(columns['manifest']
263 self.ui.write(columns['manifest']
264 % scmutil.formatrevnode(self.ui, mrev, mnode),
264 % scmutil.formatrevnode(self.ui, mrev, mnode),
265 label='ui.debug log.manifest')
265 label='ui.debug log.manifest')
266 self.ui.write(columns['user'] % ctx.user(), label='log.user')
266 self.ui.write(columns['user'] % ctx.user(), label='log.user')
267 self.ui.write(columns['date'] % dateutil.datestr(ctx.date()),
267 self.ui.write(columns['date'] % dateutil.datestr(ctx.date()),
268 label='log.date')
268 label='log.date')
269
269
270 if ctx.isunstable():
270 if ctx.isunstable():
271 instabilities = ctx.instabilities()
271 instabilities = ctx.instabilities()
272 self.ui.write(columns['instability'] % ', '.join(instabilities),
272 self.ui.write(columns['instability'] % ', '.join(instabilities),
273 label='log.instability')
273 label='log.instability')
274
274
275 elif ctx.obsolete():
275 elif ctx.obsolete():
276 self._showobsfate(ctx)
276 self._showobsfate(ctx)
277
277
278 self._exthook(ctx)
278 self._exthook(ctx)
279
279
280 if self.ui.debugflag:
280 if self.ui.debugflag:
281 files = ctx.p1().status(ctx)[:3]
281 files = ctx.p1().status(ctx)[:3]
282 for key, value in zip(['files', 'files+', 'files-'], files):
282 for key, value in zip(['files', 'files+', 'files-'], files):
283 if value:
283 if value:
284 self.ui.write(columns[key] % " ".join(value),
284 self.ui.write(columns[key] % " ".join(value),
285 label='ui.debug log.files')
285 label='ui.debug log.files')
286 elif ctx.files() and self.ui.verbose:
286 elif ctx.files() and self.ui.verbose:
287 self.ui.write(columns['files'] % " ".join(ctx.files()),
287 self.ui.write(columns['files'] % " ".join(ctx.files()),
288 label='ui.note log.files')
288 label='ui.note log.files')
289 if copies and self.ui.verbose:
289 if copies and self.ui.verbose:
290 copies = ['%s (%s)' % c for c in copies]
290 copies = ['%s (%s)' % c for c in copies]
291 self.ui.write(columns['copies'] % ' '.join(copies),
291 self.ui.write(columns['copies'] % ' '.join(copies),
292 label='ui.note log.copies')
292 label='ui.note log.copies')
293
293
294 extra = ctx.extra()
294 extra = ctx.extra()
295 if extra and self.ui.debugflag:
295 if extra and self.ui.debugflag:
296 for key, value in sorted(extra.items()):
296 for key, value in sorted(extra.items()):
297 self.ui.write(columns['extra']
297 self.ui.write(columns['extra']
298 % (key, stringutil.escapestr(value)),
298 % (key, stringutil.escapestr(value)),
299 label='ui.debug log.extra')
299 label='ui.debug log.extra')
300
300
301 description = ctx.description().strip()
301 description = ctx.description().strip()
302 if description:
302 if description:
303 if self.ui.verbose:
303 if self.ui.verbose:
304 self.ui.write(_("description:\n"),
304 self.ui.write(_("description:\n"),
305 label='ui.note log.description')
305 label='ui.note log.description')
306 self.ui.write(description,
306 self.ui.write(description,
307 label='ui.note log.description')
307 label='ui.note log.description')
308 self.ui.write("\n\n")
308 self.ui.write("\n\n")
309 else:
309 else:
310 self.ui.write(columns['summary'] % description.splitlines()[0],
310 self.ui.write(columns['summary'] % description.splitlines()[0],
311 label='log.summary')
311 label='log.summary')
312 self.ui.write("\n")
312 self.ui.write("\n")
313
313
314 self._showpatch(ctx, graphwidth)
314 self._showpatch(ctx, graphwidth)
315
315
316 def _showobsfate(self, ctx):
316 def _showobsfate(self, ctx):
317 # TODO: do not depend on templater
317 # TODO: do not depend on templater
318 tres = formatter.templateresources(self.repo.ui, self.repo)
318 tres = formatter.templateresources(self.repo.ui, self.repo)
319 t = formatter.maketemplater(self.repo.ui, '{join(obsfate, "\n")}',
319 t = formatter.maketemplater(self.repo.ui, '{join(obsfate, "\n")}',
320 defaults=templatekw.keywords,
320 defaults=templatekw.keywords,
321 resources=tres)
321 resources=tres)
322 obsfate = t.renderdefault({'ctx': ctx}).splitlines()
322 obsfate = t.renderdefault({'ctx': ctx}).splitlines()
323
323
324 if obsfate:
324 if obsfate:
325 for obsfateline in obsfate:
325 for obsfateline in obsfate:
326 self.ui.write(self._columns['obsolete'] % obsfateline,
326 self.ui.write(self._columns['obsolete'] % obsfateline,
327 label='log.obsfate')
327 label='log.obsfate')
328
328
329 def _exthook(self, ctx):
329 def _exthook(self, ctx):
330 '''empty method used by extension as a hook point
330 '''empty method used by extension as a hook point
331 '''
331 '''
332
332
333 def _showpatch(self, ctx, graphwidth=0):
333 def _showpatch(self, ctx, graphwidth=0):
334 if self._includestat:
334 if self._includestat:
335 self._differ.showdiff(self.ui, ctx, self._diffopts,
335 self._differ.showdiff(self.ui, ctx, self._diffopts,
336 graphwidth, stat=True)
336 graphwidth, stat=True)
337 if self._includestat and self._includediff:
337 if self._includestat and self._includediff:
338 self.ui.write("\n")
338 self.ui.write("\n")
339 if self._includediff:
339 if self._includediff:
340 self._differ.showdiff(self.ui, ctx, self._diffopts,
340 self._differ.showdiff(self.ui, ctx, self._diffopts,
341 graphwidth, stat=False)
341 graphwidth, stat=False)
342 if self._includestat or self._includediff:
342 if self._includestat or self._includediff:
343 self.ui.write("\n")
343 self.ui.write("\n")
344
344
345 class changesetformatter(changesetprinter):
345 class changesetformatter(changesetprinter):
346 """Format changeset information by generic formatter"""
346 """Format changeset information by generic formatter"""
347
347
348 def __init__(self, ui, repo, fm, differ=None, diffopts=None,
348 def __init__(self, ui, repo, fm, differ=None, diffopts=None,
349 buffered=False):
349 buffered=False):
350 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
350 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
351 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
351 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
352 self._fm = fm
352 self._fm = fm
353
353
354 def close(self):
354 def close(self):
355 self._fm.end()
355 self._fm.end()
356
356
357 def _show(self, ctx, copies, props):
357 def _show(self, ctx, copies, props):
358 '''show a single changeset or file revision'''
358 '''show a single changeset or file revision'''
359 fm = self._fm
359 fm = self._fm
360 fm.startitem()
360 fm.startitem()
361 fm.context(ctx=ctx)
361 fm.context(ctx=ctx)
362 fm.data(rev=scmutil.intrev(ctx),
362 fm.data(rev=scmutil.intrev(ctx),
363 node=fm.hexfunc(scmutil.binnode(ctx)))
363 node=fm.hexfunc(scmutil.binnode(ctx)))
364
364
365 if self.ui.quiet:
365 if self.ui.quiet:
366 return
366 return
367
367
368 fm.data(branch=ctx.branch(),
368 fm.data(branch=ctx.branch(),
369 phase=ctx.phasestr(),
369 phase=ctx.phasestr(),
370 user=ctx.user(),
370 user=ctx.user(),
371 date=fm.formatdate(ctx.date()),
371 date=fm.formatdate(ctx.date()),
372 desc=ctx.description(),
372 desc=ctx.description(),
373 bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'),
373 bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'),
374 tags=fm.formatlist(ctx.tags(), name='tag'),
374 tags=fm.formatlist(ctx.tags(), name='tag'),
375 parents=fm.formatlist([fm.hexfunc(c.node())
375 parents=fm.formatlist([fm.hexfunc(c.node())
376 for c in ctx.parents()], name='node'))
376 for c in ctx.parents()], name='node'))
377
377
378 if self.ui.debugflag:
378 if self.ui.debugflag:
379 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid),
379 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid),
380 extra=fm.formatdict(ctx.extra()))
380 extra=fm.formatdict(ctx.extra()))
381
381
382 files = ctx.p1().status(ctx)
382 files = ctx.p1().status(ctx)
383 fm.data(modified=fm.formatlist(files[0], name='file'),
383 fm.data(modified=fm.formatlist(files[0], name='file'),
384 added=fm.formatlist(files[1], name='file'),
384 added=fm.formatlist(files[1], name='file'),
385 removed=fm.formatlist(files[2], name='file'))
385 removed=fm.formatlist(files[2], name='file'))
386
386
387 elif self.ui.verbose:
387 elif self.ui.verbose:
388 fm.data(files=fm.formatlist(ctx.files(), name='file'))
388 fm.data(files=fm.formatlist(ctx.files(), name='file'))
389 if copies:
389 if copies:
390 fm.data(copies=fm.formatdict(copies,
390 fm.data(copies=fm.formatdict(copies,
391 key='name', value='source'))
391 key='name', value='source'))
392
392
393 if self._includestat:
393 if self._includestat:
394 self.ui.pushbuffer()
394 self.ui.pushbuffer()
395 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
395 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
396 fm.data(diffstat=self.ui.popbuffer())
396 fm.data(diffstat=self.ui.popbuffer())
397 if self._includediff:
397 if self._includediff:
398 self.ui.pushbuffer()
398 self.ui.pushbuffer()
399 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
399 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
400 fm.data(diff=self.ui.popbuffer())
400 fm.data(diff=self.ui.popbuffer())
401
401
402 class changesettemplater(changesetprinter):
402 class changesettemplater(changesetprinter):
403 '''format changeset information.
403 '''format changeset information.
404
404
405 Note: there are a variety of convenience functions to build a
405 Note: there are a variety of convenience functions to build a
406 changesettemplater for common cases. See functions such as:
406 changesettemplater for common cases. See functions such as:
407 maketemplater, changesetdisplayer, buildcommittemplate, or other
407 maketemplater, changesetdisplayer, buildcommittemplate, or other
408 functions that use changesest_templater.
408 functions that use changesest_templater.
409 '''
409 '''
410
410
411 # Arguments before "buffered" used to be positional. Consider not
411 # Arguments before "buffered" used to be positional. Consider not
412 # adding/removing arguments before "buffered" to not break callers.
412 # adding/removing arguments before "buffered" to not break callers.
413 def __init__(self, ui, repo, tmplspec, differ=None, diffopts=None,
413 def __init__(self, ui, repo, tmplspec, differ=None, diffopts=None,
414 buffered=False):
414 buffered=False):
415 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
415 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
416 # tres is shared with _graphnodeformatter()
416 # tres is shared with _graphnodeformatter()
417 self._tresources = tres = formatter.templateresources(ui, repo)
417 self._tresources = tres = formatter.templateresources(ui, repo)
418 self.t = formatter.loadtemplater(ui, tmplspec,
418 self.t = formatter.loadtemplater(ui, tmplspec,
419 defaults=templatekw.keywords,
419 defaults=templatekw.keywords,
420 resources=tres,
420 resources=tres,
421 cache=templatekw.defaulttempl)
421 cache=templatekw.defaulttempl)
422 self._counter = itertools.count()
422 self._counter = itertools.count()
423
423
424 self._tref = tmplspec.ref
424 self._tref = tmplspec.ref
425 self._parts = {'header': '', 'footer': '',
425 self._parts = {'header': '', 'footer': '',
426 tmplspec.ref: tmplspec.ref,
426 tmplspec.ref: tmplspec.ref,
427 'docheader': '', 'docfooter': '',
427 'docheader': '', 'docfooter': '',
428 'separator': ''}
428 'separator': ''}
429 if tmplspec.mapfile:
429 if tmplspec.mapfile:
430 # find correct templates for current mode, for backward
430 # find correct templates for current mode, for backward
431 # compatibility with 'log -v/-q/--debug' using a mapfile
431 # compatibility with 'log -v/-q/--debug' using a mapfile
432 tmplmodes = [
432 tmplmodes = [
433 (True, ''),
433 (True, ''),
434 (self.ui.verbose, '_verbose'),
434 (self.ui.verbose, '_verbose'),
435 (self.ui.quiet, '_quiet'),
435 (self.ui.quiet, '_quiet'),
436 (self.ui.debugflag, '_debug'),
436 (self.ui.debugflag, '_debug'),
437 ]
437 ]
438 for mode, postfix in tmplmodes:
438 for mode, postfix in tmplmodes:
439 for t in self._parts:
439 for t in self._parts:
440 cur = t + postfix
440 cur = t + postfix
441 if mode and cur in self.t:
441 if mode and cur in self.t:
442 self._parts[t] = cur
442 self._parts[t] = cur
443 else:
443 else:
444 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
444 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
445 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
445 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
446 self._parts.update(m)
446 self._parts.update(m)
447
447
448 if self._parts['docheader']:
448 if self._parts['docheader']:
449 self.ui.write(self.t.render(self._parts['docheader'], {}))
449 self.ui.write(self.t.render(self._parts['docheader'], {}))
450
450
451 def close(self):
451 def close(self):
452 if self._parts['docfooter']:
452 if self._parts['docfooter']:
453 if not self.footer:
453 if not self.footer:
454 self.footer = ""
454 self.footer = ""
455 self.footer += self.t.render(self._parts['docfooter'], {})
455 self.footer += self.t.render(self._parts['docfooter'], {})
456 return super(changesettemplater, self).close()
456 return super(changesettemplater, self).close()
457
457
458 def _show(self, ctx, copies, props):
458 def _show(self, ctx, copies, props):
459 '''show a single changeset or file revision'''
459 '''show a single changeset or file revision'''
460 props = props.copy()
460 props = props.copy()
461 props['ctx'] = ctx
461 props['ctx'] = ctx
462 props['index'] = index = next(self._counter)
462 props['index'] = index = next(self._counter)
463 props['revcache'] = {'copies': copies}
463 props['revcache'] = {'copies': copies}
464 graphwidth = props.get('graphwidth', 0)
464 graphwidth = props.get('graphwidth', 0)
465
465
466 # write separator, which wouldn't work well with the header part below
466 # write separator, which wouldn't work well with the header part below
467 # since there's inherently a conflict between header (across items) and
467 # since there's inherently a conflict between header (across items) and
468 # separator (per item)
468 # separator (per item)
469 if self._parts['separator'] and index > 0:
469 if self._parts['separator'] and index > 0:
470 self.ui.write(self.t.render(self._parts['separator'], {}))
470 self.ui.write(self.t.render(self._parts['separator'], {}))
471
471
472 # write header
472 # write header
473 if self._parts['header']:
473 if self._parts['header']:
474 h = self.t.render(self._parts['header'], props)
474 h = self.t.render(self._parts['header'], props)
475 if self.buffered:
475 if self.buffered:
476 self.header[ctx.rev()] = h
476 self.header[ctx.rev()] = h
477 else:
477 else:
478 if self.lastheader != h:
478 if self.lastheader != h:
479 self.lastheader = h
479 self.lastheader = h
480 self.ui.write(h)
480 self.ui.write(h)
481
481
482 # write changeset metadata, then patch if requested
482 # write changeset metadata, then patch if requested
483 key = self._parts[self._tref]
483 key = self._parts[self._tref]
484 self.ui.write(self.t.render(key, props))
484 self.ui.write(self.t.render(key, props))
485 self._showpatch(ctx, graphwidth)
485 self._showpatch(ctx, graphwidth)
486
486
487 if self._parts['footer']:
487 if self._parts['footer']:
488 if not self.footer:
488 if not self.footer:
489 self.footer = self.t.render(self._parts['footer'], props)
489 self.footer = self.t.render(self._parts['footer'], props)
490
490
491 def templatespec(tmpl, mapfile):
491 def templatespec(tmpl, mapfile):
492 if pycompat.ispy3:
492 if pycompat.ispy3:
493 assert not isinstance(tmpl, str), 'tmpl must not be a str'
493 assert not isinstance(tmpl, str), 'tmpl must not be a str'
494 if mapfile:
494 if mapfile:
495 return formatter.templatespec('changeset', tmpl, mapfile)
495 return formatter.templatespec('changeset', tmpl, mapfile)
496 else:
496 else:
497 return formatter.templatespec('', tmpl, None)
497 return formatter.templatespec('', tmpl, None)
498
498
499 def _lookuptemplate(ui, tmpl, style):
499 def _lookuptemplate(ui, tmpl, style):
500 """Find the template matching the given template spec or style
500 """Find the template matching the given template spec or style
501
501
502 See formatter.lookuptemplate() for details.
502 See formatter.lookuptemplate() for details.
503 """
503 """
504
504
505 # ui settings
505 # ui settings
506 if not tmpl and not style: # template are stronger than style
506 if not tmpl and not style: # template are stronger than style
507 tmpl = ui.config('ui', 'logtemplate')
507 tmpl = ui.config('ui', 'logtemplate')
508 if tmpl:
508 if tmpl:
509 return templatespec(templater.unquotestring(tmpl), None)
509 return templatespec(templater.unquotestring(tmpl), None)
510 else:
510 else:
511 style = util.expandpath(ui.config('ui', 'style'))
511 style = util.expandpath(ui.config('ui', 'style'))
512
512
513 if not tmpl and style:
513 if not tmpl and style:
514 mapfile = style
514 mapfile = style
515 if not os.path.split(mapfile)[0]:
515 if not os.path.split(mapfile)[0]:
516 mapname = (templater.templatepath('map-cmdline.' + mapfile)
516 mapname = (templater.templatepath('map-cmdline.' + mapfile)
517 or templater.templatepath(mapfile))
517 or templater.templatepath(mapfile))
518 if mapname:
518 if mapname:
519 mapfile = mapname
519 mapfile = mapname
520 return templatespec(None, mapfile)
520 return templatespec(None, mapfile)
521
521
522 if not tmpl:
522 if not tmpl:
523 return templatespec(None, None)
523 return templatespec(None, None)
524
524
525 return formatter.lookuptemplate(ui, 'changeset', tmpl)
525 return formatter.lookuptemplate(ui, 'changeset', tmpl)
526
526
527 def maketemplater(ui, repo, tmpl, buffered=False):
527 def maketemplater(ui, repo, tmpl, buffered=False):
528 """Create a changesettemplater from a literal template 'tmpl'
528 """Create a changesettemplater from a literal template 'tmpl'
529 byte-string."""
529 byte-string."""
530 spec = templatespec(tmpl, None)
530 spec = templatespec(tmpl, None)
531 return changesettemplater(ui, repo, spec, buffered=buffered)
531 return changesettemplater(ui, repo, spec, buffered=buffered)
532
532
533 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
533 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
534 """show one changeset using template or regular display.
534 """show one changeset using template or regular display.
535
535
536 Display format will be the first non-empty hit of:
536 Display format will be the first non-empty hit of:
537 1. option 'template'
537 1. option 'template'
538 2. option 'style'
538 2. option 'style'
539 3. [ui] setting 'logtemplate'
539 3. [ui] setting 'logtemplate'
540 4. [ui] setting 'style'
540 4. [ui] setting 'style'
541 If all of these values are either the unset or the empty string,
541 If all of these values are either the unset or the empty string,
542 regular display via changesetprinter() is done.
542 regular display via changesetprinter() is done.
543 """
543 """
544 postargs = (differ, opts, buffered)
544 postargs = (differ, opts, buffered)
545 if opts.get('template') in {'cbor', 'json'}:
545 if opts.get('template') in {'cbor', 'json'}:
546 fm = ui.formatter('log', opts)
546 fm = ui.formatter('log', opts)
547 return changesetformatter(ui, repo, fm, *postargs)
547 return changesetformatter(ui, repo, fm, *postargs)
548
548
549 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
549 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
550
550
551 if not spec.ref and not spec.tmpl and not spec.mapfile:
551 if not spec.ref and not spec.tmpl and not spec.mapfile:
552 return changesetprinter(ui, repo, *postargs)
552 return changesetprinter(ui, repo, *postargs)
553
553
554 return changesettemplater(ui, repo, spec, *postargs)
554 return changesettemplater(ui, repo, spec, *postargs)
555
555
556 def _makematcher(repo, revs, pats, opts):
556 def _makematcher(repo, revs, pats, opts):
557 """Build matcher and expanded patterns from log options
557 """Build matcher and expanded patterns from log options
558
558
559 If --follow, revs are the revisions to follow from.
559 If --follow, revs are the revisions to follow from.
560
560
561 Returns (match, pats, slowpath) where
561 Returns (match, pats, slowpath) where
562 - match: a matcher built from the given pats and -I/-X opts
562 - match: a matcher built from the given pats and -I/-X opts
563 - pats: patterns used (globs are expanded on Windows)
563 - pats: patterns used (globs are expanded on Windows)
564 - slowpath: True if patterns aren't as simple as scanning filelogs
564 - slowpath: True if patterns aren't as simple as scanning filelogs
565 """
565 """
566 # pats/include/exclude are passed to match.match() directly in
566 # pats/include/exclude are passed to match.match() directly in
567 # _matchfiles() revset but walkchangerevs() builds its matcher with
567 # _matchfiles() revset but walkchangerevs() builds its matcher with
568 # scmutil.match(). The difference is input pats are globbed on
568 # scmutil.match(). The difference is input pats are globbed on
569 # platforms without shell expansion (windows).
569 # platforms without shell expansion (windows).
570 wctx = repo[None]
570 wctx = repo[None]
571 match, pats = scmutil.matchandpats(wctx, pats, opts)
571 match, pats = scmutil.matchandpats(wctx, pats, opts)
572 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
572 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
573 if not slowpath:
573 if not slowpath:
574 follow = opts.get('follow') or opts.get('follow_first')
574 follow = opts.get('follow') or opts.get('follow_first')
575 startctxs = []
575 startctxs = []
576 if follow and opts.get('rev'):
576 if follow and opts.get('rev'):
577 startctxs = [repo[r] for r in revs]
577 startctxs = [repo[r] for r in revs]
578 for f in match.files():
578 for f in match.files():
579 if follow and startctxs:
579 if follow and startctxs:
580 # No idea if the path was a directory at that revision, so
580 # No idea if the path was a directory at that revision, so
581 # take the slow path.
581 # take the slow path.
582 if any(f not in c for c in startctxs):
582 if any(f not in c for c in startctxs):
583 slowpath = True
583 slowpath = True
584 continue
584 continue
585 elif follow and f not in wctx:
585 elif follow and f not in wctx:
586 # If the file exists, it may be a directory, so let it
586 # If the file exists, it may be a directory, so let it
587 # take the slow path.
587 # take the slow path.
588 if os.path.exists(repo.wjoin(f)):
588 if os.path.exists(repo.wjoin(f)):
589 slowpath = True
589 slowpath = True
590 continue
590 continue
591 else:
591 else:
592 raise error.Abort(_('cannot follow file not in parent '
592 raise error.Abort(_('cannot follow file not in parent '
593 'revision: "%s"') % f)
593 'revision: "%s"') % f)
594 filelog = repo.file(f)
594 filelog = repo.file(f)
595 if not filelog:
595 if not filelog:
596 # A zero count may be a directory or deleted file, so
596 # A zero count may be a directory or deleted file, so
597 # try to find matching entries on the slow path.
597 # try to find matching entries on the slow path.
598 if follow:
598 if follow:
599 raise error.Abort(
599 raise error.Abort(
600 _('cannot follow nonexistent file: "%s"') % f)
600 _('cannot follow nonexistent file: "%s"') % f)
601 slowpath = True
601 slowpath = True
602
602
603 # We decided to fall back to the slowpath because at least one
603 # We decided to fall back to the slowpath because at least one
604 # of the paths was not a file. Check to see if at least one of them
604 # of the paths was not a file. Check to see if at least one of them
605 # existed in history - in that case, we'll continue down the
605 # existed in history - in that case, we'll continue down the
606 # slowpath; otherwise, we can turn off the slowpath
606 # slowpath; otherwise, we can turn off the slowpath
607 if slowpath:
607 if slowpath:
608 for path in match.files():
608 for path in match.files():
609 if path == '.' or path in repo.store:
609 if path == '.' or path in repo.store:
610 break
610 break
611 else:
611 else:
612 slowpath = False
612 slowpath = False
613
613
614 return match, pats, slowpath
614 return match, pats, slowpath
615
615
616 def _fileancestors(repo, revs, match, followfirst):
616 def _fileancestors(repo, revs, match, followfirst):
617 fctxs = []
617 fctxs = []
618 for r in revs:
618 for r in revs:
619 ctx = repo[r]
619 ctx = repo[r]
620 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
620 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
621
621
622 # When displaying a revision with --patch --follow FILE, we have
622 # When displaying a revision with --patch --follow FILE, we have
623 # to know which file of the revision must be diffed. With
623 # to know which file of the revision must be diffed. With
624 # --follow, we want the names of the ancestors of FILE in the
624 # --follow, we want the names of the ancestors of FILE in the
625 # revision, stored in "fcache". "fcache" is populated as a side effect
625 # revision, stored in "fcache". "fcache" is populated as a side effect
626 # of the graph traversal.
626 # of the graph traversal.
627 fcache = {}
627 fcache = {}
628 def filematcher(ctx):
628 def filematcher(ctx):
629 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
629 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
630
630
631 def revgen():
631 def revgen():
632 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
632 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
633 fcache[rev] = [c.path() for c in cs]
633 fcache[rev] = [c.path() for c in cs]
634 yield rev
634 yield rev
635 return smartset.generatorset(revgen(), iterasc=False), filematcher
635 return smartset.generatorset(revgen(), iterasc=False), filematcher
636
636
637 def _makenofollowfilematcher(repo, pats, opts):
637 def _makenofollowfilematcher(repo, pats, opts):
638 '''hook for extensions to override the filematcher for non-follow cases'''
638 '''hook for extensions to override the filematcher for non-follow cases'''
639 return None
639 return None
640
640
641 _opt2logrevset = {
641 _opt2logrevset = {
642 'no_merges': ('not merge()', None),
642 'no_merges': ('not merge()', None),
643 'only_merges': ('merge()', None),
643 'only_merges': ('merge()', None),
644 '_matchfiles': (None, '_matchfiles(%ps)'),
644 '_matchfiles': (None, '_matchfiles(%ps)'),
645 'date': ('date(%s)', None),
645 'date': ('date(%s)', None),
646 'branch': ('branch(%s)', '%lr'),
646 'branch': ('branch(%s)', '%lr'),
647 '_patslog': ('filelog(%s)', '%lr'),
647 '_patslog': ('filelog(%s)', '%lr'),
648 'keyword': ('keyword(%s)', '%lr'),
648 'keyword': ('keyword(%s)', '%lr'),
649 'prune': ('ancestors(%s)', 'not %lr'),
649 'prune': ('ancestors(%s)', 'not %lr'),
650 'user': ('user(%s)', '%lr'),
650 'user': ('user(%s)', '%lr'),
651 }
651 }
652
652
653 def _makerevset(repo, match, pats, slowpath, opts):
653 def _makerevset(repo, match, pats, slowpath, opts):
654 """Return a revset string built from log options and file patterns"""
654 """Return a revset string built from log options and file patterns"""
655 opts = dict(opts)
655 opts = dict(opts)
656 # follow or not follow?
656 # follow or not follow?
657 follow = opts.get('follow') or opts.get('follow_first')
657 follow = opts.get('follow') or opts.get('follow_first')
658
658
659 # branch and only_branch are really aliases and must be handled at
659 # branch and only_branch are really aliases and must be handled at
660 # the same time
660 # the same time
661 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
661 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
662 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
662 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
663
663
664 if slowpath:
664 if slowpath:
665 # See walkchangerevs() slow path.
665 # See walkchangerevs() slow path.
666 #
666 #
667 # pats/include/exclude cannot be represented as separate
667 # pats/include/exclude cannot be represented as separate
668 # revset expressions as their filtering logic applies at file
668 # revset expressions as their filtering logic applies at file
669 # level. For instance "-I a -X b" matches a revision touching
669 # level. For instance "-I a -X b" matches a revision touching
670 # "a" and "b" while "file(a) and not file(b)" does
670 # "a" and "b" while "file(a) and not file(b)" does
671 # not. Besides, filesets are evaluated against the working
671 # not. Besides, filesets are evaluated against the working
672 # directory.
672 # directory.
673 matchargs = ['r:', 'd:relpath']
673 matchargs = ['r:', 'd:relpath']
674 for p in pats:
674 for p in pats:
675 matchargs.append('p:' + p)
675 matchargs.append('p:' + p)
676 for p in opts.get('include', []):
676 for p in opts.get('include', []):
677 matchargs.append('i:' + p)
677 matchargs.append('i:' + p)
678 for p in opts.get('exclude', []):
678 for p in opts.get('exclude', []):
679 matchargs.append('x:' + p)
679 matchargs.append('x:' + p)
680 opts['_matchfiles'] = matchargs
680 opts['_matchfiles'] = matchargs
681 elif not follow:
681 elif not follow:
682 opts['_patslog'] = list(pats)
682 opts['_patslog'] = list(pats)
683
683
684 expr = []
684 expr = []
685 for op, val in sorted(opts.iteritems()):
685 for op, val in sorted(opts.iteritems()):
686 if not val:
686 if not val:
687 continue
687 continue
688 if op not in _opt2logrevset:
688 if op not in _opt2logrevset:
689 continue
689 continue
690 revop, listop = _opt2logrevset[op]
690 revop, listop = _opt2logrevset[op]
691 if revop and '%' not in revop:
691 if revop and '%' not in revop:
692 expr.append(revop)
692 expr.append(revop)
693 elif not listop:
693 elif not listop:
694 expr.append(revsetlang.formatspec(revop, val))
694 expr.append(revsetlang.formatspec(revop, val))
695 else:
695 else:
696 if revop:
696 if revop:
697 val = [revsetlang.formatspec(revop, v) for v in val]
697 val = [revsetlang.formatspec(revop, v) for v in val]
698 expr.append(revsetlang.formatspec(listop, val))
698 expr.append(revsetlang.formatspec(listop, val))
699
699
700 if expr:
700 if expr:
701 expr = '(' + ' and '.join(expr) + ')'
701 expr = '(' + ' and '.join(expr) + ')'
702 else:
702 else:
703 expr = None
703 expr = None
704 return expr
704 return expr
705
705
706 def _initialrevs(repo, opts):
706 def _initialrevs(repo, opts):
707 """Return the initial set of revisions to be filtered or followed"""
707 """Return the initial set of revisions to be filtered or followed"""
708 follow = opts.get('follow') or opts.get('follow_first')
708 follow = opts.get('follow') or opts.get('follow_first')
709 if opts.get('rev'):
709 if opts.get('rev'):
710 revs = scmutil.revrange(repo, opts['rev'])
710 revs = scmutil.revrange(repo, opts['rev'])
711 elif follow and repo.dirstate.p1() == nullid:
711 elif follow and repo.dirstate.p1() == nullid:
712 revs = smartset.baseset()
712 revs = smartset.baseset()
713 elif follow:
713 elif follow:
714 revs = repo.revs('.')
714 revs = repo.revs('.')
715 else:
715 else:
716 revs = smartset.spanset(repo)
716 revs = smartset.spanset(repo)
717 revs.reverse()
717 revs.reverse()
718 return revs
718 return revs
719
719
720 def getrevs(repo, pats, opts):
720 def getrevs(repo, pats, opts):
721 """Return (revs, differ) where revs is a smartset
721 """Return (revs, differ) where revs is a smartset
722
722
723 differ is a changesetdiffer with pre-configured file matcher.
723 differ is a changesetdiffer with pre-configured file matcher.
724 """
724 """
725 follow = opts.get('follow') or opts.get('follow_first')
725 follow = opts.get('follow') or opts.get('follow_first')
726 followfirst = opts.get('follow_first')
726 followfirst = opts.get('follow_first')
727 limit = getlimit(opts)
727 limit = getlimit(opts)
728 revs = _initialrevs(repo, opts)
728 revs = _initialrevs(repo, opts)
729 if not revs:
729 if not revs:
730 return smartset.baseset(), None
730 return smartset.baseset(), None
731 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
731 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
732 filematcher = None
732 filematcher = None
733 if follow:
733 if follow:
734 if slowpath or match.always():
734 if slowpath or match.always():
735 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
735 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
736 else:
736 else:
737 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
737 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
738 revs.reverse()
738 revs.reverse()
739 if filematcher is None:
739 if filematcher is None:
740 filematcher = _makenofollowfilematcher(repo, pats, opts)
740 filematcher = _makenofollowfilematcher(repo, pats, opts)
741 if filematcher is None:
741 if filematcher is None:
742 def filematcher(ctx):
742 def filematcher(ctx):
743 return match
743 return match
744
744
745 expr = _makerevset(repo, match, pats, slowpath, opts)
745 expr = _makerevset(repo, match, pats, slowpath, opts)
746 if opts.get('graph'):
746 if opts.get('graph'):
747 # User-specified revs might be unsorted, but don't sort before
747 # User-specified revs might be unsorted, but don't sort before
748 # _makerevset because it might depend on the order of revs
748 # _makerevset because it might depend on the order of revs
749 if repo.ui.configbool('experimental', 'log.topo'):
749 if repo.ui.configbool('experimental', 'log.topo'):
750 if not revs.istopo():
750 if not revs.istopo():
751 revs = dagop.toposort(revs, repo.changelog.parentrevs)
751 revs = dagop.toposort(revs, repo.changelog.parentrevs)
752 # TODO: try to iterate the set lazily
752 # TODO: try to iterate the set lazily
753 revs = revset.baseset(list(revs), istopo=True)
753 revs = revset.baseset(list(revs), istopo=True)
754 elif not (revs.isdescending() or revs.istopo()):
754 elif not (revs.isdescending() or revs.istopo()):
755 revs.sort(reverse=True)
755 revs.sort(reverse=True)
756 if expr:
756 if expr:
757 matcher = revset.match(None, expr)
757 matcher = revset.match(None, expr)
758 revs = matcher(repo, revs)
758 revs = matcher(repo, revs)
759 if limit is not None:
759 if limit is not None:
760 revs = revs.slice(0, limit)
760 revs = revs.slice(0, limit)
761
761
762 differ = changesetdiffer()
762 differ = changesetdiffer()
763 differ._makefilematcher = filematcher
763 differ._makefilematcher = filematcher
764 return revs, differ
764 return revs, differ
765
765
766 def _parselinerangeopt(repo, opts):
766 def _parselinerangeopt(repo, opts):
767 """Parse --line-range log option and return a list of tuples (filename,
767 """Parse --line-range log option and return a list of tuples (filename,
768 (fromline, toline)).
768 (fromline, toline)).
769 """
769 """
770 linerangebyfname = []
770 linerangebyfname = []
771 for pat in opts.get('line_range', []):
771 for pat in opts.get('line_range', []):
772 try:
772 try:
773 pat, linerange = pat.rsplit(',', 1)
773 pat, linerange = pat.rsplit(',', 1)
774 except ValueError:
774 except ValueError:
775 raise error.Abort(_('malformatted line-range pattern %s') % pat)
775 raise error.Abort(_('malformatted line-range pattern %s') % pat)
776 try:
776 try:
777 fromline, toline = map(int, linerange.split(':'))
777 fromline, toline = map(int, linerange.split(':'))
778 except ValueError:
778 except ValueError:
779 raise error.Abort(_("invalid line range for %s") % pat)
779 raise error.Abort(_("invalid line range for %s") % pat)
780 msg = _("line range pattern '%s' must match exactly one file") % pat
780 msg = _("line range pattern '%s' must match exactly one file") % pat
781 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
781 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
782 linerangebyfname.append(
782 linerangebyfname.append(
783 (fname, util.processlinerange(fromline, toline)))
783 (fname, util.processlinerange(fromline, toline)))
784 return linerangebyfname
784 return linerangebyfname
785
785
786 def getlinerangerevs(repo, userrevs, opts):
786 def getlinerangerevs(repo, userrevs, opts):
787 """Return (revs, differ).
787 """Return (revs, differ).
788
788
789 "revs" are revisions obtained by processing "line-range" log options and
789 "revs" are revisions obtained by processing "line-range" log options and
790 walking block ancestors of each specified file/line-range.
790 walking block ancestors of each specified file/line-range.
791
791
792 "differ" is a changesetdiffer with pre-configured file matcher and hunks
792 "differ" is a changesetdiffer with pre-configured file matcher and hunks
793 filter.
793 filter.
794 """
794 """
795 wctx = repo[None]
795 wctx = repo[None]
796
796
797 # Two-levels map of "rev -> file ctx -> [line range]".
797 # Two-levels map of "rev -> file ctx -> [line range]".
798 linerangesbyrev = {}
798 linerangesbyrev = {}
799 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
799 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
800 if fname not in wctx:
800 if fname not in wctx:
801 raise error.Abort(_('cannot follow file not in parent '
801 raise error.Abort(_('cannot follow file not in parent '
802 'revision: "%s"') % fname)
802 'revision: "%s"') % fname)
803 fctx = wctx.filectx(fname)
803 fctx = wctx.filectx(fname)
804 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
804 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
805 rev = fctx.introrev()
805 rev = fctx.introrev()
806 if rev not in userrevs:
806 if rev not in userrevs:
807 continue
807 continue
808 linerangesbyrev.setdefault(
808 linerangesbyrev.setdefault(
809 rev, {}).setdefault(
809 rev, {}).setdefault(
810 fctx.path(), []).append(linerange)
810 fctx.path(), []).append(linerange)
811
811
812 def nofilterhunksfn(fctx, hunks):
812 def nofilterhunksfn(fctx, hunks):
813 return hunks
813 return hunks
814
814
815 def hunksfilter(ctx):
815 def hunksfilter(ctx):
816 fctxlineranges = linerangesbyrev.get(ctx.rev())
816 fctxlineranges = linerangesbyrev.get(ctx.rev())
817 if fctxlineranges is None:
817 if fctxlineranges is None:
818 return nofilterhunksfn
818 return nofilterhunksfn
819
819
820 def filterfn(fctx, hunks):
820 def filterfn(fctx, hunks):
821 lineranges = fctxlineranges.get(fctx.path())
821 lineranges = fctxlineranges.get(fctx.path())
822 if lineranges is not None:
822 if lineranges is not None:
823 for hr, lines in hunks:
823 for hr, lines in hunks:
824 if hr is None: # binary
824 if hr is None: # binary
825 yield hr, lines
825 yield hr, lines
826 continue
826 continue
827 if any(mdiff.hunkinrange(hr[2:], lr)
827 if any(mdiff.hunkinrange(hr[2:], lr)
828 for lr in lineranges):
828 for lr in lineranges):
829 yield hr, lines
829 yield hr, lines
830 else:
830 else:
831 for hunk in hunks:
831 for hunk in hunks:
832 yield hunk
832 yield hunk
833
833
834 return filterfn
834 return filterfn
835
835
836 def filematcher(ctx):
836 def filematcher(ctx):
837 files = list(linerangesbyrev.get(ctx.rev(), []))
837 files = list(linerangesbyrev.get(ctx.rev(), []))
838 return scmutil.matchfiles(repo, files)
838 return scmutil.matchfiles(repo, files)
839
839
840 revs = sorted(linerangesbyrev, reverse=True)
840 revs = sorted(linerangesbyrev, reverse=True)
841
841
842 differ = changesetdiffer()
842 differ = changesetdiffer()
843 differ._makefilematcher = filematcher
843 differ._makefilematcher = filematcher
844 differ._makehunksfilter = hunksfilter
844 differ._makehunksfilter = hunksfilter
845 return revs, differ
845 return revs, differ
846
846
847 def _graphnodeformatter(ui, displayer):
847 def _graphnodeformatter(ui, displayer):
848 spec = ui.config('ui', 'graphnodetemplate')
848 spec = ui.config('ui', 'graphnodetemplate')
849 if not spec:
849 if not spec:
850 return templatekw.getgraphnode # fast path for "{graphnode}"
850 return templatekw.getgraphnode # fast path for "{graphnode}"
851
851
852 spec = templater.unquotestring(spec)
852 spec = templater.unquotestring(spec)
853 if isinstance(displayer, changesettemplater):
853 if isinstance(displayer, changesettemplater):
854 # reuse cache of slow templates
854 # reuse cache of slow templates
855 tres = displayer._tresources
855 tres = displayer._tresources
856 else:
856 else:
857 tres = formatter.templateresources(ui)
857 tres = formatter.templateresources(ui)
858 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
858 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
859 resources=tres)
859 resources=tres)
860 def formatnode(repo, ctx):
860 def formatnode(repo, ctx):
861 props = {'ctx': ctx, 'repo': repo}
861 props = {'ctx': ctx, 'repo': repo}
862 return templ.renderdefault(props)
862 return templ.renderdefault(props)
863 return formatnode
863 return formatnode
864
864
865 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None, props=None):
865 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
866 props = props or {}
866 props = props or {}
867 formatnode = _graphnodeformatter(ui, displayer)
867 formatnode = _graphnodeformatter(ui, displayer)
868 state = graphmod.asciistate()
868 state = graphmod.asciistate()
869 styles = state['styles']
869 styles = state['styles']
870
870
871 # only set graph styling if HGPLAIN is not set.
871 # only set graph styling if HGPLAIN is not set.
872 if ui.plain('graph'):
872 if ui.plain('graph'):
873 # set all edge styles to |, the default pre-3.8 behaviour
873 # set all edge styles to |, the default pre-3.8 behaviour
874 styles.update(dict.fromkeys(styles, '|'))
874 styles.update(dict.fromkeys(styles, '|'))
875 else:
875 else:
876 edgetypes = {
876 edgetypes = {
877 'parent': graphmod.PARENT,
877 'parent': graphmod.PARENT,
878 'grandparent': graphmod.GRANDPARENT,
878 'grandparent': graphmod.GRANDPARENT,
879 'missing': graphmod.MISSINGPARENT
879 'missing': graphmod.MISSINGPARENT
880 }
880 }
881 for name, key in edgetypes.items():
881 for name, key in edgetypes.items():
882 # experimental config: experimental.graphstyle.*
882 # experimental config: experimental.graphstyle.*
883 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
883 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
884 styles[key])
884 styles[key])
885 if not styles[key]:
885 if not styles[key]:
886 styles[key] = None
886 styles[key] = None
887
887
888 # experimental config: experimental.graphshorten
888 # experimental config: experimental.graphshorten
889 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
889 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
890
890
891 for rev, type, ctx, parents in dag:
891 for rev, type, ctx, parents in dag:
892 char = formatnode(repo, ctx)
892 char = formatnode(repo, ctx)
893 copies = None
893 copies = getcopies(ctx) if getcopies else None
894 if getrenamed:
895 copies = []
896 for fn in ctx.files():
897 rename = getrenamed(fn, ctx.rev())
898 if rename:
899 copies.append((fn, rename))
900 edges = edgefn(type, char, state, rev, parents)
894 edges = edgefn(type, char, state, rev, parents)
901 firstedge = next(edges)
895 firstedge = next(edges)
902 width = firstedge[2]
896 width = firstedge[2]
903 displayer.show(ctx, copies=copies,
897 displayer.show(ctx, copies=copies,
904 graphwidth=width, **pycompat.strkwargs(props))
898 graphwidth=width, **pycompat.strkwargs(props))
905 lines = displayer.hunk.pop(rev).split('\n')
899 lines = displayer.hunk.pop(rev).split('\n')
906 if not lines[-1]:
900 if not lines[-1]:
907 del lines[-1]
901 del lines[-1]
908 displayer.flush(ctx)
902 displayer.flush(ctx)
909 for type, char, width, coldata in itertools.chain([firstedge], edges):
903 for type, char, width, coldata in itertools.chain([firstedge], edges):
910 graphmod.ascii(ui, state, type, char, lines, coldata)
904 graphmod.ascii(ui, state, type, char, lines, coldata)
911 lines = []
905 lines = []
912 displayer.close()
906 displayer.close()
913
907
914 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
908 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
915 revdag = graphmod.dagwalker(repo, revs)
909 revdag = graphmod.dagwalker(repo, revs)
916 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
910 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
917
911
918 def displayrevs(ui, repo, revs, displayer, getrenamed):
912 def displayrevs(ui, repo, revs, displayer, getcopies):
919 for rev in revs:
913 for rev in revs:
920 ctx = repo[rev]
914 ctx = repo[rev]
921 copies = None
915 copies = getcopies(ctx) if getcopies else None
922 if getrenamed is not None:
923 copies = []
924 for fn in ctx.files():
925 rename = getrenamed(fn, rev)
926 if rename:
927 copies.append((fn, rename))
928 displayer.show(ctx, copies=copies)
916 displayer.show(ctx, copies=copies)
929 displayer.flush(ctx)
917 displayer.flush(ctx)
930 displayer.close()
918 displayer.close()
931
919
932 def checkunsupportedgraphflags(pats, opts):
920 def checkunsupportedgraphflags(pats, opts):
933 for op in ["newest_first"]:
921 for op in ["newest_first"]:
934 if op in opts and opts[op]:
922 if op in opts and opts[op]:
935 raise error.Abort(_("-G/--graph option is incompatible with --%s")
923 raise error.Abort(_("-G/--graph option is incompatible with --%s")
936 % op.replace("_", "-"))
924 % op.replace("_", "-"))
937
925
938 def graphrevs(repo, nodes, opts):
926 def graphrevs(repo, nodes, opts):
939 limit = getlimit(opts)
927 limit = getlimit(opts)
940 nodes.reverse()
928 nodes.reverse()
941 if limit is not None:
929 if limit is not None:
942 nodes = nodes[:limit]
930 nodes = nodes[:limit]
943 return graphmod.nodes(repo, nodes)
931 return graphmod.nodes(repo, nodes)
General Comments 0
You need to be logged in to leave comments. Login now