##// END OF EJS Templates
uncommit: add options to update to the current user or current date...
Matt Harbison -
r43173:66048f6b default
parent child Browse files
Show More
@@ -1,264 +1,267 b''
1 # uncommit - undo the actions of a commit
1 # uncommit - undo the actions of a commit
2 #
2 #
3 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
3 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
4 # Logilab SA <contact@logilab.fr>
4 # Logilab SA <contact@logilab.fr>
5 # Pierre-Yves David <pierre-yves.david@ens-lyon.org>
5 # Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 # Patrick Mezard <patrick@mezard.eu>
6 # Patrick Mezard <patrick@mezard.eu>
7 # Copyright 2016 Facebook, Inc.
7 # Copyright 2016 Facebook, Inc.
8 #
8 #
9 # This software may be used and distributed according to the terms of the
9 # This software may be used and distributed according to the terms of the
10 # GNU General Public License version 2 or any later version.
10 # GNU General Public License version 2 or any later version.
11
11
12 """uncommit part or all of a local changeset (EXPERIMENTAL)
12 """uncommit part or all of a local changeset (EXPERIMENTAL)
13
13
14 This command undoes the effect of a local commit, returning the affected
14 This command undoes the effect of a local commit, returning the affected
15 files to their uncommitted state. This means that files modified, added or
15 files to their uncommitted state. This means that files modified, added or
16 removed in the changeset will be left unchanged, and so will remain modified,
16 removed in the changeset will be left unchanged, and so will remain modified,
17 added and removed in the working directory.
17 added and removed in the working directory.
18 """
18 """
19
19
20 from __future__ import absolute_import
20 from __future__ import absolute_import
21
21
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23
23
24 from mercurial import (
24 from mercurial import (
25 cmdutil,
25 cmdutil,
26 commands,
26 commands,
27 context,
27 context,
28 copies as copiesmod,
28 copies as copiesmod,
29 error,
29 error,
30 node,
30 node,
31 obsutil,
31 obsutil,
32 pycompat,
32 pycompat,
33 registrar,
33 registrar,
34 rewriteutil,
34 rewriteutil,
35 scmutil,
35 scmutil,
36 util,
36 util,
37 )
37 )
38
38
39 cmdtable = {}
39 cmdtable = {}
40 command = registrar.command(cmdtable)
40 command = registrar.command(cmdtable)
41
41
42 configtable = {}
42 configtable = {}
43 configitem = registrar.configitem(configtable)
43 configitem = registrar.configitem(configtable)
44
44
45 configitem('experimental', 'uncommitondirtywdir',
45 configitem('experimental', 'uncommitondirtywdir',
46 default=False,
46 default=False,
47 )
47 )
48 configitem('experimental', 'uncommit.keep',
48 configitem('experimental', 'uncommit.keep',
49 default=False,
49 default=False,
50 )
50 )
51
51
52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
54 # be specifying the version(s) of Mercurial they are tested with, or
54 # be specifying the version(s) of Mercurial they are tested with, or
55 # leave the attribute unspecified.
55 # leave the attribute unspecified.
56 testedwith = 'ships-with-hg-core'
56 testedwith = 'ships-with-hg-core'
57
57
58 def _commitfiltered(repo, ctx, match, keepcommit, message=None, user=None,
58 def _commitfiltered(repo, ctx, match, keepcommit, message=None, user=None,
59 date=None):
59 date=None):
60 """Recommit ctx with changed files not in match. Return the new
60 """Recommit ctx with changed files not in match. Return the new
61 node identifier, or None if nothing changed.
61 node identifier, or None if nothing changed.
62 """
62 """
63 base = ctx.p1()
63 base = ctx.p1()
64 # ctx
64 # ctx
65 initialfiles = set(ctx.files())
65 initialfiles = set(ctx.files())
66 exclude = set(f for f in initialfiles if match(f))
66 exclude = set(f for f in initialfiles if match(f))
67
67
68 # No files matched commit, so nothing excluded
68 # No files matched commit, so nothing excluded
69 if not exclude:
69 if not exclude:
70 return None
70 return None
71
71
72 # return the p1 so that we don't create an obsmarker later
72 # return the p1 so that we don't create an obsmarker later
73 if not keepcommit:
73 if not keepcommit:
74 return ctx.p1().node()
74 return ctx.p1().node()
75
75
76 files = (initialfiles - exclude)
76 files = (initialfiles - exclude)
77 # Filter copies
77 # Filter copies
78 copied = copiesmod.pathcopies(base, ctx)
78 copied = copiesmod.pathcopies(base, ctx)
79 copied = dict((dst, src) for dst, src in copied.iteritems()
79 copied = dict((dst, src) for dst, src in copied.iteritems()
80 if dst in files)
80 if dst in files)
81 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
81 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
82 if path not in contentctx:
82 if path not in contentctx:
83 return None
83 return None
84 fctx = contentctx[path]
84 fctx = contentctx[path]
85 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
85 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
86 fctx.islink(),
86 fctx.islink(),
87 fctx.isexec(),
87 fctx.isexec(),
88 copysource=copied.get(path))
88 copysource=copied.get(path))
89 return mctx
89 return mctx
90
90
91 if not files:
91 if not files:
92 repo.ui.status(_("note: keeping empty commit\n"))
92 repo.ui.status(_("note: keeping empty commit\n"))
93
93
94 if message is None:
94 if message is None:
95 message = ctx.description()
95 message = ctx.description()
96 if not user:
96 if not user:
97 user = ctx.user()
97 user = ctx.user()
98 if not date:
98 if not date:
99 date = ctx.date()
99 date = ctx.date()
100
100
101 new = context.memctx(repo,
101 new = context.memctx(repo,
102 parents=[base.node(), node.nullid],
102 parents=[base.node(), node.nullid],
103 text=message,
103 text=message,
104 files=files,
104 files=files,
105 filectxfn=filectxfn,
105 filectxfn=filectxfn,
106 user=user,
106 user=user,
107 date=date,
107 date=date,
108 extra=ctx.extra())
108 extra=ctx.extra())
109 return repo.commitctx(new)
109 return repo.commitctx(new)
110
110
111 @command('uncommit',
111 @command('uncommit',
112 [('', 'keep', None, _('allow an empty commit after uncommiting')),
112 [('', 'keep', None, _('allow an empty commit after uncommiting')),
113 ('', 'allow-dirty-working-copy', False,
113 ('', 'allow-dirty-working-copy', False,
114 _('allow uncommit with outstanding changes'))
114 _('allow uncommit with outstanding changes'))
115 ] + commands.walkopts + commands.commitopts + commands.commitopts2,
115 ] + commands.walkopts + commands.commitopts + commands.commitopts2
116 + commands.commitopts3,
116 _('[OPTION]... [FILE]...'),
117 _('[OPTION]... [FILE]...'),
117 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
118 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
118 def uncommit(ui, repo, *pats, **opts):
119 def uncommit(ui, repo, *pats, **opts):
119 """uncommit part or all of a local changeset
120 """uncommit part or all of a local changeset
120
121
121 This command undoes the effect of a local commit, returning the affected
122 This command undoes the effect of a local commit, returning the affected
122 files to their uncommitted state. This means that files modified or
123 files to their uncommitted state. This means that files modified or
123 deleted in the changeset will be left unchanged, and so will remain
124 deleted in the changeset will be left unchanged, and so will remain
124 modified in the working directory.
125 modified in the working directory.
125
126
126 If no files are specified, the commit will be pruned, unless --keep is
127 If no files are specified, the commit will be pruned, unless --keep is
127 given.
128 given.
128 """
129 """
129 opts = pycompat.byteskwargs(opts)
130 opts = pycompat.byteskwargs(opts)
130
131
132 cmdutil.resolvecommitoptions(ui, opts)
133
131 with repo.wlock(), repo.lock():
134 with repo.wlock(), repo.lock():
132
135
133 m, a, r, d = repo.status()[:4]
136 m, a, r, d = repo.status()[:4]
134 isdirtypath = any(set(m + a + r + d) & set(pats))
137 isdirtypath = any(set(m + a + r + d) & set(pats))
135 allowdirtywcopy = (opts['allow_dirty_working_copy'] or
138 allowdirtywcopy = (opts['allow_dirty_working_copy'] or
136 repo.ui.configbool('experimental', 'uncommitondirtywdir'))
139 repo.ui.configbool('experimental', 'uncommitondirtywdir'))
137 if not allowdirtywcopy and (not pats or isdirtypath):
140 if not allowdirtywcopy and (not pats or isdirtypath):
138 cmdutil.bailifchanged(repo, hint=_('requires '
141 cmdutil.bailifchanged(repo, hint=_('requires '
139 '--allow-dirty-working-copy to uncommit'))
142 '--allow-dirty-working-copy to uncommit'))
140 old = repo['.']
143 old = repo['.']
141 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
144 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
142 if len(old.parents()) > 1:
145 if len(old.parents()) > 1:
143 raise error.Abort(_("cannot uncommit merge changeset"))
146 raise error.Abort(_("cannot uncommit merge changeset"))
144
147
145 match = scmutil.match(old, pats, opts)
148 match = scmutil.match(old, pats, opts)
146
149
147 # Check all explicitly given files; abort if there's a problem.
150 # Check all explicitly given files; abort if there's a problem.
148 if match.files():
151 if match.files():
149 s = old.status(old.p1(), match, listclean=True)
152 s = old.status(old.p1(), match, listclean=True)
150 eligible = set(s.added) | set(s.modified) | set(s.removed)
153 eligible = set(s.added) | set(s.modified) | set(s.removed)
151
154
152 badfiles = set(match.files()) - eligible
155 badfiles = set(match.files()) - eligible
153
156
154 # Naming a parent directory of an eligible file is OK, even
157 # Naming a parent directory of an eligible file is OK, even
155 # if not everything tracked in that directory can be
158 # if not everything tracked in that directory can be
156 # uncommitted.
159 # uncommitted.
157 if badfiles:
160 if badfiles:
158 badfiles -= {f for f in util.dirs(eligible)}
161 badfiles -= {f for f in util.dirs(eligible)}
159
162
160 for f in sorted(badfiles):
163 for f in sorted(badfiles):
161 if f in s.clean:
164 if f in s.clean:
162 hint = _(b"file was not changed in working directory "
165 hint = _(b"file was not changed in working directory "
163 b"parent")
166 b"parent")
164 elif repo.wvfs.exists(f):
167 elif repo.wvfs.exists(f):
165 hint = _(b"file was untracked in working directory parent")
168 hint = _(b"file was untracked in working directory parent")
166 else:
169 else:
167 hint = _(b"file does not exist")
170 hint = _(b"file does not exist")
168
171
169 raise error.Abort(_(b'cannot uncommit "%s"')
172 raise error.Abort(_(b'cannot uncommit "%s"')
170 % scmutil.getuipathfn(repo)(f), hint=hint)
173 % scmutil.getuipathfn(repo)(f), hint=hint)
171
174
172 with repo.transaction('uncommit'):
175 with repo.transaction('uncommit'):
173 if not (opts[b'message'] or opts[b'logfile']):
176 if not (opts[b'message'] or opts[b'logfile']):
174 opts[b'message'] = old.description()
177 opts[b'message'] = old.description()
175 message = cmdutil.logmessage(ui, pycompat.byteskwargs(opts))
178 message = cmdutil.logmessage(ui, pycompat.byteskwargs(opts))
176
179
177 keepcommit = pats
180 keepcommit = pats
178 if not keepcommit:
181 if not keepcommit:
179 if opts.get('keep') is not None:
182 if opts.get('keep') is not None:
180 keepcommit = opts.get('keep')
183 keepcommit = opts.get('keep')
181 else:
184 else:
182 keepcommit = ui.configbool('experimental', 'uncommit.keep')
185 keepcommit = ui.configbool('experimental', 'uncommit.keep')
183 newid = _commitfiltered(repo, old, match, keepcommit,
186 newid = _commitfiltered(repo, old, match, keepcommit,
184 message=message, user=opts.get(b'user'),
187 message=message, user=opts.get(b'user'),
185 date=opts.get(b'date'))
188 date=opts.get(b'date'))
186 if newid is None:
189 if newid is None:
187 ui.status(_("nothing to uncommit\n"))
190 ui.status(_("nothing to uncommit\n"))
188 return 1
191 return 1
189
192
190 mapping = {}
193 mapping = {}
191 if newid != old.p1().node():
194 if newid != old.p1().node():
192 # Move local changes on filtered changeset
195 # Move local changes on filtered changeset
193 mapping[old.node()] = (newid,)
196 mapping[old.node()] = (newid,)
194 else:
197 else:
195 # Fully removed the old commit
198 # Fully removed the old commit
196 mapping[old.node()] = ()
199 mapping[old.node()] = ()
197
200
198 with repo.dirstate.parentchange():
201 with repo.dirstate.parentchange():
199 scmutil.movedirstate(repo, repo[newid], match)
202 scmutil.movedirstate(repo, repo[newid], match)
200
203
201 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
204 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
202
205
203 def predecessormarkers(ctx):
206 def predecessormarkers(ctx):
204 """yields the obsolete markers marking the given changeset as a successor"""
207 """yields the obsolete markers marking the given changeset as a successor"""
205 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
208 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
206 yield obsutil.marker(ctx.repo(), data)
209 yield obsutil.marker(ctx.repo(), data)
207
210
208 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
211 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
209 helpbasic=True)
212 helpbasic=True)
210 def unamend(ui, repo, **opts):
213 def unamend(ui, repo, **opts):
211 """undo the most recent amend operation on a current changeset
214 """undo the most recent amend operation on a current changeset
212
215
213 This command will roll back to the previous version of a changeset,
216 This command will roll back to the previous version of a changeset,
214 leaving working directory in state in which it was before running
217 leaving working directory in state in which it was before running
215 `hg amend` (e.g. files modified as part of an amend will be
218 `hg amend` (e.g. files modified as part of an amend will be
216 marked as modified `hg status`)
219 marked as modified `hg status`)
217 """
220 """
218
221
219 unfi = repo.unfiltered()
222 unfi = repo.unfiltered()
220 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
223 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
221
224
222 # identify the commit from which to unamend
225 # identify the commit from which to unamend
223 curctx = repo['.']
226 curctx = repo['.']
224
227
225 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
228 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
226
229
227 # identify the commit to which to unamend
230 # identify the commit to which to unamend
228 markers = list(predecessormarkers(curctx))
231 markers = list(predecessormarkers(curctx))
229 if len(markers) != 1:
232 if len(markers) != 1:
230 e = _("changeset must have one predecessor, found %i predecessors")
233 e = _("changeset must have one predecessor, found %i predecessors")
231 raise error.Abort(e % len(markers))
234 raise error.Abort(e % len(markers))
232
235
233 prednode = markers[0].prednode()
236 prednode = markers[0].prednode()
234 predctx = unfi[prednode]
237 predctx = unfi[prednode]
235
238
236 # add an extra so that we get a new hash
239 # add an extra so that we get a new hash
237 # note: allowing unamend to undo an unamend is an intentional feature
240 # note: allowing unamend to undo an unamend is an intentional feature
238 extras = predctx.extra()
241 extras = predctx.extra()
239 extras['unamend_source'] = curctx.hex()
242 extras['unamend_source'] = curctx.hex()
240
243
241 def filectxfn(repo, ctx_, path):
244 def filectxfn(repo, ctx_, path):
242 try:
245 try:
243 return predctx.filectx(path)
246 return predctx.filectx(path)
244 except KeyError:
247 except KeyError:
245 return None
248 return None
246
249
247 # Make a new commit same as predctx
250 # Make a new commit same as predctx
248 newctx = context.memctx(repo,
251 newctx = context.memctx(repo,
249 parents=(predctx.p1(), predctx.p2()),
252 parents=(predctx.p1(), predctx.p2()),
250 text=predctx.description(),
253 text=predctx.description(),
251 files=predctx.files(),
254 files=predctx.files(),
252 filectxfn=filectxfn,
255 filectxfn=filectxfn,
253 user=predctx.user(),
256 user=predctx.user(),
254 date=predctx.date(),
257 date=predctx.date(),
255 extra=extras)
258 extra=extras)
256 newprednode = repo.commitctx(newctx)
259 newprednode = repo.commitctx(newctx)
257 newpredctx = repo[newprednode]
260 newpredctx = repo[newprednode]
258 dirstate = repo.dirstate
261 dirstate = repo.dirstate
259
262
260 with dirstate.parentchange():
263 with dirstate.parentchange():
261 scmutil.movedirstate(repo, newpredctx)
264 scmutil.movedirstate(repo, newpredctx)
262
265
263 mapping = {curctx.node(): (newprednode,)}
266 mapping = {curctx.node(): (newprednode,)}
264 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
267 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
@@ -1,3439 +1,3455 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in 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 copy as copymod
10 import copy as copymod
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 bookmarks,
24 bookmarks,
25 changelog,
25 changelog,
26 copies,
26 copies,
27 crecord as crecordmod,
27 crecord as crecordmod,
28 dirstateguard,
28 dirstateguard,
29 encoding,
29 encoding,
30 error,
30 error,
31 formatter,
31 formatter,
32 logcmdutil,
32 logcmdutil,
33 match as matchmod,
33 match as matchmod,
34 merge as mergemod,
34 merge as mergemod,
35 mergeutil,
35 mergeutil,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 phases,
39 phases,
40 pycompat,
40 pycompat,
41 repair,
41 repair,
42 revlog,
42 revlog,
43 rewriteutil,
43 rewriteutil,
44 scmutil,
44 scmutil,
45 smartset,
45 smartset,
46 state as statemod,
46 state as statemod,
47 subrepoutil,
47 subrepoutil,
48 templatekw,
48 templatekw,
49 templater,
49 templater,
50 util,
50 util,
51 vfs as vfsmod,
51 vfs as vfsmod,
52 )
52 )
53
53
54 from .utils import (
54 from .utils import (
55 dateutil,
55 dateutil,
56 stringutil,
56 stringutil,
57 )
57 )
58
58
59 stringio = util.stringio
59 stringio = util.stringio
60
60
61 # templates of common command options
61 # templates of common command options
62
62
63 dryrunopts = [
63 dryrunopts = [
64 ('n', 'dry-run', None,
64 ('n', 'dry-run', None,
65 _('do not perform actions, just print output')),
65 _('do not perform actions, just print output')),
66 ]
66 ]
67
67
68 confirmopts = [
68 confirmopts = [
69 ('', 'confirm', None,
69 ('', 'confirm', None,
70 _('ask before applying actions')),
70 _('ask before applying actions')),
71 ]
71 ]
72
72
73 remoteopts = [
73 remoteopts = [
74 ('e', 'ssh', '',
74 ('e', 'ssh', '',
75 _('specify ssh command to use'), _('CMD')),
75 _('specify ssh command to use'), _('CMD')),
76 ('', 'remotecmd', '',
76 ('', 'remotecmd', '',
77 _('specify hg command to run on the remote side'), _('CMD')),
77 _('specify hg command to run on the remote side'), _('CMD')),
78 ('', 'insecure', None,
78 ('', 'insecure', None,
79 _('do not verify server certificate (ignoring web.cacerts config)')),
79 _('do not verify server certificate (ignoring web.cacerts config)')),
80 ]
80 ]
81
81
82 walkopts = [
82 walkopts = [
83 ('I', 'include', [],
83 ('I', 'include', [],
84 _('include names matching the given patterns'), _('PATTERN')),
84 _('include names matching the given patterns'), _('PATTERN')),
85 ('X', 'exclude', [],
85 ('X', 'exclude', [],
86 _('exclude names matching the given patterns'), _('PATTERN')),
86 _('exclude names matching the given patterns'), _('PATTERN')),
87 ]
87 ]
88
88
89 commitopts = [
89 commitopts = [
90 ('m', 'message', '',
90 ('m', 'message', '',
91 _('use text as commit message'), _('TEXT')),
91 _('use text as commit message'), _('TEXT')),
92 ('l', 'logfile', '',
92 ('l', 'logfile', '',
93 _('read commit message from file'), _('FILE')),
93 _('read commit message from file'), _('FILE')),
94 ]
94 ]
95
95
96 commitopts2 = [
96 commitopts2 = [
97 ('d', 'date', '',
97 ('d', 'date', '',
98 _('record the specified date as commit date'), _('DATE')),
98 _('record the specified date as commit date'), _('DATE')),
99 ('u', 'user', '',
99 ('u', 'user', '',
100 _('record the specified user as committer'), _('USER')),
100 _('record the specified user as committer'), _('USER')),
101 ]
101 ]
102
102
103 commitopts3 = [
104 (b'D', b'current-date', None,
105 _(b'record the current date as commit date')),
106 (b'U', b'current-user', None,
107 _(b'record the current user as committer')),
108 ]
109
103 formatteropts = [
110 formatteropts = [
104 ('T', 'template', '',
111 ('T', 'template', '',
105 _('display with template'), _('TEMPLATE')),
112 _('display with template'), _('TEMPLATE')),
106 ]
113 ]
107
114
108 templateopts = [
115 templateopts = [
109 ('', 'style', '',
116 ('', 'style', '',
110 _('display using template map file (DEPRECATED)'), _('STYLE')),
117 _('display using template map file (DEPRECATED)'), _('STYLE')),
111 ('T', 'template', '',
118 ('T', 'template', '',
112 _('display with template'), _('TEMPLATE')),
119 _('display with template'), _('TEMPLATE')),
113 ]
120 ]
114
121
115 logopts = [
122 logopts = [
116 ('p', 'patch', None, _('show patch')),
123 ('p', 'patch', None, _('show patch')),
117 ('g', 'git', None, _('use git extended diff format')),
124 ('g', 'git', None, _('use git extended diff format')),
118 ('l', 'limit', '',
125 ('l', 'limit', '',
119 _('limit number of changes displayed'), _('NUM')),
126 _('limit number of changes displayed'), _('NUM')),
120 ('M', 'no-merges', None, _('do not show merges')),
127 ('M', 'no-merges', None, _('do not show merges')),
121 ('', 'stat', None, _('output diffstat-style summary of changes')),
128 ('', 'stat', None, _('output diffstat-style summary of changes')),
122 ('G', 'graph', None, _("show the revision DAG")),
129 ('G', 'graph', None, _("show the revision DAG")),
123 ] + templateopts
130 ] + templateopts
124
131
125 diffopts = [
132 diffopts = [
126 ('a', 'text', None, _('treat all files as text')),
133 ('a', 'text', None, _('treat all files as text')),
127 ('g', 'git', None, _('use git extended diff format')),
134 ('g', 'git', None, _('use git extended diff format')),
128 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
135 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
129 ('', 'nodates', None, _('omit dates from diff headers'))
136 ('', 'nodates', None, _('omit dates from diff headers'))
130 ]
137 ]
131
138
132 diffwsopts = [
139 diffwsopts = [
133 ('w', 'ignore-all-space', None,
140 ('w', 'ignore-all-space', None,
134 _('ignore white space when comparing lines')),
141 _('ignore white space when comparing lines')),
135 ('b', 'ignore-space-change', None,
142 ('b', 'ignore-space-change', None,
136 _('ignore changes in the amount of white space')),
143 _('ignore changes in the amount of white space')),
137 ('B', 'ignore-blank-lines', None,
144 ('B', 'ignore-blank-lines', None,
138 _('ignore changes whose lines are all blank')),
145 _('ignore changes whose lines are all blank')),
139 ('Z', 'ignore-space-at-eol', None,
146 ('Z', 'ignore-space-at-eol', None,
140 _('ignore changes in whitespace at EOL')),
147 _('ignore changes in whitespace at EOL')),
141 ]
148 ]
142
149
143 diffopts2 = [
150 diffopts2 = [
144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
151 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
145 ('p', 'show-function', None, _('show which function each change is in')),
152 ('p', 'show-function', None, _('show which function each change is in')),
146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
153 ('', 'reverse', None, _('produce a diff that undoes the changes')),
147 ] + diffwsopts + [
154 ] + diffwsopts + [
148 ('U', 'unified', '',
155 ('U', 'unified', '',
149 _('number of lines of context to show'), _('NUM')),
156 _('number of lines of context to show'), _('NUM')),
150 ('', 'stat', None, _('output diffstat-style summary of changes')),
157 ('', 'stat', None, _('output diffstat-style summary of changes')),
151 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
158 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
152 ]
159 ]
153
160
154 mergetoolopts = [
161 mergetoolopts = [
155 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
162 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
156 ]
163 ]
157
164
158 similarityopts = [
165 similarityopts = [
159 ('s', 'similarity', '',
166 ('s', 'similarity', '',
160 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
167 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
161 ]
168 ]
162
169
163 subrepoopts = [
170 subrepoopts = [
164 ('S', 'subrepos', None,
171 ('S', 'subrepos', None,
165 _('recurse into subrepositories'))
172 _('recurse into subrepositories'))
166 ]
173 ]
167
174
168 debugrevlogopts = [
175 debugrevlogopts = [
169 ('c', 'changelog', False, _('open changelog')),
176 ('c', 'changelog', False, _('open changelog')),
170 ('m', 'manifest', False, _('open manifest')),
177 ('m', 'manifest', False, _('open manifest')),
171 ('', 'dir', '', _('open directory manifest')),
178 ('', 'dir', '', _('open directory manifest')),
172 ]
179 ]
173
180
174 # special string such that everything below this line will be ingored in the
181 # special string such that everything below this line will be ingored in the
175 # editor text
182 # editor text
176 _linebelow = "^HG: ------------------------ >8 ------------------------$"
183 _linebelow = "^HG: ------------------------ >8 ------------------------$"
177
184
185 def resolvecommitoptions(ui, opts):
186 """modify commit options dict to handle related options
187 """
188 # N.B. this is extremely similar to setupheaderopts() in mq.py
189 if not opts.get(b'date') and opts.get(b'current_date'):
190 opts[b'date'] = b'%d %d' % dateutil.makedate()
191 if not opts.get(b'user') and opts.get(b'current_user'):
192 opts[b'user'] = ui.username()
193
178 def ishunk(x):
194 def ishunk(x):
179 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
195 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
180 return isinstance(x, hunkclasses)
196 return isinstance(x, hunkclasses)
181
197
182 def newandmodified(chunks, originalchunks):
198 def newandmodified(chunks, originalchunks):
183 newlyaddedandmodifiedfiles = set()
199 newlyaddedandmodifiedfiles = set()
184 alsorestore = set()
200 alsorestore = set()
185 for chunk in chunks:
201 for chunk in chunks:
186 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in
202 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in
187 originalchunks):
203 originalchunks):
188 newlyaddedandmodifiedfiles.add(chunk.header.filename())
204 newlyaddedandmodifiedfiles.add(chunk.header.filename())
189 alsorestore.update(set(chunk.header.files()) -
205 alsorestore.update(set(chunk.header.files()) -
190 {chunk.header.filename()})
206 {chunk.header.filename()})
191 return newlyaddedandmodifiedfiles, alsorestore
207 return newlyaddedandmodifiedfiles, alsorestore
192
208
193 def parsealiases(cmd):
209 def parsealiases(cmd):
194 return cmd.split("|")
210 return cmd.split("|")
195
211
196 def setupwrapcolorwrite(ui):
212 def setupwrapcolorwrite(ui):
197 # wrap ui.write so diff output can be labeled/colorized
213 # wrap ui.write so diff output can be labeled/colorized
198 def wrapwrite(orig, *args, **kw):
214 def wrapwrite(orig, *args, **kw):
199 label = kw.pop(r'label', '')
215 label = kw.pop(r'label', '')
200 for chunk, l in patch.difflabel(lambda: args):
216 for chunk, l in patch.difflabel(lambda: args):
201 orig(chunk, label=label + l)
217 orig(chunk, label=label + l)
202
218
203 oldwrite = ui.write
219 oldwrite = ui.write
204 def wrap(*args, **kwargs):
220 def wrap(*args, **kwargs):
205 return wrapwrite(oldwrite, *args, **kwargs)
221 return wrapwrite(oldwrite, *args, **kwargs)
206 setattr(ui, 'write', wrap)
222 setattr(ui, 'write', wrap)
207 return oldwrite
223 return oldwrite
208
224
209 def filterchunks(ui, originalhunks, usecurses, testfile, match,
225 def filterchunks(ui, originalhunks, usecurses, testfile, match,
210 operation=None):
226 operation=None):
211 try:
227 try:
212 if usecurses:
228 if usecurses:
213 if testfile:
229 if testfile:
214 recordfn = crecordmod.testdecorator(
230 recordfn = crecordmod.testdecorator(
215 testfile, crecordmod.testchunkselector)
231 testfile, crecordmod.testchunkselector)
216 else:
232 else:
217 recordfn = crecordmod.chunkselector
233 recordfn = crecordmod.chunkselector
218
234
219 return crecordmod.filterpatch(ui, originalhunks, recordfn,
235 return crecordmod.filterpatch(ui, originalhunks, recordfn,
220 operation)
236 operation)
221 except crecordmod.fallbackerror as e:
237 except crecordmod.fallbackerror as e:
222 ui.warn('%s\n' % e.message)
238 ui.warn('%s\n' % e.message)
223 ui.warn(_('falling back to text mode\n'))
239 ui.warn(_('falling back to text mode\n'))
224
240
225 return patch.filterpatch(ui, originalhunks, match, operation)
241 return patch.filterpatch(ui, originalhunks, match, operation)
226
242
227 def recordfilter(ui, originalhunks, match, operation=None):
243 def recordfilter(ui, originalhunks, match, operation=None):
228 """ Prompts the user to filter the originalhunks and return a list of
244 """ Prompts the user to filter the originalhunks and return a list of
229 selected hunks.
245 selected hunks.
230 *operation* is used for to build ui messages to indicate the user what
246 *operation* is used for to build ui messages to indicate the user what
231 kind of filtering they are doing: reverting, committing, shelving, etc.
247 kind of filtering they are doing: reverting, committing, shelving, etc.
232 (see patch.filterpatch).
248 (see patch.filterpatch).
233 """
249 """
234 usecurses = crecordmod.checkcurses(ui)
250 usecurses = crecordmod.checkcurses(ui)
235 testfile = ui.config('experimental', 'crecordtest')
251 testfile = ui.config('experimental', 'crecordtest')
236 oldwrite = setupwrapcolorwrite(ui)
252 oldwrite = setupwrapcolorwrite(ui)
237 try:
253 try:
238 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
254 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
239 testfile, match, operation)
255 testfile, match, operation)
240 finally:
256 finally:
241 ui.write = oldwrite
257 ui.write = oldwrite
242 return newchunks, newopts
258 return newchunks, newopts
243
259
244 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
260 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
245 filterfn, *pats, **opts):
261 filterfn, *pats, **opts):
246 opts = pycompat.byteskwargs(opts)
262 opts = pycompat.byteskwargs(opts)
247 if not ui.interactive():
263 if not ui.interactive():
248 if cmdsuggest:
264 if cmdsuggest:
249 msg = _('running non-interactively, use %s instead') % cmdsuggest
265 msg = _('running non-interactively, use %s instead') % cmdsuggest
250 else:
266 else:
251 msg = _('running non-interactively')
267 msg = _('running non-interactively')
252 raise error.Abort(msg)
268 raise error.Abort(msg)
253
269
254 # make sure username is set before going interactive
270 # make sure username is set before going interactive
255 if not opts.get('user'):
271 if not opts.get('user'):
256 ui.username() # raise exception, username not provided
272 ui.username() # raise exception, username not provided
257
273
258 def recordfunc(ui, repo, message, match, opts):
274 def recordfunc(ui, repo, message, match, opts):
259 """This is generic record driver.
275 """This is generic record driver.
260
276
261 Its job is to interactively filter local changes, and
277 Its job is to interactively filter local changes, and
262 accordingly prepare working directory into a state in which the
278 accordingly prepare working directory into a state in which the
263 job can be delegated to a non-interactive commit command such as
279 job can be delegated to a non-interactive commit command such as
264 'commit' or 'qrefresh'.
280 'commit' or 'qrefresh'.
265
281
266 After the actual job is done by non-interactive command, the
282 After the actual job is done by non-interactive command, the
267 working directory is restored to its original state.
283 working directory is restored to its original state.
268
284
269 In the end we'll record interesting changes, and everything else
285 In the end we'll record interesting changes, and everything else
270 will be left in place, so the user can continue working.
286 will be left in place, so the user can continue working.
271 """
287 """
272 if not opts.get('interactive-unshelve'):
288 if not opts.get('interactive-unshelve'):
273 checkunfinished(repo, commit=True)
289 checkunfinished(repo, commit=True)
274 wctx = repo[None]
290 wctx = repo[None]
275 merge = len(wctx.parents()) > 1
291 merge = len(wctx.parents()) > 1
276 if merge:
292 if merge:
277 raise error.Abort(_('cannot partially commit a merge '
293 raise error.Abort(_('cannot partially commit a merge '
278 '(use "hg commit" instead)'))
294 '(use "hg commit" instead)'))
279
295
280 def fail(f, msg):
296 def fail(f, msg):
281 raise error.Abort('%s: %s' % (f, msg))
297 raise error.Abort('%s: %s' % (f, msg))
282
298
283 force = opts.get('force')
299 force = opts.get('force')
284 if not force:
300 if not force:
285 vdirs = []
301 vdirs = []
286 match = matchmod.badmatch(match, fail)
302 match = matchmod.badmatch(match, fail)
287 match.explicitdir = vdirs.append
303 match.explicitdir = vdirs.append
288
304
289 status = repo.status(match=match)
305 status = repo.status(match=match)
290
306
291 overrides = {(b'ui', b'commitsubrepos'): True}
307 overrides = {(b'ui', b'commitsubrepos'): True}
292
308
293 with repo.ui.configoverride(overrides, b'record'):
309 with repo.ui.configoverride(overrides, b'record'):
294 # subrepoutil.precommit() modifies the status
310 # subrepoutil.precommit() modifies the status
295 tmpstatus = scmutil.status(copymod.copy(status[0]),
311 tmpstatus = scmutil.status(copymod.copy(status[0]),
296 copymod.copy(status[1]),
312 copymod.copy(status[1]),
297 copymod.copy(status[2]),
313 copymod.copy(status[2]),
298 copymod.copy(status[3]),
314 copymod.copy(status[3]),
299 copymod.copy(status[4]),
315 copymod.copy(status[4]),
300 copymod.copy(status[5]),
316 copymod.copy(status[5]),
301 copymod.copy(status[6]))
317 copymod.copy(status[6]))
302
318
303 # Force allows -X subrepo to skip the subrepo.
319 # Force allows -X subrepo to skip the subrepo.
304 subs, commitsubs, newstate = subrepoutil.precommit(
320 subs, commitsubs, newstate = subrepoutil.precommit(
305 repo.ui, wctx, tmpstatus, match, force=True)
321 repo.ui, wctx, tmpstatus, match, force=True)
306 for s in subs:
322 for s in subs:
307 if s in commitsubs:
323 if s in commitsubs:
308 dirtyreason = wctx.sub(s).dirtyreason(True)
324 dirtyreason = wctx.sub(s).dirtyreason(True)
309 raise error.Abort(dirtyreason)
325 raise error.Abort(dirtyreason)
310
326
311 if not force:
327 if not force:
312 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
328 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
313 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
329 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
314 section='commands',
330 section='commands',
315 configprefix='commit.interactive.')
331 configprefix='commit.interactive.')
316 diffopts.nodates = True
332 diffopts.nodates = True
317 diffopts.git = True
333 diffopts.git = True
318 diffopts.showfunc = True
334 diffopts.showfunc = True
319 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
335 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
320 originalchunks = patch.parsepatch(originaldiff)
336 originalchunks = patch.parsepatch(originaldiff)
321 match = scmutil.match(repo[None], pats)
337 match = scmutil.match(repo[None], pats)
322
338
323 # 1. filter patch, since we are intending to apply subset of it
339 # 1. filter patch, since we are intending to apply subset of it
324 try:
340 try:
325 chunks, newopts = filterfn(ui, originalchunks, match)
341 chunks, newopts = filterfn(ui, originalchunks, match)
326 except error.PatchError as err:
342 except error.PatchError as err:
327 raise error.Abort(_('error parsing patch: %s') % err)
343 raise error.Abort(_('error parsing patch: %s') % err)
328 opts.update(newopts)
344 opts.update(newopts)
329
345
330 # We need to keep a backup of files that have been newly added and
346 # We need to keep a backup of files that have been newly added and
331 # modified during the recording process because there is a previous
347 # modified during the recording process because there is a previous
332 # version without the edit in the workdir. We also will need to restore
348 # version without the edit in the workdir. We also will need to restore
333 # files that were the sources of renames so that the patch application
349 # files that were the sources of renames so that the patch application
334 # works.
350 # works.
335 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks,
351 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks,
336 originalchunks)
352 originalchunks)
337 contenders = set()
353 contenders = set()
338 for h in chunks:
354 for h in chunks:
339 try:
355 try:
340 contenders.update(set(h.files()))
356 contenders.update(set(h.files()))
341 except AttributeError:
357 except AttributeError:
342 pass
358 pass
343
359
344 changed = status.modified + status.added + status.removed
360 changed = status.modified + status.added + status.removed
345 newfiles = [f for f in changed if f in contenders]
361 newfiles = [f for f in changed if f in contenders]
346 if not newfiles:
362 if not newfiles:
347 ui.status(_('no changes to record\n'))
363 ui.status(_('no changes to record\n'))
348 return 0
364 return 0
349
365
350 modified = set(status.modified)
366 modified = set(status.modified)
351
367
352 # 2. backup changed files, so we can restore them in the end
368 # 2. backup changed files, so we can restore them in the end
353
369
354 if backupall:
370 if backupall:
355 tobackup = changed
371 tobackup = changed
356 else:
372 else:
357 tobackup = [f for f in newfiles if f in modified or f in
373 tobackup = [f for f in newfiles if f in modified or f in
358 newlyaddedandmodifiedfiles]
374 newlyaddedandmodifiedfiles]
359 backups = {}
375 backups = {}
360 if tobackup:
376 if tobackup:
361 backupdir = repo.vfs.join('record-backups')
377 backupdir = repo.vfs.join('record-backups')
362 try:
378 try:
363 os.mkdir(backupdir)
379 os.mkdir(backupdir)
364 except OSError as err:
380 except OSError as err:
365 if err.errno != errno.EEXIST:
381 if err.errno != errno.EEXIST:
366 raise
382 raise
367 try:
383 try:
368 # backup continues
384 # backup continues
369 for f in tobackup:
385 for f in tobackup:
370 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
386 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
371 dir=backupdir)
387 dir=backupdir)
372 os.close(fd)
388 os.close(fd)
373 ui.debug('backup %r as %r\n' % (f, tmpname))
389 ui.debug('backup %r as %r\n' % (f, tmpname))
374 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
390 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
375 backups[f] = tmpname
391 backups[f] = tmpname
376
392
377 fp = stringio()
393 fp = stringio()
378 for c in chunks:
394 for c in chunks:
379 fname = c.filename()
395 fname = c.filename()
380 if fname in backups:
396 if fname in backups:
381 c.write(fp)
397 c.write(fp)
382 dopatch = fp.tell()
398 dopatch = fp.tell()
383 fp.seek(0)
399 fp.seek(0)
384
400
385 # 2.5 optionally review / modify patch in text editor
401 # 2.5 optionally review / modify patch in text editor
386 if opts.get('review', False):
402 if opts.get('review', False):
387 patchtext = (crecordmod.diffhelptext
403 patchtext = (crecordmod.diffhelptext
388 + crecordmod.patchhelptext
404 + crecordmod.patchhelptext
389 + fp.read())
405 + fp.read())
390 reviewedpatch = ui.edit(patchtext, "",
406 reviewedpatch = ui.edit(patchtext, "",
391 action="diff",
407 action="diff",
392 repopath=repo.path)
408 repopath=repo.path)
393 fp.truncate(0)
409 fp.truncate(0)
394 fp.write(reviewedpatch)
410 fp.write(reviewedpatch)
395 fp.seek(0)
411 fp.seek(0)
396
412
397 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
413 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
398 # 3a. apply filtered patch to clean repo (clean)
414 # 3a. apply filtered patch to clean repo (clean)
399 if backups:
415 if backups:
400 # Equivalent to hg.revert
416 # Equivalent to hg.revert
401 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
417 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
402 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
418 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
403 force=True, matcher=m)
419 force=True, matcher=m)
404
420
405 # 3b. (apply)
421 # 3b. (apply)
406 if dopatch:
422 if dopatch:
407 try:
423 try:
408 ui.debug('applying patch\n')
424 ui.debug('applying patch\n')
409 ui.debug(fp.getvalue())
425 ui.debug(fp.getvalue())
410 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
426 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
411 except error.PatchError as err:
427 except error.PatchError as err:
412 raise error.Abort(pycompat.bytestr(err))
428 raise error.Abort(pycompat.bytestr(err))
413 del fp
429 del fp
414
430
415 # 4. We prepared working directory according to filtered
431 # 4. We prepared working directory according to filtered
416 # patch. Now is the time to delegate the job to
432 # patch. Now is the time to delegate the job to
417 # commit/qrefresh or the like!
433 # commit/qrefresh or the like!
418
434
419 # Make all of the pathnames absolute.
435 # Make all of the pathnames absolute.
420 newfiles = [repo.wjoin(nf) for nf in newfiles]
436 newfiles = [repo.wjoin(nf) for nf in newfiles]
421 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
437 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
422 finally:
438 finally:
423 # 5. finally restore backed-up files
439 # 5. finally restore backed-up files
424 try:
440 try:
425 dirstate = repo.dirstate
441 dirstate = repo.dirstate
426 for realname, tmpname in backups.iteritems():
442 for realname, tmpname in backups.iteritems():
427 ui.debug('restoring %r to %r\n' % (tmpname, realname))
443 ui.debug('restoring %r to %r\n' % (tmpname, realname))
428
444
429 if dirstate[realname] == 'n':
445 if dirstate[realname] == 'n':
430 # without normallookup, restoring timestamp
446 # without normallookup, restoring timestamp
431 # may cause partially committed files
447 # may cause partially committed files
432 # to be treated as unmodified
448 # to be treated as unmodified
433 dirstate.normallookup(realname)
449 dirstate.normallookup(realname)
434
450
435 # copystat=True here and above are a hack to trick any
451 # copystat=True here and above are a hack to trick any
436 # editors that have f open that we haven't modified them.
452 # editors that have f open that we haven't modified them.
437 #
453 #
438 # Also note that this racy as an editor could notice the
454 # Also note that this racy as an editor could notice the
439 # file's mtime before we've finished writing it.
455 # file's mtime before we've finished writing it.
440 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
456 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
441 os.unlink(tmpname)
457 os.unlink(tmpname)
442 if tobackup:
458 if tobackup:
443 os.rmdir(backupdir)
459 os.rmdir(backupdir)
444 except OSError:
460 except OSError:
445 pass
461 pass
446
462
447 def recordinwlock(ui, repo, message, match, opts):
463 def recordinwlock(ui, repo, message, match, opts):
448 with repo.wlock():
464 with repo.wlock():
449 return recordfunc(ui, repo, message, match, opts)
465 return recordfunc(ui, repo, message, match, opts)
450
466
451 return commit(ui, repo, recordinwlock, pats, opts)
467 return commit(ui, repo, recordinwlock, pats, opts)
452
468
453 class dirnode(object):
469 class dirnode(object):
454 """
470 """
455 Represent a directory in user working copy with information required for
471 Represent a directory in user working copy with information required for
456 the purpose of tersing its status.
472 the purpose of tersing its status.
457
473
458 path is the path to the directory, without a trailing '/'
474 path is the path to the directory, without a trailing '/'
459
475
460 statuses is a set of statuses of all files in this directory (this includes
476 statuses is a set of statuses of all files in this directory (this includes
461 all the files in all the subdirectories too)
477 all the files in all the subdirectories too)
462
478
463 files is a list of files which are direct child of this directory
479 files is a list of files which are direct child of this directory
464
480
465 subdirs is a dictionary of sub-directory name as the key and it's own
481 subdirs is a dictionary of sub-directory name as the key and it's own
466 dirnode object as the value
482 dirnode object as the value
467 """
483 """
468
484
469 def __init__(self, dirpath):
485 def __init__(self, dirpath):
470 self.path = dirpath
486 self.path = dirpath
471 self.statuses = set()
487 self.statuses = set()
472 self.files = []
488 self.files = []
473 self.subdirs = {}
489 self.subdirs = {}
474
490
475 def _addfileindir(self, filename, status):
491 def _addfileindir(self, filename, status):
476 """Add a file in this directory as a direct child."""
492 """Add a file in this directory as a direct child."""
477 self.files.append((filename, status))
493 self.files.append((filename, status))
478
494
479 def addfile(self, filename, status):
495 def addfile(self, filename, status):
480 """
496 """
481 Add a file to this directory or to its direct parent directory.
497 Add a file to this directory or to its direct parent directory.
482
498
483 If the file is not direct child of this directory, we traverse to the
499 If the file is not direct child of this directory, we traverse to the
484 directory of which this file is a direct child of and add the file
500 directory of which this file is a direct child of and add the file
485 there.
501 there.
486 """
502 """
487
503
488 # the filename contains a path separator, it means it's not the direct
504 # the filename contains a path separator, it means it's not the direct
489 # child of this directory
505 # child of this directory
490 if '/' in filename:
506 if '/' in filename:
491 subdir, filep = filename.split('/', 1)
507 subdir, filep = filename.split('/', 1)
492
508
493 # does the dirnode object for subdir exists
509 # does the dirnode object for subdir exists
494 if subdir not in self.subdirs:
510 if subdir not in self.subdirs:
495 subdirpath = pathutil.join(self.path, subdir)
511 subdirpath = pathutil.join(self.path, subdir)
496 self.subdirs[subdir] = dirnode(subdirpath)
512 self.subdirs[subdir] = dirnode(subdirpath)
497
513
498 # try adding the file in subdir
514 # try adding the file in subdir
499 self.subdirs[subdir].addfile(filep, status)
515 self.subdirs[subdir].addfile(filep, status)
500
516
501 else:
517 else:
502 self._addfileindir(filename, status)
518 self._addfileindir(filename, status)
503
519
504 if status not in self.statuses:
520 if status not in self.statuses:
505 self.statuses.add(status)
521 self.statuses.add(status)
506
522
507 def iterfilepaths(self):
523 def iterfilepaths(self):
508 """Yield (status, path) for files directly under this directory."""
524 """Yield (status, path) for files directly under this directory."""
509 for f, st in self.files:
525 for f, st in self.files:
510 yield st, pathutil.join(self.path, f)
526 yield st, pathutil.join(self.path, f)
511
527
512 def tersewalk(self, terseargs):
528 def tersewalk(self, terseargs):
513 """
529 """
514 Yield (status, path) obtained by processing the status of this
530 Yield (status, path) obtained by processing the status of this
515 dirnode.
531 dirnode.
516
532
517 terseargs is the string of arguments passed by the user with `--terse`
533 terseargs is the string of arguments passed by the user with `--terse`
518 flag.
534 flag.
519
535
520 Following are the cases which can happen:
536 Following are the cases which can happen:
521
537
522 1) All the files in the directory (including all the files in its
538 1) All the files in the directory (including all the files in its
523 subdirectories) share the same status and the user has asked us to terse
539 subdirectories) share the same status and the user has asked us to terse
524 that status. -> yield (status, dirpath). dirpath will end in '/'.
540 that status. -> yield (status, dirpath). dirpath will end in '/'.
525
541
526 2) Otherwise, we do following:
542 2) Otherwise, we do following:
527
543
528 a) Yield (status, filepath) for all the files which are in this
544 a) Yield (status, filepath) for all the files which are in this
529 directory (only the ones in this directory, not the subdirs)
545 directory (only the ones in this directory, not the subdirs)
530
546
531 b) Recurse the function on all the subdirectories of this
547 b) Recurse the function on all the subdirectories of this
532 directory
548 directory
533 """
549 """
534
550
535 if len(self.statuses) == 1:
551 if len(self.statuses) == 1:
536 onlyst = self.statuses.pop()
552 onlyst = self.statuses.pop()
537
553
538 # Making sure we terse only when the status abbreviation is
554 # Making sure we terse only when the status abbreviation is
539 # passed as terse argument
555 # passed as terse argument
540 if onlyst in terseargs:
556 if onlyst in terseargs:
541 yield onlyst, self.path + '/'
557 yield onlyst, self.path + '/'
542 return
558 return
543
559
544 # add the files to status list
560 # add the files to status list
545 for st, fpath in self.iterfilepaths():
561 for st, fpath in self.iterfilepaths():
546 yield st, fpath
562 yield st, fpath
547
563
548 #recurse on the subdirs
564 #recurse on the subdirs
549 for dirobj in self.subdirs.values():
565 for dirobj in self.subdirs.values():
550 for st, fpath in dirobj.tersewalk(terseargs):
566 for st, fpath in dirobj.tersewalk(terseargs):
551 yield st, fpath
567 yield st, fpath
552
568
553 def tersedir(statuslist, terseargs):
569 def tersedir(statuslist, terseargs):
554 """
570 """
555 Terse the status if all the files in a directory shares the same status.
571 Terse the status if all the files in a directory shares the same status.
556
572
557 statuslist is scmutil.status() object which contains a list of files for
573 statuslist is scmutil.status() object which contains a list of files for
558 each status.
574 each status.
559 terseargs is string which is passed by the user as the argument to `--terse`
575 terseargs is string which is passed by the user as the argument to `--terse`
560 flag.
576 flag.
561
577
562 The function makes a tree of objects of dirnode class, and at each node it
578 The function makes a tree of objects of dirnode class, and at each node it
563 stores the information required to know whether we can terse a certain
579 stores the information required to know whether we can terse a certain
564 directory or not.
580 directory or not.
565 """
581 """
566 # the order matters here as that is used to produce final list
582 # the order matters here as that is used to produce final list
567 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
583 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
568
584
569 # checking the argument validity
585 # checking the argument validity
570 for s in pycompat.bytestr(terseargs):
586 for s in pycompat.bytestr(terseargs):
571 if s not in allst:
587 if s not in allst:
572 raise error.Abort(_("'%s' not recognized") % s)
588 raise error.Abort(_("'%s' not recognized") % s)
573
589
574 # creating a dirnode object for the root of the repo
590 # creating a dirnode object for the root of the repo
575 rootobj = dirnode('')
591 rootobj = dirnode('')
576 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
592 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
577 'ignored', 'removed')
593 'ignored', 'removed')
578
594
579 tersedict = {}
595 tersedict = {}
580 for attrname in pstatus:
596 for attrname in pstatus:
581 statuschar = attrname[0:1]
597 statuschar = attrname[0:1]
582 for f in getattr(statuslist, attrname):
598 for f in getattr(statuslist, attrname):
583 rootobj.addfile(f, statuschar)
599 rootobj.addfile(f, statuschar)
584 tersedict[statuschar] = []
600 tersedict[statuschar] = []
585
601
586 # we won't be tersing the root dir, so add files in it
602 # we won't be tersing the root dir, so add files in it
587 for st, fpath in rootobj.iterfilepaths():
603 for st, fpath in rootobj.iterfilepaths():
588 tersedict[st].append(fpath)
604 tersedict[st].append(fpath)
589
605
590 # process each sub-directory and build tersedict
606 # process each sub-directory and build tersedict
591 for subdir in rootobj.subdirs.values():
607 for subdir in rootobj.subdirs.values():
592 for st, f in subdir.tersewalk(terseargs):
608 for st, f in subdir.tersewalk(terseargs):
593 tersedict[st].append(f)
609 tersedict[st].append(f)
594
610
595 tersedlist = []
611 tersedlist = []
596 for st in allst:
612 for st in allst:
597 tersedict[st].sort()
613 tersedict[st].sort()
598 tersedlist.append(tersedict[st])
614 tersedlist.append(tersedict[st])
599
615
600 return tersedlist
616 return tersedlist
601
617
602 def _commentlines(raw):
618 def _commentlines(raw):
603 '''Surround lineswith a comment char and a new line'''
619 '''Surround lineswith a comment char and a new line'''
604 lines = raw.splitlines()
620 lines = raw.splitlines()
605 commentedlines = ['# %s' % line for line in lines]
621 commentedlines = ['# %s' % line for line in lines]
606 return '\n'.join(commentedlines) + '\n'
622 return '\n'.join(commentedlines) + '\n'
607
623
608 def _conflictsmsg(repo):
624 def _conflictsmsg(repo):
609 mergestate = mergemod.mergestate.read(repo)
625 mergestate = mergemod.mergestate.read(repo)
610 if not mergestate.active():
626 if not mergestate.active():
611 return
627 return
612
628
613 m = scmutil.match(repo[None])
629 m = scmutil.match(repo[None])
614 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
630 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
615 if unresolvedlist:
631 if unresolvedlist:
616 mergeliststr = '\n'.join(
632 mergeliststr = '\n'.join(
617 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
633 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
618 for path in sorted(unresolvedlist)])
634 for path in sorted(unresolvedlist)])
619 msg = _('''Unresolved merge conflicts:
635 msg = _('''Unresolved merge conflicts:
620
636
621 %s
637 %s
622
638
623 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
639 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
624 else:
640 else:
625 msg = _('No unresolved merge conflicts.')
641 msg = _('No unresolved merge conflicts.')
626
642
627 return _commentlines(msg)
643 return _commentlines(msg)
628
644
629 def morestatus(repo, fm):
645 def morestatus(repo, fm):
630 statetuple = statemod.getrepostate(repo)
646 statetuple = statemod.getrepostate(repo)
631 label = 'status.morestatus'
647 label = 'status.morestatus'
632 if statetuple:
648 if statetuple:
633 state, helpfulmsg = statetuple
649 state, helpfulmsg = statetuple
634 statemsg = _('The repository is in an unfinished *%s* state.') % state
650 statemsg = _('The repository is in an unfinished *%s* state.') % state
635 fm.plain('%s\n' % _commentlines(statemsg), label=label)
651 fm.plain('%s\n' % _commentlines(statemsg), label=label)
636 conmsg = _conflictsmsg(repo)
652 conmsg = _conflictsmsg(repo)
637 if conmsg:
653 if conmsg:
638 fm.plain('%s\n' % conmsg, label=label)
654 fm.plain('%s\n' % conmsg, label=label)
639 if helpfulmsg:
655 if helpfulmsg:
640 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label)
656 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label)
641
657
642 def findpossible(cmd, table, strict=False):
658 def findpossible(cmd, table, strict=False):
643 """
659 """
644 Return cmd -> (aliases, command table entry)
660 Return cmd -> (aliases, command table entry)
645 for each matching command.
661 for each matching command.
646 Return debug commands (or their aliases) only if no normal command matches.
662 Return debug commands (or their aliases) only if no normal command matches.
647 """
663 """
648 choice = {}
664 choice = {}
649 debugchoice = {}
665 debugchoice = {}
650
666
651 if cmd in table:
667 if cmd in table:
652 # short-circuit exact matches, "log" alias beats "log|history"
668 # short-circuit exact matches, "log" alias beats "log|history"
653 keys = [cmd]
669 keys = [cmd]
654 else:
670 else:
655 keys = table.keys()
671 keys = table.keys()
656
672
657 allcmds = []
673 allcmds = []
658 for e in keys:
674 for e in keys:
659 aliases = parsealiases(e)
675 aliases = parsealiases(e)
660 allcmds.extend(aliases)
676 allcmds.extend(aliases)
661 found = None
677 found = None
662 if cmd in aliases:
678 if cmd in aliases:
663 found = cmd
679 found = cmd
664 elif not strict:
680 elif not strict:
665 for a in aliases:
681 for a in aliases:
666 if a.startswith(cmd):
682 if a.startswith(cmd):
667 found = a
683 found = a
668 break
684 break
669 if found is not None:
685 if found is not None:
670 if aliases[0].startswith("debug") or found.startswith("debug"):
686 if aliases[0].startswith("debug") or found.startswith("debug"):
671 debugchoice[found] = (aliases, table[e])
687 debugchoice[found] = (aliases, table[e])
672 else:
688 else:
673 choice[found] = (aliases, table[e])
689 choice[found] = (aliases, table[e])
674
690
675 if not choice and debugchoice:
691 if not choice and debugchoice:
676 choice = debugchoice
692 choice = debugchoice
677
693
678 return choice, allcmds
694 return choice, allcmds
679
695
680 def findcmd(cmd, table, strict=True):
696 def findcmd(cmd, table, strict=True):
681 """Return (aliases, command table entry) for command string."""
697 """Return (aliases, command table entry) for command string."""
682 choice, allcmds = findpossible(cmd, table, strict)
698 choice, allcmds = findpossible(cmd, table, strict)
683
699
684 if cmd in choice:
700 if cmd in choice:
685 return choice[cmd]
701 return choice[cmd]
686
702
687 if len(choice) > 1:
703 if len(choice) > 1:
688 clist = sorted(choice)
704 clist = sorted(choice)
689 raise error.AmbiguousCommand(cmd, clist)
705 raise error.AmbiguousCommand(cmd, clist)
690
706
691 if choice:
707 if choice:
692 return list(choice.values())[0]
708 return list(choice.values())[0]
693
709
694 raise error.UnknownCommand(cmd, allcmds)
710 raise error.UnknownCommand(cmd, allcmds)
695
711
696 def changebranch(ui, repo, revs, label):
712 def changebranch(ui, repo, revs, label):
697 """ Change the branch name of given revs to label """
713 """ Change the branch name of given revs to label """
698
714
699 with repo.wlock(), repo.lock(), repo.transaction('branches'):
715 with repo.wlock(), repo.lock(), repo.transaction('branches'):
700 # abort in case of uncommitted merge or dirty wdir
716 # abort in case of uncommitted merge or dirty wdir
701 bailifchanged(repo)
717 bailifchanged(repo)
702 revs = scmutil.revrange(repo, revs)
718 revs = scmutil.revrange(repo, revs)
703 if not revs:
719 if not revs:
704 raise error.Abort("empty revision set")
720 raise error.Abort("empty revision set")
705 roots = repo.revs('roots(%ld)', revs)
721 roots = repo.revs('roots(%ld)', revs)
706 if len(roots) > 1:
722 if len(roots) > 1:
707 raise error.Abort(_("cannot change branch of non-linear revisions"))
723 raise error.Abort(_("cannot change branch of non-linear revisions"))
708 rewriteutil.precheck(repo, revs, 'change branch of')
724 rewriteutil.precheck(repo, revs, 'change branch of')
709
725
710 root = repo[roots.first()]
726 root = repo[roots.first()]
711 rpb = {parent.branch() for parent in root.parents()}
727 rpb = {parent.branch() for parent in root.parents()}
712 if label not in rpb and label in repo.branchmap():
728 if label not in rpb and label in repo.branchmap():
713 raise error.Abort(_("a branch of the same name already exists"))
729 raise error.Abort(_("a branch of the same name already exists"))
714
730
715 if repo.revs('obsolete() and %ld', revs):
731 if repo.revs('obsolete() and %ld', revs):
716 raise error.Abort(_("cannot change branch of a obsolete changeset"))
732 raise error.Abort(_("cannot change branch of a obsolete changeset"))
717
733
718 # make sure only topological heads
734 # make sure only topological heads
719 if repo.revs('heads(%ld) - head()', revs):
735 if repo.revs('heads(%ld) - head()', revs):
720 raise error.Abort(_("cannot change branch in middle of a stack"))
736 raise error.Abort(_("cannot change branch in middle of a stack"))
721
737
722 replacements = {}
738 replacements = {}
723 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
739 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
724 # mercurial.subrepo -> mercurial.cmdutil
740 # mercurial.subrepo -> mercurial.cmdutil
725 from . import context
741 from . import context
726 for rev in revs:
742 for rev in revs:
727 ctx = repo[rev]
743 ctx = repo[rev]
728 oldbranch = ctx.branch()
744 oldbranch = ctx.branch()
729 # check if ctx has same branch
745 # check if ctx has same branch
730 if oldbranch == label:
746 if oldbranch == label:
731 continue
747 continue
732
748
733 def filectxfn(repo, newctx, path):
749 def filectxfn(repo, newctx, path):
734 try:
750 try:
735 return ctx[path]
751 return ctx[path]
736 except error.ManifestLookupError:
752 except error.ManifestLookupError:
737 return None
753 return None
738
754
739 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
755 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
740 % (hex(ctx.node()), oldbranch, label))
756 % (hex(ctx.node()), oldbranch, label))
741 extra = ctx.extra()
757 extra = ctx.extra()
742 extra['branch_change'] = hex(ctx.node())
758 extra['branch_change'] = hex(ctx.node())
743 # While changing branch of set of linear commits, make sure that
759 # While changing branch of set of linear commits, make sure that
744 # we base our commits on new parent rather than old parent which
760 # we base our commits on new parent rather than old parent which
745 # was obsoleted while changing the branch
761 # was obsoleted while changing the branch
746 p1 = ctx.p1().node()
762 p1 = ctx.p1().node()
747 p2 = ctx.p2().node()
763 p2 = ctx.p2().node()
748 if p1 in replacements:
764 if p1 in replacements:
749 p1 = replacements[p1][0]
765 p1 = replacements[p1][0]
750 if p2 in replacements:
766 if p2 in replacements:
751 p2 = replacements[p2][0]
767 p2 = replacements[p2][0]
752
768
753 mc = context.memctx(repo, (p1, p2),
769 mc = context.memctx(repo, (p1, p2),
754 ctx.description(),
770 ctx.description(),
755 ctx.files(),
771 ctx.files(),
756 filectxfn,
772 filectxfn,
757 user=ctx.user(),
773 user=ctx.user(),
758 date=ctx.date(),
774 date=ctx.date(),
759 extra=extra,
775 extra=extra,
760 branch=label)
776 branch=label)
761
777
762 newnode = repo.commitctx(mc)
778 newnode = repo.commitctx(mc)
763 replacements[ctx.node()] = (newnode,)
779 replacements[ctx.node()] = (newnode,)
764 ui.debug('new node id is %s\n' % hex(newnode))
780 ui.debug('new node id is %s\n' % hex(newnode))
765
781
766 # create obsmarkers and move bookmarks
782 # create obsmarkers and move bookmarks
767 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
783 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
768
784
769 # move the working copy too
785 # move the working copy too
770 wctx = repo[None]
786 wctx = repo[None]
771 # in-progress merge is a bit too complex for now.
787 # in-progress merge is a bit too complex for now.
772 if len(wctx.parents()) == 1:
788 if len(wctx.parents()) == 1:
773 newid = replacements.get(wctx.p1().node())
789 newid = replacements.get(wctx.p1().node())
774 if newid is not None:
790 if newid is not None:
775 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
791 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
776 # mercurial.cmdutil
792 # mercurial.cmdutil
777 from . import hg
793 from . import hg
778 hg.update(repo, newid[0], quietempty=True)
794 hg.update(repo, newid[0], quietempty=True)
779
795
780 ui.status(_("changed branch on %d changesets\n") % len(replacements))
796 ui.status(_("changed branch on %d changesets\n") % len(replacements))
781
797
782 def findrepo(p):
798 def findrepo(p):
783 while not os.path.isdir(os.path.join(p, ".hg")):
799 while not os.path.isdir(os.path.join(p, ".hg")):
784 oldp, p = p, os.path.dirname(p)
800 oldp, p = p, os.path.dirname(p)
785 if p == oldp:
801 if p == oldp:
786 return None
802 return None
787
803
788 return p
804 return p
789
805
790 def bailifchanged(repo, merge=True, hint=None):
806 def bailifchanged(repo, merge=True, hint=None):
791 """ enforce the precondition that working directory must be clean.
807 """ enforce the precondition that working directory must be clean.
792
808
793 'merge' can be set to false if a pending uncommitted merge should be
809 'merge' can be set to false if a pending uncommitted merge should be
794 ignored (such as when 'update --check' runs).
810 ignored (such as when 'update --check' runs).
795
811
796 'hint' is the usual hint given to Abort exception.
812 'hint' is the usual hint given to Abort exception.
797 """
813 """
798
814
799 if merge and repo.dirstate.p2() != nullid:
815 if merge and repo.dirstate.p2() != nullid:
800 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
816 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
801 modified, added, removed, deleted = repo.status()[:4]
817 modified, added, removed, deleted = repo.status()[:4]
802 if modified or added or removed or deleted:
818 if modified or added or removed or deleted:
803 raise error.Abort(_('uncommitted changes'), hint=hint)
819 raise error.Abort(_('uncommitted changes'), hint=hint)
804 ctx = repo[None]
820 ctx = repo[None]
805 for s in sorted(ctx.substate):
821 for s in sorted(ctx.substate):
806 ctx.sub(s).bailifchanged(hint=hint)
822 ctx.sub(s).bailifchanged(hint=hint)
807
823
808 def logmessage(ui, opts):
824 def logmessage(ui, opts):
809 """ get the log message according to -m and -l option """
825 """ get the log message according to -m and -l option """
810 message = opts.get('message')
826 message = opts.get('message')
811 logfile = opts.get('logfile')
827 logfile = opts.get('logfile')
812
828
813 if message and logfile:
829 if message and logfile:
814 raise error.Abort(_('options --message and --logfile are mutually '
830 raise error.Abort(_('options --message and --logfile are mutually '
815 'exclusive'))
831 'exclusive'))
816 if not message and logfile:
832 if not message and logfile:
817 try:
833 try:
818 if isstdiofilename(logfile):
834 if isstdiofilename(logfile):
819 message = ui.fin.read()
835 message = ui.fin.read()
820 else:
836 else:
821 message = '\n'.join(util.readfile(logfile).splitlines())
837 message = '\n'.join(util.readfile(logfile).splitlines())
822 except IOError as inst:
838 except IOError as inst:
823 raise error.Abort(_("can't read commit message '%s': %s") %
839 raise error.Abort(_("can't read commit message '%s': %s") %
824 (logfile, encoding.strtolocal(inst.strerror)))
840 (logfile, encoding.strtolocal(inst.strerror)))
825 return message
841 return message
826
842
827 def mergeeditform(ctxorbool, baseformname):
843 def mergeeditform(ctxorbool, baseformname):
828 """return appropriate editform name (referencing a committemplate)
844 """return appropriate editform name (referencing a committemplate)
829
845
830 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
846 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
831 merging is committed.
847 merging is committed.
832
848
833 This returns baseformname with '.merge' appended if it is a merge,
849 This returns baseformname with '.merge' appended if it is a merge,
834 otherwise '.normal' is appended.
850 otherwise '.normal' is appended.
835 """
851 """
836 if isinstance(ctxorbool, bool):
852 if isinstance(ctxorbool, bool):
837 if ctxorbool:
853 if ctxorbool:
838 return baseformname + ".merge"
854 return baseformname + ".merge"
839 elif len(ctxorbool.parents()) > 1:
855 elif len(ctxorbool.parents()) > 1:
840 return baseformname + ".merge"
856 return baseformname + ".merge"
841
857
842 return baseformname + ".normal"
858 return baseformname + ".normal"
843
859
844 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
860 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
845 editform='', **opts):
861 editform='', **opts):
846 """get appropriate commit message editor according to '--edit' option
862 """get appropriate commit message editor according to '--edit' option
847
863
848 'finishdesc' is a function to be called with edited commit message
864 'finishdesc' is a function to be called with edited commit message
849 (= 'description' of the new changeset) just after editing, but
865 (= 'description' of the new changeset) just after editing, but
850 before checking empty-ness. It should return actual text to be
866 before checking empty-ness. It should return actual text to be
851 stored into history. This allows to change description before
867 stored into history. This allows to change description before
852 storing.
868 storing.
853
869
854 'extramsg' is a extra message to be shown in the editor instead of
870 'extramsg' is a extra message to be shown in the editor instead of
855 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
871 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
856 is automatically added.
872 is automatically added.
857
873
858 'editform' is a dot-separated list of names, to distinguish
874 'editform' is a dot-separated list of names, to distinguish
859 the purpose of commit text editing.
875 the purpose of commit text editing.
860
876
861 'getcommiteditor' returns 'commitforceeditor' regardless of
877 'getcommiteditor' returns 'commitforceeditor' regardless of
862 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
878 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
863 they are specific for usage in MQ.
879 they are specific for usage in MQ.
864 """
880 """
865 if edit or finishdesc or extramsg:
881 if edit or finishdesc or extramsg:
866 return lambda r, c, s: commitforceeditor(r, c, s,
882 return lambda r, c, s: commitforceeditor(r, c, s,
867 finishdesc=finishdesc,
883 finishdesc=finishdesc,
868 extramsg=extramsg,
884 extramsg=extramsg,
869 editform=editform)
885 editform=editform)
870 elif editform:
886 elif editform:
871 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
887 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
872 else:
888 else:
873 return commiteditor
889 return commiteditor
874
890
875 def _escapecommandtemplate(tmpl):
891 def _escapecommandtemplate(tmpl):
876 parts = []
892 parts = []
877 for typ, start, end in templater.scantemplate(tmpl, raw=True):
893 for typ, start, end in templater.scantemplate(tmpl, raw=True):
878 if typ == b'string':
894 if typ == b'string':
879 parts.append(stringutil.escapestr(tmpl[start:end]))
895 parts.append(stringutil.escapestr(tmpl[start:end]))
880 else:
896 else:
881 parts.append(tmpl[start:end])
897 parts.append(tmpl[start:end])
882 return b''.join(parts)
898 return b''.join(parts)
883
899
884 def rendercommandtemplate(ui, tmpl, props):
900 def rendercommandtemplate(ui, tmpl, props):
885 r"""Expand a literal template 'tmpl' in a way suitable for command line
901 r"""Expand a literal template 'tmpl' in a way suitable for command line
886
902
887 '\' in outermost string is not taken as an escape character because it
903 '\' in outermost string is not taken as an escape character because it
888 is a directory separator on Windows.
904 is a directory separator on Windows.
889
905
890 >>> from . import ui as uimod
906 >>> from . import ui as uimod
891 >>> ui = uimod.ui()
907 >>> ui = uimod.ui()
892 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
908 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
893 'c:\\foo'
909 'c:\\foo'
894 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
910 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
895 'c:{path}'
911 'c:{path}'
896 """
912 """
897 if not tmpl:
913 if not tmpl:
898 return tmpl
914 return tmpl
899 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
915 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
900 return t.renderdefault(props)
916 return t.renderdefault(props)
901
917
902 def rendertemplate(ctx, tmpl, props=None):
918 def rendertemplate(ctx, tmpl, props=None):
903 """Expand a literal template 'tmpl' byte-string against one changeset
919 """Expand a literal template 'tmpl' byte-string against one changeset
904
920
905 Each props item must be a stringify-able value or a callable returning
921 Each props item must be a stringify-able value or a callable returning
906 such value, i.e. no bare list nor dict should be passed.
922 such value, i.e. no bare list nor dict should be passed.
907 """
923 """
908 repo = ctx.repo()
924 repo = ctx.repo()
909 tres = formatter.templateresources(repo.ui, repo)
925 tres = formatter.templateresources(repo.ui, repo)
910 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
926 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
911 resources=tres)
927 resources=tres)
912 mapping = {'ctx': ctx}
928 mapping = {'ctx': ctx}
913 if props:
929 if props:
914 mapping.update(props)
930 mapping.update(props)
915 return t.renderdefault(mapping)
931 return t.renderdefault(mapping)
916
932
917 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
933 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
918 r"""Convert old-style filename format string to template string
934 r"""Convert old-style filename format string to template string
919
935
920 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
936 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
921 'foo-{reporoot|basename}-{seqno}.patch'
937 'foo-{reporoot|basename}-{seqno}.patch'
922 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
938 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
923 '{rev}{tags % "{tag}"}{node}'
939 '{rev}{tags % "{tag}"}{node}'
924
940
925 '\' in outermost strings has to be escaped because it is a directory
941 '\' in outermost strings has to be escaped because it is a directory
926 separator on Windows:
942 separator on Windows:
927
943
928 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
944 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
929 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
945 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
930 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
946 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
931 '\\\\\\\\foo\\\\bar.patch'
947 '\\\\\\\\foo\\\\bar.patch'
932 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
948 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
933 '\\\\{tags % "{tag}"}'
949 '\\\\{tags % "{tag}"}'
934
950
935 but inner strings follow the template rules (i.e. '\' is taken as an
951 but inner strings follow the template rules (i.e. '\' is taken as an
936 escape character):
952 escape character):
937
953
938 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
954 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
939 '{"c:\\tmp"}'
955 '{"c:\\tmp"}'
940 """
956 """
941 expander = {
957 expander = {
942 b'H': b'{node}',
958 b'H': b'{node}',
943 b'R': b'{rev}',
959 b'R': b'{rev}',
944 b'h': b'{node|short}',
960 b'h': b'{node|short}',
945 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
961 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
946 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
962 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
947 b'%': b'%',
963 b'%': b'%',
948 b'b': b'{reporoot|basename}',
964 b'b': b'{reporoot|basename}',
949 }
965 }
950 if total is not None:
966 if total is not None:
951 expander[b'N'] = b'{total}'
967 expander[b'N'] = b'{total}'
952 if seqno is not None:
968 if seqno is not None:
953 expander[b'n'] = b'{seqno}'
969 expander[b'n'] = b'{seqno}'
954 if total is not None and seqno is not None:
970 if total is not None and seqno is not None:
955 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
971 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
956 if pathname is not None:
972 if pathname is not None:
957 expander[b's'] = b'{pathname|basename}'
973 expander[b's'] = b'{pathname|basename}'
958 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
974 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
959 expander[b'p'] = b'{pathname}'
975 expander[b'p'] = b'{pathname}'
960
976
961 newname = []
977 newname = []
962 for typ, start, end in templater.scantemplate(pat, raw=True):
978 for typ, start, end in templater.scantemplate(pat, raw=True):
963 if typ != b'string':
979 if typ != b'string':
964 newname.append(pat[start:end])
980 newname.append(pat[start:end])
965 continue
981 continue
966 i = start
982 i = start
967 while i < end:
983 while i < end:
968 n = pat.find(b'%', i, end)
984 n = pat.find(b'%', i, end)
969 if n < 0:
985 if n < 0:
970 newname.append(stringutil.escapestr(pat[i:end]))
986 newname.append(stringutil.escapestr(pat[i:end]))
971 break
987 break
972 newname.append(stringutil.escapestr(pat[i:n]))
988 newname.append(stringutil.escapestr(pat[i:n]))
973 if n + 2 > end:
989 if n + 2 > end:
974 raise error.Abort(_("incomplete format spec in output "
990 raise error.Abort(_("incomplete format spec in output "
975 "filename"))
991 "filename"))
976 c = pat[n + 1:n + 2]
992 c = pat[n + 1:n + 2]
977 i = n + 2
993 i = n + 2
978 try:
994 try:
979 newname.append(expander[c])
995 newname.append(expander[c])
980 except KeyError:
996 except KeyError:
981 raise error.Abort(_("invalid format spec '%%%s' in output "
997 raise error.Abort(_("invalid format spec '%%%s' in output "
982 "filename") % c)
998 "filename") % c)
983 return ''.join(newname)
999 return ''.join(newname)
984
1000
985 def makefilename(ctx, pat, **props):
1001 def makefilename(ctx, pat, **props):
986 if not pat:
1002 if not pat:
987 return pat
1003 return pat
988 tmpl = _buildfntemplate(pat, **props)
1004 tmpl = _buildfntemplate(pat, **props)
989 # BUG: alias expansion shouldn't be made against template fragments
1005 # BUG: alias expansion shouldn't be made against template fragments
990 # rewritten from %-format strings, but we have no easy way to partially
1006 # rewritten from %-format strings, but we have no easy way to partially
991 # disable the expansion.
1007 # disable the expansion.
992 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1008 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
993
1009
994 def isstdiofilename(pat):
1010 def isstdiofilename(pat):
995 """True if the given pat looks like a filename denoting stdin/stdout"""
1011 """True if the given pat looks like a filename denoting stdin/stdout"""
996 return not pat or pat == '-'
1012 return not pat or pat == '-'
997
1013
998 class _unclosablefile(object):
1014 class _unclosablefile(object):
999 def __init__(self, fp):
1015 def __init__(self, fp):
1000 self._fp = fp
1016 self._fp = fp
1001
1017
1002 def close(self):
1018 def close(self):
1003 pass
1019 pass
1004
1020
1005 def __iter__(self):
1021 def __iter__(self):
1006 return iter(self._fp)
1022 return iter(self._fp)
1007
1023
1008 def __getattr__(self, attr):
1024 def __getattr__(self, attr):
1009 return getattr(self._fp, attr)
1025 return getattr(self._fp, attr)
1010
1026
1011 def __enter__(self):
1027 def __enter__(self):
1012 return self
1028 return self
1013
1029
1014 def __exit__(self, exc_type, exc_value, exc_tb):
1030 def __exit__(self, exc_type, exc_value, exc_tb):
1015 pass
1031 pass
1016
1032
1017 def makefileobj(ctx, pat, mode='wb', **props):
1033 def makefileobj(ctx, pat, mode='wb', **props):
1018 writable = mode not in ('r', 'rb')
1034 writable = mode not in ('r', 'rb')
1019
1035
1020 if isstdiofilename(pat):
1036 if isstdiofilename(pat):
1021 repo = ctx.repo()
1037 repo = ctx.repo()
1022 if writable:
1038 if writable:
1023 fp = repo.ui.fout
1039 fp = repo.ui.fout
1024 else:
1040 else:
1025 fp = repo.ui.fin
1041 fp = repo.ui.fin
1026 return _unclosablefile(fp)
1042 return _unclosablefile(fp)
1027 fn = makefilename(ctx, pat, **props)
1043 fn = makefilename(ctx, pat, **props)
1028 return open(fn, mode)
1044 return open(fn, mode)
1029
1045
1030 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1046 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1031 """opens the changelog, manifest, a filelog or a given revlog"""
1047 """opens the changelog, manifest, a filelog or a given revlog"""
1032 cl = opts['changelog']
1048 cl = opts['changelog']
1033 mf = opts['manifest']
1049 mf = opts['manifest']
1034 dir = opts['dir']
1050 dir = opts['dir']
1035 msg = None
1051 msg = None
1036 if cl and mf:
1052 if cl and mf:
1037 msg = _('cannot specify --changelog and --manifest at the same time')
1053 msg = _('cannot specify --changelog and --manifest at the same time')
1038 elif cl and dir:
1054 elif cl and dir:
1039 msg = _('cannot specify --changelog and --dir at the same time')
1055 msg = _('cannot specify --changelog and --dir at the same time')
1040 elif cl or mf or dir:
1056 elif cl or mf or dir:
1041 if file_:
1057 if file_:
1042 msg = _('cannot specify filename with --changelog or --manifest')
1058 msg = _('cannot specify filename with --changelog or --manifest')
1043 elif not repo:
1059 elif not repo:
1044 msg = _('cannot specify --changelog or --manifest or --dir '
1060 msg = _('cannot specify --changelog or --manifest or --dir '
1045 'without a repository')
1061 'without a repository')
1046 if msg:
1062 if msg:
1047 raise error.Abort(msg)
1063 raise error.Abort(msg)
1048
1064
1049 r = None
1065 r = None
1050 if repo:
1066 if repo:
1051 if cl:
1067 if cl:
1052 r = repo.unfiltered().changelog
1068 r = repo.unfiltered().changelog
1053 elif dir:
1069 elif dir:
1054 if 'treemanifest' not in repo.requirements:
1070 if 'treemanifest' not in repo.requirements:
1055 raise error.Abort(_("--dir can only be used on repos with "
1071 raise error.Abort(_("--dir can only be used on repos with "
1056 "treemanifest enabled"))
1072 "treemanifest enabled"))
1057 if not dir.endswith('/'):
1073 if not dir.endswith('/'):
1058 dir = dir + '/'
1074 dir = dir + '/'
1059 dirlog = repo.manifestlog.getstorage(dir)
1075 dirlog = repo.manifestlog.getstorage(dir)
1060 if len(dirlog):
1076 if len(dirlog):
1061 r = dirlog
1077 r = dirlog
1062 elif mf:
1078 elif mf:
1063 r = repo.manifestlog.getstorage(b'')
1079 r = repo.manifestlog.getstorage(b'')
1064 elif file_:
1080 elif file_:
1065 filelog = repo.file(file_)
1081 filelog = repo.file(file_)
1066 if len(filelog):
1082 if len(filelog):
1067 r = filelog
1083 r = filelog
1068
1084
1069 # Not all storage may be revlogs. If requested, try to return an actual
1085 # Not all storage may be revlogs. If requested, try to return an actual
1070 # revlog instance.
1086 # revlog instance.
1071 if returnrevlog:
1087 if returnrevlog:
1072 if isinstance(r, revlog.revlog):
1088 if isinstance(r, revlog.revlog):
1073 pass
1089 pass
1074 elif util.safehasattr(r, '_revlog'):
1090 elif util.safehasattr(r, '_revlog'):
1075 r = r._revlog
1091 r = r._revlog
1076 elif r is not None:
1092 elif r is not None:
1077 raise error.Abort(_('%r does not appear to be a revlog') % r)
1093 raise error.Abort(_('%r does not appear to be a revlog') % r)
1078
1094
1079 if not r:
1095 if not r:
1080 if not returnrevlog:
1096 if not returnrevlog:
1081 raise error.Abort(_('cannot give path to non-revlog'))
1097 raise error.Abort(_('cannot give path to non-revlog'))
1082
1098
1083 if not file_:
1099 if not file_:
1084 raise error.CommandError(cmd, _('invalid arguments'))
1100 raise error.CommandError(cmd, _('invalid arguments'))
1085 if not os.path.isfile(file_):
1101 if not os.path.isfile(file_):
1086 raise error.Abort(_("revlog '%s' not found") % file_)
1102 raise error.Abort(_("revlog '%s' not found") % file_)
1087 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1103 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1088 file_[:-2] + ".i")
1104 file_[:-2] + ".i")
1089 return r
1105 return r
1090
1106
1091 def openrevlog(repo, cmd, file_, opts):
1107 def openrevlog(repo, cmd, file_, opts):
1092 """Obtain a revlog backing storage of an item.
1108 """Obtain a revlog backing storage of an item.
1093
1109
1094 This is similar to ``openstorage()`` except it always returns a revlog.
1110 This is similar to ``openstorage()`` except it always returns a revlog.
1095
1111
1096 In most cases, a caller cares about the main storage object - not the
1112 In most cases, a caller cares about the main storage object - not the
1097 revlog backing it. Therefore, this function should only be used by code
1113 revlog backing it. Therefore, this function should only be used by code
1098 that needs to examine low-level revlog implementation details. e.g. debug
1114 that needs to examine low-level revlog implementation details. e.g. debug
1099 commands.
1115 commands.
1100 """
1116 """
1101 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1117 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1102
1118
1103 def copy(ui, repo, pats, opts, rename=False):
1119 def copy(ui, repo, pats, opts, rename=False):
1104 # called with the repo lock held
1120 # called with the repo lock held
1105 #
1121 #
1106 # hgsep => pathname that uses "/" to separate directories
1122 # hgsep => pathname that uses "/" to separate directories
1107 # ossep => pathname that uses os.sep to separate directories
1123 # ossep => pathname that uses os.sep to separate directories
1108 cwd = repo.getcwd()
1124 cwd = repo.getcwd()
1109 targets = {}
1125 targets = {}
1110 after = opts.get("after")
1126 after = opts.get("after")
1111 dryrun = opts.get("dry_run")
1127 dryrun = opts.get("dry_run")
1112 wctx = repo[None]
1128 wctx = repo[None]
1113
1129
1114 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1130 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1115 def walkpat(pat):
1131 def walkpat(pat):
1116 srcs = []
1132 srcs = []
1117 if after:
1133 if after:
1118 badstates = '?'
1134 badstates = '?'
1119 else:
1135 else:
1120 badstates = '?r'
1136 badstates = '?r'
1121 m = scmutil.match(wctx, [pat], opts, globbed=True)
1137 m = scmutil.match(wctx, [pat], opts, globbed=True)
1122 for abs in wctx.walk(m):
1138 for abs in wctx.walk(m):
1123 state = repo.dirstate[abs]
1139 state = repo.dirstate[abs]
1124 rel = uipathfn(abs)
1140 rel = uipathfn(abs)
1125 exact = m.exact(abs)
1141 exact = m.exact(abs)
1126 if state in badstates:
1142 if state in badstates:
1127 if exact and state == '?':
1143 if exact and state == '?':
1128 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1144 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1129 if exact and state == 'r':
1145 if exact and state == 'r':
1130 ui.warn(_('%s: not copying - file has been marked for'
1146 ui.warn(_('%s: not copying - file has been marked for'
1131 ' remove\n') % rel)
1147 ' remove\n') % rel)
1132 continue
1148 continue
1133 # abs: hgsep
1149 # abs: hgsep
1134 # rel: ossep
1150 # rel: ossep
1135 srcs.append((abs, rel, exact))
1151 srcs.append((abs, rel, exact))
1136 return srcs
1152 return srcs
1137
1153
1138 # abssrc: hgsep
1154 # abssrc: hgsep
1139 # relsrc: ossep
1155 # relsrc: ossep
1140 # otarget: ossep
1156 # otarget: ossep
1141 def copyfile(abssrc, relsrc, otarget, exact):
1157 def copyfile(abssrc, relsrc, otarget, exact):
1142 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1158 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1143 if '/' in abstarget:
1159 if '/' in abstarget:
1144 # We cannot normalize abstarget itself, this would prevent
1160 # We cannot normalize abstarget itself, this would prevent
1145 # case only renames, like a => A.
1161 # case only renames, like a => A.
1146 abspath, absname = abstarget.rsplit('/', 1)
1162 abspath, absname = abstarget.rsplit('/', 1)
1147 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1163 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1148 reltarget = repo.pathto(abstarget, cwd)
1164 reltarget = repo.pathto(abstarget, cwd)
1149 target = repo.wjoin(abstarget)
1165 target = repo.wjoin(abstarget)
1150 src = repo.wjoin(abssrc)
1166 src = repo.wjoin(abssrc)
1151 state = repo.dirstate[abstarget]
1167 state = repo.dirstate[abstarget]
1152
1168
1153 scmutil.checkportable(ui, abstarget)
1169 scmutil.checkportable(ui, abstarget)
1154
1170
1155 # check for collisions
1171 # check for collisions
1156 prevsrc = targets.get(abstarget)
1172 prevsrc = targets.get(abstarget)
1157 if prevsrc is not None:
1173 if prevsrc is not None:
1158 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1174 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1159 (reltarget, repo.pathto(abssrc, cwd),
1175 (reltarget, repo.pathto(abssrc, cwd),
1160 repo.pathto(prevsrc, cwd)))
1176 repo.pathto(prevsrc, cwd)))
1161 return True # report a failure
1177 return True # report a failure
1162
1178
1163 # check for overwrites
1179 # check for overwrites
1164 exists = os.path.lexists(target)
1180 exists = os.path.lexists(target)
1165 samefile = False
1181 samefile = False
1166 if exists and abssrc != abstarget:
1182 if exists and abssrc != abstarget:
1167 if (repo.dirstate.normalize(abssrc) ==
1183 if (repo.dirstate.normalize(abssrc) ==
1168 repo.dirstate.normalize(abstarget)):
1184 repo.dirstate.normalize(abstarget)):
1169 if not rename:
1185 if not rename:
1170 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1186 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1171 return True # report a failure
1187 return True # report a failure
1172 exists = False
1188 exists = False
1173 samefile = True
1189 samefile = True
1174
1190
1175 if not after and exists or after and state in 'mn':
1191 if not after and exists or after and state in 'mn':
1176 if not opts['force']:
1192 if not opts['force']:
1177 if state in 'mn':
1193 if state in 'mn':
1178 msg = _('%s: not overwriting - file already committed\n')
1194 msg = _('%s: not overwriting - file already committed\n')
1179 if after:
1195 if after:
1180 flags = '--after --force'
1196 flags = '--after --force'
1181 else:
1197 else:
1182 flags = '--force'
1198 flags = '--force'
1183 if rename:
1199 if rename:
1184 hint = _("('hg rename %s' to replace the file by "
1200 hint = _("('hg rename %s' to replace the file by "
1185 'recording a rename)\n') % flags
1201 'recording a rename)\n') % flags
1186 else:
1202 else:
1187 hint = _("('hg copy %s' to replace the file by "
1203 hint = _("('hg copy %s' to replace the file by "
1188 'recording a copy)\n') % flags
1204 'recording a copy)\n') % flags
1189 else:
1205 else:
1190 msg = _('%s: not overwriting - file exists\n')
1206 msg = _('%s: not overwriting - file exists\n')
1191 if rename:
1207 if rename:
1192 hint = _("('hg rename --after' to record the rename)\n")
1208 hint = _("('hg rename --after' to record the rename)\n")
1193 else:
1209 else:
1194 hint = _("('hg copy --after' to record the copy)\n")
1210 hint = _("('hg copy --after' to record the copy)\n")
1195 ui.warn(msg % reltarget)
1211 ui.warn(msg % reltarget)
1196 ui.warn(hint)
1212 ui.warn(hint)
1197 return True # report a failure
1213 return True # report a failure
1198
1214
1199 if after:
1215 if after:
1200 if not exists:
1216 if not exists:
1201 if rename:
1217 if rename:
1202 ui.warn(_('%s: not recording move - %s does not exist\n') %
1218 ui.warn(_('%s: not recording move - %s does not exist\n') %
1203 (relsrc, reltarget))
1219 (relsrc, reltarget))
1204 else:
1220 else:
1205 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1221 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1206 (relsrc, reltarget))
1222 (relsrc, reltarget))
1207 return True # report a failure
1223 return True # report a failure
1208 elif not dryrun:
1224 elif not dryrun:
1209 try:
1225 try:
1210 if exists:
1226 if exists:
1211 os.unlink(target)
1227 os.unlink(target)
1212 targetdir = os.path.dirname(target) or '.'
1228 targetdir = os.path.dirname(target) or '.'
1213 if not os.path.isdir(targetdir):
1229 if not os.path.isdir(targetdir):
1214 os.makedirs(targetdir)
1230 os.makedirs(targetdir)
1215 if samefile:
1231 if samefile:
1216 tmp = target + "~hgrename"
1232 tmp = target + "~hgrename"
1217 os.rename(src, tmp)
1233 os.rename(src, tmp)
1218 os.rename(tmp, target)
1234 os.rename(tmp, target)
1219 else:
1235 else:
1220 # Preserve stat info on renames, not on copies; this matches
1236 # Preserve stat info on renames, not on copies; this matches
1221 # Linux CLI behavior.
1237 # Linux CLI behavior.
1222 util.copyfile(src, target, copystat=rename)
1238 util.copyfile(src, target, copystat=rename)
1223 srcexists = True
1239 srcexists = True
1224 except IOError as inst:
1240 except IOError as inst:
1225 if inst.errno == errno.ENOENT:
1241 if inst.errno == errno.ENOENT:
1226 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1242 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1227 srcexists = False
1243 srcexists = False
1228 else:
1244 else:
1229 ui.warn(_('%s: cannot copy - %s\n') %
1245 ui.warn(_('%s: cannot copy - %s\n') %
1230 (relsrc, encoding.strtolocal(inst.strerror)))
1246 (relsrc, encoding.strtolocal(inst.strerror)))
1231 return True # report a failure
1247 return True # report a failure
1232
1248
1233 if ui.verbose or not exact:
1249 if ui.verbose or not exact:
1234 if rename:
1250 if rename:
1235 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1251 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1236 else:
1252 else:
1237 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1253 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1238
1254
1239 targets[abstarget] = abssrc
1255 targets[abstarget] = abssrc
1240
1256
1241 # fix up dirstate
1257 # fix up dirstate
1242 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1258 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1243 dryrun=dryrun, cwd=cwd)
1259 dryrun=dryrun, cwd=cwd)
1244 if rename and not dryrun:
1260 if rename and not dryrun:
1245 if not after and srcexists and not samefile:
1261 if not after and srcexists and not samefile:
1246 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1262 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1247 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1263 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1248 wctx.forget([abssrc])
1264 wctx.forget([abssrc])
1249
1265
1250 # pat: ossep
1266 # pat: ossep
1251 # dest ossep
1267 # dest ossep
1252 # srcs: list of (hgsep, hgsep, ossep, bool)
1268 # srcs: list of (hgsep, hgsep, ossep, bool)
1253 # return: function that takes hgsep and returns ossep
1269 # return: function that takes hgsep and returns ossep
1254 def targetpathfn(pat, dest, srcs):
1270 def targetpathfn(pat, dest, srcs):
1255 if os.path.isdir(pat):
1271 if os.path.isdir(pat):
1256 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1272 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1257 abspfx = util.localpath(abspfx)
1273 abspfx = util.localpath(abspfx)
1258 if destdirexists:
1274 if destdirexists:
1259 striplen = len(os.path.split(abspfx)[0])
1275 striplen = len(os.path.split(abspfx)[0])
1260 else:
1276 else:
1261 striplen = len(abspfx)
1277 striplen = len(abspfx)
1262 if striplen:
1278 if striplen:
1263 striplen += len(pycompat.ossep)
1279 striplen += len(pycompat.ossep)
1264 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1280 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1265 elif destdirexists:
1281 elif destdirexists:
1266 res = lambda p: os.path.join(dest,
1282 res = lambda p: os.path.join(dest,
1267 os.path.basename(util.localpath(p)))
1283 os.path.basename(util.localpath(p)))
1268 else:
1284 else:
1269 res = lambda p: dest
1285 res = lambda p: dest
1270 return res
1286 return res
1271
1287
1272 # pat: ossep
1288 # pat: ossep
1273 # dest ossep
1289 # dest ossep
1274 # srcs: list of (hgsep, hgsep, ossep, bool)
1290 # srcs: list of (hgsep, hgsep, ossep, bool)
1275 # return: function that takes hgsep and returns ossep
1291 # return: function that takes hgsep and returns ossep
1276 def targetpathafterfn(pat, dest, srcs):
1292 def targetpathafterfn(pat, dest, srcs):
1277 if matchmod.patkind(pat):
1293 if matchmod.patkind(pat):
1278 # a mercurial pattern
1294 # a mercurial pattern
1279 res = lambda p: os.path.join(dest,
1295 res = lambda p: os.path.join(dest,
1280 os.path.basename(util.localpath(p)))
1296 os.path.basename(util.localpath(p)))
1281 else:
1297 else:
1282 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1298 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1283 if len(abspfx) < len(srcs[0][0]):
1299 if len(abspfx) < len(srcs[0][0]):
1284 # A directory. Either the target path contains the last
1300 # A directory. Either the target path contains the last
1285 # component of the source path or it does not.
1301 # component of the source path or it does not.
1286 def evalpath(striplen):
1302 def evalpath(striplen):
1287 score = 0
1303 score = 0
1288 for s in srcs:
1304 for s in srcs:
1289 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1305 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1290 if os.path.lexists(t):
1306 if os.path.lexists(t):
1291 score += 1
1307 score += 1
1292 return score
1308 return score
1293
1309
1294 abspfx = util.localpath(abspfx)
1310 abspfx = util.localpath(abspfx)
1295 striplen = len(abspfx)
1311 striplen = len(abspfx)
1296 if striplen:
1312 if striplen:
1297 striplen += len(pycompat.ossep)
1313 striplen += len(pycompat.ossep)
1298 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1314 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1299 score = evalpath(striplen)
1315 score = evalpath(striplen)
1300 striplen1 = len(os.path.split(abspfx)[0])
1316 striplen1 = len(os.path.split(abspfx)[0])
1301 if striplen1:
1317 if striplen1:
1302 striplen1 += len(pycompat.ossep)
1318 striplen1 += len(pycompat.ossep)
1303 if evalpath(striplen1) > score:
1319 if evalpath(striplen1) > score:
1304 striplen = striplen1
1320 striplen = striplen1
1305 res = lambda p: os.path.join(dest,
1321 res = lambda p: os.path.join(dest,
1306 util.localpath(p)[striplen:])
1322 util.localpath(p)[striplen:])
1307 else:
1323 else:
1308 # a file
1324 # a file
1309 if destdirexists:
1325 if destdirexists:
1310 res = lambda p: os.path.join(dest,
1326 res = lambda p: os.path.join(dest,
1311 os.path.basename(util.localpath(p)))
1327 os.path.basename(util.localpath(p)))
1312 else:
1328 else:
1313 res = lambda p: dest
1329 res = lambda p: dest
1314 return res
1330 return res
1315
1331
1316 pats = scmutil.expandpats(pats)
1332 pats = scmutil.expandpats(pats)
1317 if not pats:
1333 if not pats:
1318 raise error.Abort(_('no source or destination specified'))
1334 raise error.Abort(_('no source or destination specified'))
1319 if len(pats) == 1:
1335 if len(pats) == 1:
1320 raise error.Abort(_('no destination specified'))
1336 raise error.Abort(_('no destination specified'))
1321 dest = pats.pop()
1337 dest = pats.pop()
1322 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1338 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1323 if not destdirexists:
1339 if not destdirexists:
1324 if len(pats) > 1 or matchmod.patkind(pats[0]):
1340 if len(pats) > 1 or matchmod.patkind(pats[0]):
1325 raise error.Abort(_('with multiple sources, destination must be an '
1341 raise error.Abort(_('with multiple sources, destination must be an '
1326 'existing directory'))
1342 'existing directory'))
1327 if util.endswithsep(dest):
1343 if util.endswithsep(dest):
1328 raise error.Abort(_('destination %s is not a directory') % dest)
1344 raise error.Abort(_('destination %s is not a directory') % dest)
1329
1345
1330 tfn = targetpathfn
1346 tfn = targetpathfn
1331 if after:
1347 if after:
1332 tfn = targetpathafterfn
1348 tfn = targetpathafterfn
1333 copylist = []
1349 copylist = []
1334 for pat in pats:
1350 for pat in pats:
1335 srcs = walkpat(pat)
1351 srcs = walkpat(pat)
1336 if not srcs:
1352 if not srcs:
1337 continue
1353 continue
1338 copylist.append((tfn(pat, dest, srcs), srcs))
1354 copylist.append((tfn(pat, dest, srcs), srcs))
1339 if not copylist:
1355 if not copylist:
1340 raise error.Abort(_('no files to copy'))
1356 raise error.Abort(_('no files to copy'))
1341
1357
1342 errors = 0
1358 errors = 0
1343 for targetpath, srcs in copylist:
1359 for targetpath, srcs in copylist:
1344 for abssrc, relsrc, exact in srcs:
1360 for abssrc, relsrc, exact in srcs:
1345 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1361 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1346 errors += 1
1362 errors += 1
1347
1363
1348 return errors != 0
1364 return errors != 0
1349
1365
1350 ## facility to let extension process additional data into an import patch
1366 ## facility to let extension process additional data into an import patch
1351 # list of identifier to be executed in order
1367 # list of identifier to be executed in order
1352 extrapreimport = [] # run before commit
1368 extrapreimport = [] # run before commit
1353 extrapostimport = [] # run after commit
1369 extrapostimport = [] # run after commit
1354 # mapping from identifier to actual import function
1370 # mapping from identifier to actual import function
1355 #
1371 #
1356 # 'preimport' are run before the commit is made and are provided the following
1372 # 'preimport' are run before the commit is made and are provided the following
1357 # arguments:
1373 # arguments:
1358 # - repo: the localrepository instance,
1374 # - repo: the localrepository instance,
1359 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1375 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1360 # - extra: the future extra dictionary of the changeset, please mutate it,
1376 # - extra: the future extra dictionary of the changeset, please mutate it,
1361 # - opts: the import options.
1377 # - opts: the import options.
1362 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1378 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1363 # mutation of in memory commit and more. Feel free to rework the code to get
1379 # mutation of in memory commit and more. Feel free to rework the code to get
1364 # there.
1380 # there.
1365 extrapreimportmap = {}
1381 extrapreimportmap = {}
1366 # 'postimport' are run after the commit is made and are provided the following
1382 # 'postimport' are run after the commit is made and are provided the following
1367 # argument:
1383 # argument:
1368 # - ctx: the changectx created by import.
1384 # - ctx: the changectx created by import.
1369 extrapostimportmap = {}
1385 extrapostimportmap = {}
1370
1386
1371 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1387 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1372 """Utility function used by commands.import to import a single patch
1388 """Utility function used by commands.import to import a single patch
1373
1389
1374 This function is explicitly defined here to help the evolve extension to
1390 This function is explicitly defined here to help the evolve extension to
1375 wrap this part of the import logic.
1391 wrap this part of the import logic.
1376
1392
1377 The API is currently a bit ugly because it a simple code translation from
1393 The API is currently a bit ugly because it a simple code translation from
1378 the import command. Feel free to make it better.
1394 the import command. Feel free to make it better.
1379
1395
1380 :patchdata: a dictionary containing parsed patch data (such as from
1396 :patchdata: a dictionary containing parsed patch data (such as from
1381 ``patch.extract()``)
1397 ``patch.extract()``)
1382 :parents: nodes that will be parent of the created commit
1398 :parents: nodes that will be parent of the created commit
1383 :opts: the full dict of option passed to the import command
1399 :opts: the full dict of option passed to the import command
1384 :msgs: list to save commit message to.
1400 :msgs: list to save commit message to.
1385 (used in case we need to save it when failing)
1401 (used in case we need to save it when failing)
1386 :updatefunc: a function that update a repo to a given node
1402 :updatefunc: a function that update a repo to a given node
1387 updatefunc(<repo>, <node>)
1403 updatefunc(<repo>, <node>)
1388 """
1404 """
1389 # avoid cycle context -> subrepo -> cmdutil
1405 # avoid cycle context -> subrepo -> cmdutil
1390 from . import context
1406 from . import context
1391
1407
1392 tmpname = patchdata.get('filename')
1408 tmpname = patchdata.get('filename')
1393 message = patchdata.get('message')
1409 message = patchdata.get('message')
1394 user = opts.get('user') or patchdata.get('user')
1410 user = opts.get('user') or patchdata.get('user')
1395 date = opts.get('date') or patchdata.get('date')
1411 date = opts.get('date') or patchdata.get('date')
1396 branch = patchdata.get('branch')
1412 branch = patchdata.get('branch')
1397 nodeid = patchdata.get('nodeid')
1413 nodeid = patchdata.get('nodeid')
1398 p1 = patchdata.get('p1')
1414 p1 = patchdata.get('p1')
1399 p2 = patchdata.get('p2')
1415 p2 = patchdata.get('p2')
1400
1416
1401 nocommit = opts.get('no_commit')
1417 nocommit = opts.get('no_commit')
1402 importbranch = opts.get('import_branch')
1418 importbranch = opts.get('import_branch')
1403 update = not opts.get('bypass')
1419 update = not opts.get('bypass')
1404 strip = opts["strip"]
1420 strip = opts["strip"]
1405 prefix = opts["prefix"]
1421 prefix = opts["prefix"]
1406 sim = float(opts.get('similarity') or 0)
1422 sim = float(opts.get('similarity') or 0)
1407
1423
1408 if not tmpname:
1424 if not tmpname:
1409 return None, None, False
1425 return None, None, False
1410
1426
1411 rejects = False
1427 rejects = False
1412
1428
1413 cmdline_message = logmessage(ui, opts)
1429 cmdline_message = logmessage(ui, opts)
1414 if cmdline_message:
1430 if cmdline_message:
1415 # pickup the cmdline msg
1431 # pickup the cmdline msg
1416 message = cmdline_message
1432 message = cmdline_message
1417 elif message:
1433 elif message:
1418 # pickup the patch msg
1434 # pickup the patch msg
1419 message = message.strip()
1435 message = message.strip()
1420 else:
1436 else:
1421 # launch the editor
1437 # launch the editor
1422 message = None
1438 message = None
1423 ui.debug('message:\n%s\n' % (message or ''))
1439 ui.debug('message:\n%s\n' % (message or ''))
1424
1440
1425 if len(parents) == 1:
1441 if len(parents) == 1:
1426 parents.append(repo[nullid])
1442 parents.append(repo[nullid])
1427 if opts.get('exact'):
1443 if opts.get('exact'):
1428 if not nodeid or not p1:
1444 if not nodeid or not p1:
1429 raise error.Abort(_('not a Mercurial patch'))
1445 raise error.Abort(_('not a Mercurial patch'))
1430 p1 = repo[p1]
1446 p1 = repo[p1]
1431 p2 = repo[p2 or nullid]
1447 p2 = repo[p2 or nullid]
1432 elif p2:
1448 elif p2:
1433 try:
1449 try:
1434 p1 = repo[p1]
1450 p1 = repo[p1]
1435 p2 = repo[p2]
1451 p2 = repo[p2]
1436 # Without any options, consider p2 only if the
1452 # Without any options, consider p2 only if the
1437 # patch is being applied on top of the recorded
1453 # patch is being applied on top of the recorded
1438 # first parent.
1454 # first parent.
1439 if p1 != parents[0]:
1455 if p1 != parents[0]:
1440 p1 = parents[0]
1456 p1 = parents[0]
1441 p2 = repo[nullid]
1457 p2 = repo[nullid]
1442 except error.RepoError:
1458 except error.RepoError:
1443 p1, p2 = parents
1459 p1, p2 = parents
1444 if p2.node() == nullid:
1460 if p2.node() == nullid:
1445 ui.warn(_("warning: import the patch as a normal revision\n"
1461 ui.warn(_("warning: import the patch as a normal revision\n"
1446 "(use --exact to import the patch as a merge)\n"))
1462 "(use --exact to import the patch as a merge)\n"))
1447 else:
1463 else:
1448 p1, p2 = parents
1464 p1, p2 = parents
1449
1465
1450 n = None
1466 n = None
1451 if update:
1467 if update:
1452 if p1 != parents[0]:
1468 if p1 != parents[0]:
1453 updatefunc(repo, p1.node())
1469 updatefunc(repo, p1.node())
1454 if p2 != parents[1]:
1470 if p2 != parents[1]:
1455 repo.setparents(p1.node(), p2.node())
1471 repo.setparents(p1.node(), p2.node())
1456
1472
1457 if opts.get('exact') or importbranch:
1473 if opts.get('exact') or importbranch:
1458 repo.dirstate.setbranch(branch or 'default')
1474 repo.dirstate.setbranch(branch or 'default')
1459
1475
1460 partial = opts.get('partial', False)
1476 partial = opts.get('partial', False)
1461 files = set()
1477 files = set()
1462 try:
1478 try:
1463 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1479 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1464 files=files, eolmode=None, similarity=sim / 100.0)
1480 files=files, eolmode=None, similarity=sim / 100.0)
1465 except error.PatchError as e:
1481 except error.PatchError as e:
1466 if not partial:
1482 if not partial:
1467 raise error.Abort(pycompat.bytestr(e))
1483 raise error.Abort(pycompat.bytestr(e))
1468 if partial:
1484 if partial:
1469 rejects = True
1485 rejects = True
1470
1486
1471 files = list(files)
1487 files = list(files)
1472 if nocommit:
1488 if nocommit:
1473 if message:
1489 if message:
1474 msgs.append(message)
1490 msgs.append(message)
1475 else:
1491 else:
1476 if opts.get('exact') or p2:
1492 if opts.get('exact') or p2:
1477 # If you got here, you either use --force and know what
1493 # If you got here, you either use --force and know what
1478 # you are doing or used --exact or a merge patch while
1494 # you are doing or used --exact or a merge patch while
1479 # being updated to its first parent.
1495 # being updated to its first parent.
1480 m = None
1496 m = None
1481 else:
1497 else:
1482 m = scmutil.matchfiles(repo, files or [])
1498 m = scmutil.matchfiles(repo, files or [])
1483 editform = mergeeditform(repo[None], 'import.normal')
1499 editform = mergeeditform(repo[None], 'import.normal')
1484 if opts.get('exact'):
1500 if opts.get('exact'):
1485 editor = None
1501 editor = None
1486 else:
1502 else:
1487 editor = getcommiteditor(editform=editform,
1503 editor = getcommiteditor(editform=editform,
1488 **pycompat.strkwargs(opts))
1504 **pycompat.strkwargs(opts))
1489 extra = {}
1505 extra = {}
1490 for idfunc in extrapreimport:
1506 for idfunc in extrapreimport:
1491 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1507 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1492 overrides = {}
1508 overrides = {}
1493 if partial:
1509 if partial:
1494 overrides[('ui', 'allowemptycommit')] = True
1510 overrides[('ui', 'allowemptycommit')] = True
1495 with repo.ui.configoverride(overrides, 'import'):
1511 with repo.ui.configoverride(overrides, 'import'):
1496 n = repo.commit(message, user,
1512 n = repo.commit(message, user,
1497 date, match=m,
1513 date, match=m,
1498 editor=editor, extra=extra)
1514 editor=editor, extra=extra)
1499 for idfunc in extrapostimport:
1515 for idfunc in extrapostimport:
1500 extrapostimportmap[idfunc](repo[n])
1516 extrapostimportmap[idfunc](repo[n])
1501 else:
1517 else:
1502 if opts.get('exact') or importbranch:
1518 if opts.get('exact') or importbranch:
1503 branch = branch or 'default'
1519 branch = branch or 'default'
1504 else:
1520 else:
1505 branch = p1.branch()
1521 branch = p1.branch()
1506 store = patch.filestore()
1522 store = patch.filestore()
1507 try:
1523 try:
1508 files = set()
1524 files = set()
1509 try:
1525 try:
1510 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1526 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1511 files, eolmode=None)
1527 files, eolmode=None)
1512 except error.PatchError as e:
1528 except error.PatchError as e:
1513 raise error.Abort(stringutil.forcebytestr(e))
1529 raise error.Abort(stringutil.forcebytestr(e))
1514 if opts.get('exact'):
1530 if opts.get('exact'):
1515 editor = None
1531 editor = None
1516 else:
1532 else:
1517 editor = getcommiteditor(editform='import.bypass')
1533 editor = getcommiteditor(editform='import.bypass')
1518 memctx = context.memctx(repo, (p1.node(), p2.node()),
1534 memctx = context.memctx(repo, (p1.node(), p2.node()),
1519 message,
1535 message,
1520 files=files,
1536 files=files,
1521 filectxfn=store,
1537 filectxfn=store,
1522 user=user,
1538 user=user,
1523 date=date,
1539 date=date,
1524 branch=branch,
1540 branch=branch,
1525 editor=editor)
1541 editor=editor)
1526 n = memctx.commit()
1542 n = memctx.commit()
1527 finally:
1543 finally:
1528 store.close()
1544 store.close()
1529 if opts.get('exact') and nocommit:
1545 if opts.get('exact') and nocommit:
1530 # --exact with --no-commit is still useful in that it does merge
1546 # --exact with --no-commit is still useful in that it does merge
1531 # and branch bits
1547 # and branch bits
1532 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1548 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1533 elif opts.get('exact') and (not n or hex(n) != nodeid):
1549 elif opts.get('exact') and (not n or hex(n) != nodeid):
1534 raise error.Abort(_('patch is damaged or loses information'))
1550 raise error.Abort(_('patch is damaged or loses information'))
1535 msg = _('applied to working directory')
1551 msg = _('applied to working directory')
1536 if n:
1552 if n:
1537 # i18n: refers to a short changeset id
1553 # i18n: refers to a short changeset id
1538 msg = _('created %s') % short(n)
1554 msg = _('created %s') % short(n)
1539 return msg, n, rejects
1555 return msg, n, rejects
1540
1556
1541 # facility to let extensions include additional data in an exported patch
1557 # facility to let extensions include additional data in an exported patch
1542 # list of identifiers to be executed in order
1558 # list of identifiers to be executed in order
1543 extraexport = []
1559 extraexport = []
1544 # mapping from identifier to actual export function
1560 # mapping from identifier to actual export function
1545 # function as to return a string to be added to the header or None
1561 # function as to return a string to be added to the header or None
1546 # it is given two arguments (sequencenumber, changectx)
1562 # it is given two arguments (sequencenumber, changectx)
1547 extraexportmap = {}
1563 extraexportmap = {}
1548
1564
1549 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1565 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1550 node = scmutil.binnode(ctx)
1566 node = scmutil.binnode(ctx)
1551 parents = [p.node() for p in ctx.parents() if p]
1567 parents = [p.node() for p in ctx.parents() if p]
1552 branch = ctx.branch()
1568 branch = ctx.branch()
1553 if switch_parent:
1569 if switch_parent:
1554 parents.reverse()
1570 parents.reverse()
1555
1571
1556 if parents:
1572 if parents:
1557 prev = parents[0]
1573 prev = parents[0]
1558 else:
1574 else:
1559 prev = nullid
1575 prev = nullid
1560
1576
1561 fm.context(ctx=ctx)
1577 fm.context(ctx=ctx)
1562 fm.plain('# HG changeset patch\n')
1578 fm.plain('# HG changeset patch\n')
1563 fm.write('user', '# User %s\n', ctx.user())
1579 fm.write('user', '# User %s\n', ctx.user())
1564 fm.plain('# Date %d %d\n' % ctx.date())
1580 fm.plain('# Date %d %d\n' % ctx.date())
1565 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1581 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1566 fm.condwrite(branch and branch != 'default',
1582 fm.condwrite(branch and branch != 'default',
1567 'branch', '# Branch %s\n', branch)
1583 'branch', '# Branch %s\n', branch)
1568 fm.write('node', '# Node ID %s\n', hex(node))
1584 fm.write('node', '# Node ID %s\n', hex(node))
1569 fm.plain('# Parent %s\n' % hex(prev))
1585 fm.plain('# Parent %s\n' % hex(prev))
1570 if len(parents) > 1:
1586 if len(parents) > 1:
1571 fm.plain('# Parent %s\n' % hex(parents[1]))
1587 fm.plain('# Parent %s\n' % hex(parents[1]))
1572 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1588 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1573
1589
1574 # TODO: redesign extraexportmap function to support formatter
1590 # TODO: redesign extraexportmap function to support formatter
1575 for headerid in extraexport:
1591 for headerid in extraexport:
1576 header = extraexportmap[headerid](seqno, ctx)
1592 header = extraexportmap[headerid](seqno, ctx)
1577 if header is not None:
1593 if header is not None:
1578 fm.plain('# %s\n' % header)
1594 fm.plain('# %s\n' % header)
1579
1595
1580 fm.write('desc', '%s\n', ctx.description().rstrip())
1596 fm.write('desc', '%s\n', ctx.description().rstrip())
1581 fm.plain('\n')
1597 fm.plain('\n')
1582
1598
1583 if fm.isplain():
1599 if fm.isplain():
1584 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1600 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1585 for chunk, label in chunkiter:
1601 for chunk, label in chunkiter:
1586 fm.plain(chunk, label=label)
1602 fm.plain(chunk, label=label)
1587 else:
1603 else:
1588 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1604 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1589 # TODO: make it structured?
1605 # TODO: make it structured?
1590 fm.data(diff=b''.join(chunkiter))
1606 fm.data(diff=b''.join(chunkiter))
1591
1607
1592 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1608 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1593 """Export changesets to stdout or a single file"""
1609 """Export changesets to stdout or a single file"""
1594 for seqno, rev in enumerate(revs, 1):
1610 for seqno, rev in enumerate(revs, 1):
1595 ctx = repo[rev]
1611 ctx = repo[rev]
1596 if not dest.startswith('<'):
1612 if not dest.startswith('<'):
1597 repo.ui.note("%s\n" % dest)
1613 repo.ui.note("%s\n" % dest)
1598 fm.startitem()
1614 fm.startitem()
1599 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1615 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1600
1616
1601 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1617 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1602 match):
1618 match):
1603 """Export changesets to possibly multiple files"""
1619 """Export changesets to possibly multiple files"""
1604 total = len(revs)
1620 total = len(revs)
1605 revwidth = max(len(str(rev)) for rev in revs)
1621 revwidth = max(len(str(rev)) for rev in revs)
1606 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1622 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1607
1623
1608 for seqno, rev in enumerate(revs, 1):
1624 for seqno, rev in enumerate(revs, 1):
1609 ctx = repo[rev]
1625 ctx = repo[rev]
1610 dest = makefilename(ctx, fntemplate,
1626 dest = makefilename(ctx, fntemplate,
1611 total=total, seqno=seqno, revwidth=revwidth)
1627 total=total, seqno=seqno, revwidth=revwidth)
1612 filemap.setdefault(dest, []).append((seqno, rev))
1628 filemap.setdefault(dest, []).append((seqno, rev))
1613
1629
1614 for dest in filemap:
1630 for dest in filemap:
1615 with formatter.maybereopen(basefm, dest) as fm:
1631 with formatter.maybereopen(basefm, dest) as fm:
1616 repo.ui.note("%s\n" % dest)
1632 repo.ui.note("%s\n" % dest)
1617 for seqno, rev in filemap[dest]:
1633 for seqno, rev in filemap[dest]:
1618 fm.startitem()
1634 fm.startitem()
1619 ctx = repo[rev]
1635 ctx = repo[rev]
1620 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1636 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1621 diffopts)
1637 diffopts)
1622
1638
1623 def _prefetchchangedfiles(repo, revs, match):
1639 def _prefetchchangedfiles(repo, revs, match):
1624 allfiles = set()
1640 allfiles = set()
1625 for rev in revs:
1641 for rev in revs:
1626 for file in repo[rev].files():
1642 for file in repo[rev].files():
1627 if not match or match(file):
1643 if not match or match(file):
1628 allfiles.add(file)
1644 allfiles.add(file)
1629 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1645 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1630
1646
1631 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1647 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1632 opts=None, match=None):
1648 opts=None, match=None):
1633 '''export changesets as hg patches
1649 '''export changesets as hg patches
1634
1650
1635 Args:
1651 Args:
1636 repo: The repository from which we're exporting revisions.
1652 repo: The repository from which we're exporting revisions.
1637 revs: A list of revisions to export as revision numbers.
1653 revs: A list of revisions to export as revision numbers.
1638 basefm: A formatter to which patches should be written.
1654 basefm: A formatter to which patches should be written.
1639 fntemplate: An optional string to use for generating patch file names.
1655 fntemplate: An optional string to use for generating patch file names.
1640 switch_parent: If True, show diffs against second parent when not nullid.
1656 switch_parent: If True, show diffs against second parent when not nullid.
1641 Default is false, which always shows diff against p1.
1657 Default is false, which always shows diff against p1.
1642 opts: diff options to use for generating the patch.
1658 opts: diff options to use for generating the patch.
1643 match: If specified, only export changes to files matching this matcher.
1659 match: If specified, only export changes to files matching this matcher.
1644
1660
1645 Returns:
1661 Returns:
1646 Nothing.
1662 Nothing.
1647
1663
1648 Side Effect:
1664 Side Effect:
1649 "HG Changeset Patch" data is emitted to one of the following
1665 "HG Changeset Patch" data is emitted to one of the following
1650 destinations:
1666 destinations:
1651 fntemplate specified: Each rev is written to a unique file named using
1667 fntemplate specified: Each rev is written to a unique file named using
1652 the given template.
1668 the given template.
1653 Otherwise: All revs will be written to basefm.
1669 Otherwise: All revs will be written to basefm.
1654 '''
1670 '''
1655 _prefetchchangedfiles(repo, revs, match)
1671 _prefetchchangedfiles(repo, revs, match)
1656
1672
1657 if not fntemplate:
1673 if not fntemplate:
1658 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1674 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1659 else:
1675 else:
1660 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1676 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1661 match)
1677 match)
1662
1678
1663 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1679 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1664 """Export changesets to the given file stream"""
1680 """Export changesets to the given file stream"""
1665 _prefetchchangedfiles(repo, revs, match)
1681 _prefetchchangedfiles(repo, revs, match)
1666
1682
1667 dest = getattr(fp, 'name', '<unnamed>')
1683 dest = getattr(fp, 'name', '<unnamed>')
1668 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1684 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1669 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1685 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1670
1686
1671 def showmarker(fm, marker, index=None):
1687 def showmarker(fm, marker, index=None):
1672 """utility function to display obsolescence marker in a readable way
1688 """utility function to display obsolescence marker in a readable way
1673
1689
1674 To be used by debug function."""
1690 To be used by debug function."""
1675 if index is not None:
1691 if index is not None:
1676 fm.write('index', '%i ', index)
1692 fm.write('index', '%i ', index)
1677 fm.write('prednode', '%s ', hex(marker.prednode()))
1693 fm.write('prednode', '%s ', hex(marker.prednode()))
1678 succs = marker.succnodes()
1694 succs = marker.succnodes()
1679 fm.condwrite(succs, 'succnodes', '%s ',
1695 fm.condwrite(succs, 'succnodes', '%s ',
1680 fm.formatlist(map(hex, succs), name='node'))
1696 fm.formatlist(map(hex, succs), name='node'))
1681 fm.write('flag', '%X ', marker.flags())
1697 fm.write('flag', '%X ', marker.flags())
1682 parents = marker.parentnodes()
1698 parents = marker.parentnodes()
1683 if parents is not None:
1699 if parents is not None:
1684 fm.write('parentnodes', '{%s} ',
1700 fm.write('parentnodes', '{%s} ',
1685 fm.formatlist(map(hex, parents), name='node', sep=', '))
1701 fm.formatlist(map(hex, parents), name='node', sep=', '))
1686 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1702 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1687 meta = marker.metadata().copy()
1703 meta = marker.metadata().copy()
1688 meta.pop('date', None)
1704 meta.pop('date', None)
1689 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1705 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1690 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1706 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1691 fm.plain('\n')
1707 fm.plain('\n')
1692
1708
1693 def finddate(ui, repo, date):
1709 def finddate(ui, repo, date):
1694 """Find the tipmost changeset that matches the given date spec"""
1710 """Find the tipmost changeset that matches the given date spec"""
1695
1711
1696 df = dateutil.matchdate(date)
1712 df = dateutil.matchdate(date)
1697 m = scmutil.matchall(repo)
1713 m = scmutil.matchall(repo)
1698 results = {}
1714 results = {}
1699
1715
1700 def prep(ctx, fns):
1716 def prep(ctx, fns):
1701 d = ctx.date()
1717 d = ctx.date()
1702 if df(d[0]):
1718 if df(d[0]):
1703 results[ctx.rev()] = d
1719 results[ctx.rev()] = d
1704
1720
1705 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1721 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1706 rev = ctx.rev()
1722 rev = ctx.rev()
1707 if rev in results:
1723 if rev in results:
1708 ui.status(_("found revision %s from %s\n") %
1724 ui.status(_("found revision %s from %s\n") %
1709 (rev, dateutil.datestr(results[rev])))
1725 (rev, dateutil.datestr(results[rev])))
1710 return '%d' % rev
1726 return '%d' % rev
1711
1727
1712 raise error.Abort(_("revision matching date not found"))
1728 raise error.Abort(_("revision matching date not found"))
1713
1729
1714 def increasingwindows(windowsize=8, sizelimit=512):
1730 def increasingwindows(windowsize=8, sizelimit=512):
1715 while True:
1731 while True:
1716 yield windowsize
1732 yield windowsize
1717 if windowsize < sizelimit:
1733 if windowsize < sizelimit:
1718 windowsize *= 2
1734 windowsize *= 2
1719
1735
1720 def _walkrevs(repo, opts):
1736 def _walkrevs(repo, opts):
1721 # Default --rev value depends on --follow but --follow behavior
1737 # Default --rev value depends on --follow but --follow behavior
1722 # depends on revisions resolved from --rev...
1738 # depends on revisions resolved from --rev...
1723 follow = opts.get('follow') or opts.get('follow_first')
1739 follow = opts.get('follow') or opts.get('follow_first')
1724 if opts.get('rev'):
1740 if opts.get('rev'):
1725 revs = scmutil.revrange(repo, opts['rev'])
1741 revs = scmutil.revrange(repo, opts['rev'])
1726 elif follow and repo.dirstate.p1() == nullid:
1742 elif follow and repo.dirstate.p1() == nullid:
1727 revs = smartset.baseset()
1743 revs = smartset.baseset()
1728 elif follow:
1744 elif follow:
1729 revs = repo.revs('reverse(:.)')
1745 revs = repo.revs('reverse(:.)')
1730 else:
1746 else:
1731 revs = smartset.spanset(repo)
1747 revs = smartset.spanset(repo)
1732 revs.reverse()
1748 revs.reverse()
1733 return revs
1749 return revs
1734
1750
1735 class FileWalkError(Exception):
1751 class FileWalkError(Exception):
1736 pass
1752 pass
1737
1753
1738 def walkfilerevs(repo, match, follow, revs, fncache):
1754 def walkfilerevs(repo, match, follow, revs, fncache):
1739 '''Walks the file history for the matched files.
1755 '''Walks the file history for the matched files.
1740
1756
1741 Returns the changeset revs that are involved in the file history.
1757 Returns the changeset revs that are involved in the file history.
1742
1758
1743 Throws FileWalkError if the file history can't be walked using
1759 Throws FileWalkError if the file history can't be walked using
1744 filelogs alone.
1760 filelogs alone.
1745 '''
1761 '''
1746 wanted = set()
1762 wanted = set()
1747 copies = []
1763 copies = []
1748 minrev, maxrev = min(revs), max(revs)
1764 minrev, maxrev = min(revs), max(revs)
1749 def filerevs(filelog, last):
1765 def filerevs(filelog, last):
1750 """
1766 """
1751 Only files, no patterns. Check the history of each file.
1767 Only files, no patterns. Check the history of each file.
1752
1768
1753 Examines filelog entries within minrev, maxrev linkrev range
1769 Examines filelog entries within minrev, maxrev linkrev range
1754 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1770 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1755 tuples in backwards order
1771 tuples in backwards order
1756 """
1772 """
1757 cl_count = len(repo)
1773 cl_count = len(repo)
1758 revs = []
1774 revs = []
1759 for j in pycompat.xrange(0, last + 1):
1775 for j in pycompat.xrange(0, last + 1):
1760 linkrev = filelog.linkrev(j)
1776 linkrev = filelog.linkrev(j)
1761 if linkrev < minrev:
1777 if linkrev < minrev:
1762 continue
1778 continue
1763 # only yield rev for which we have the changelog, it can
1779 # only yield rev for which we have the changelog, it can
1764 # happen while doing "hg log" during a pull or commit
1780 # happen while doing "hg log" during a pull or commit
1765 if linkrev >= cl_count:
1781 if linkrev >= cl_count:
1766 break
1782 break
1767
1783
1768 parentlinkrevs = []
1784 parentlinkrevs = []
1769 for p in filelog.parentrevs(j):
1785 for p in filelog.parentrevs(j):
1770 if p != nullrev:
1786 if p != nullrev:
1771 parentlinkrevs.append(filelog.linkrev(p))
1787 parentlinkrevs.append(filelog.linkrev(p))
1772 n = filelog.node(j)
1788 n = filelog.node(j)
1773 revs.append((linkrev, parentlinkrevs,
1789 revs.append((linkrev, parentlinkrevs,
1774 follow and filelog.renamed(n)))
1790 follow and filelog.renamed(n)))
1775
1791
1776 return reversed(revs)
1792 return reversed(revs)
1777 def iterfiles():
1793 def iterfiles():
1778 pctx = repo['.']
1794 pctx = repo['.']
1779 for filename in match.files():
1795 for filename in match.files():
1780 if follow:
1796 if follow:
1781 if filename not in pctx:
1797 if filename not in pctx:
1782 raise error.Abort(_('cannot follow file not in parent '
1798 raise error.Abort(_('cannot follow file not in parent '
1783 'revision: "%s"') % filename)
1799 'revision: "%s"') % filename)
1784 yield filename, pctx[filename].filenode()
1800 yield filename, pctx[filename].filenode()
1785 else:
1801 else:
1786 yield filename, None
1802 yield filename, None
1787 for filename_node in copies:
1803 for filename_node in copies:
1788 yield filename_node
1804 yield filename_node
1789
1805
1790 for file_, node in iterfiles():
1806 for file_, node in iterfiles():
1791 filelog = repo.file(file_)
1807 filelog = repo.file(file_)
1792 if not len(filelog):
1808 if not len(filelog):
1793 if node is None:
1809 if node is None:
1794 # A zero count may be a directory or deleted file, so
1810 # A zero count may be a directory or deleted file, so
1795 # try to find matching entries on the slow path.
1811 # try to find matching entries on the slow path.
1796 if follow:
1812 if follow:
1797 raise error.Abort(
1813 raise error.Abort(
1798 _('cannot follow nonexistent file: "%s"') % file_)
1814 _('cannot follow nonexistent file: "%s"') % file_)
1799 raise FileWalkError("Cannot walk via filelog")
1815 raise FileWalkError("Cannot walk via filelog")
1800 else:
1816 else:
1801 continue
1817 continue
1802
1818
1803 if node is None:
1819 if node is None:
1804 last = len(filelog) - 1
1820 last = len(filelog) - 1
1805 else:
1821 else:
1806 last = filelog.rev(node)
1822 last = filelog.rev(node)
1807
1823
1808 # keep track of all ancestors of the file
1824 # keep track of all ancestors of the file
1809 ancestors = {filelog.linkrev(last)}
1825 ancestors = {filelog.linkrev(last)}
1810
1826
1811 # iterate from latest to oldest revision
1827 # iterate from latest to oldest revision
1812 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
1828 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
1813 if not follow:
1829 if not follow:
1814 if rev > maxrev:
1830 if rev > maxrev:
1815 continue
1831 continue
1816 else:
1832 else:
1817 # Note that last might not be the first interesting
1833 # Note that last might not be the first interesting
1818 # rev to us:
1834 # rev to us:
1819 # if the file has been changed after maxrev, we'll
1835 # if the file has been changed after maxrev, we'll
1820 # have linkrev(last) > maxrev, and we still need
1836 # have linkrev(last) > maxrev, and we still need
1821 # to explore the file graph
1837 # to explore the file graph
1822 if rev not in ancestors:
1838 if rev not in ancestors:
1823 continue
1839 continue
1824 # XXX insert 1327 fix here
1840 # XXX insert 1327 fix here
1825 if flparentlinkrevs:
1841 if flparentlinkrevs:
1826 ancestors.update(flparentlinkrevs)
1842 ancestors.update(flparentlinkrevs)
1827
1843
1828 fncache.setdefault(rev, []).append(file_)
1844 fncache.setdefault(rev, []).append(file_)
1829 wanted.add(rev)
1845 wanted.add(rev)
1830 if copied:
1846 if copied:
1831 copies.append(copied)
1847 copies.append(copied)
1832
1848
1833 return wanted
1849 return wanted
1834
1850
1835 class _followfilter(object):
1851 class _followfilter(object):
1836 def __init__(self, repo, onlyfirst=False):
1852 def __init__(self, repo, onlyfirst=False):
1837 self.repo = repo
1853 self.repo = repo
1838 self.startrev = nullrev
1854 self.startrev = nullrev
1839 self.roots = set()
1855 self.roots = set()
1840 self.onlyfirst = onlyfirst
1856 self.onlyfirst = onlyfirst
1841
1857
1842 def match(self, rev):
1858 def match(self, rev):
1843 def realparents(rev):
1859 def realparents(rev):
1844 if self.onlyfirst:
1860 if self.onlyfirst:
1845 return self.repo.changelog.parentrevs(rev)[0:1]
1861 return self.repo.changelog.parentrevs(rev)[0:1]
1846 else:
1862 else:
1847 return filter(lambda x: x != nullrev,
1863 return filter(lambda x: x != nullrev,
1848 self.repo.changelog.parentrevs(rev))
1864 self.repo.changelog.parentrevs(rev))
1849
1865
1850 if self.startrev == nullrev:
1866 if self.startrev == nullrev:
1851 self.startrev = rev
1867 self.startrev = rev
1852 return True
1868 return True
1853
1869
1854 if rev > self.startrev:
1870 if rev > self.startrev:
1855 # forward: all descendants
1871 # forward: all descendants
1856 if not self.roots:
1872 if not self.roots:
1857 self.roots.add(self.startrev)
1873 self.roots.add(self.startrev)
1858 for parent in realparents(rev):
1874 for parent in realparents(rev):
1859 if parent in self.roots:
1875 if parent in self.roots:
1860 self.roots.add(rev)
1876 self.roots.add(rev)
1861 return True
1877 return True
1862 else:
1878 else:
1863 # backwards: all parents
1879 # backwards: all parents
1864 if not self.roots:
1880 if not self.roots:
1865 self.roots.update(realparents(self.startrev))
1881 self.roots.update(realparents(self.startrev))
1866 if rev in self.roots:
1882 if rev in self.roots:
1867 self.roots.remove(rev)
1883 self.roots.remove(rev)
1868 self.roots.update(realparents(rev))
1884 self.roots.update(realparents(rev))
1869 return True
1885 return True
1870
1886
1871 return False
1887 return False
1872
1888
1873 def walkchangerevs(repo, match, opts, prepare):
1889 def walkchangerevs(repo, match, opts, prepare):
1874 '''Iterate over files and the revs in which they changed.
1890 '''Iterate over files and the revs in which they changed.
1875
1891
1876 Callers most commonly need to iterate backwards over the history
1892 Callers most commonly need to iterate backwards over the history
1877 in which they are interested. Doing so has awful (quadratic-looking)
1893 in which they are interested. Doing so has awful (quadratic-looking)
1878 performance, so we use iterators in a "windowed" way.
1894 performance, so we use iterators in a "windowed" way.
1879
1895
1880 We walk a window of revisions in the desired order. Within the
1896 We walk a window of revisions in the desired order. Within the
1881 window, we first walk forwards to gather data, then in the desired
1897 window, we first walk forwards to gather data, then in the desired
1882 order (usually backwards) to display it.
1898 order (usually backwards) to display it.
1883
1899
1884 This function returns an iterator yielding contexts. Before
1900 This function returns an iterator yielding contexts. Before
1885 yielding each context, the iterator will first call the prepare
1901 yielding each context, the iterator will first call the prepare
1886 function on each context in the window in forward order.'''
1902 function on each context in the window in forward order.'''
1887
1903
1888 allfiles = opts.get('all_files')
1904 allfiles = opts.get('all_files')
1889 follow = opts.get('follow') or opts.get('follow_first')
1905 follow = opts.get('follow') or opts.get('follow_first')
1890 revs = _walkrevs(repo, opts)
1906 revs = _walkrevs(repo, opts)
1891 if not revs:
1907 if not revs:
1892 return []
1908 return []
1893 wanted = set()
1909 wanted = set()
1894 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1910 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1895 fncache = {}
1911 fncache = {}
1896 change = repo.__getitem__
1912 change = repo.__getitem__
1897
1913
1898 # First step is to fill wanted, the set of revisions that we want to yield.
1914 # First step is to fill wanted, the set of revisions that we want to yield.
1899 # When it does not induce extra cost, we also fill fncache for revisions in
1915 # When it does not induce extra cost, we also fill fncache for revisions in
1900 # wanted: a cache of filenames that were changed (ctx.files()) and that
1916 # wanted: a cache of filenames that were changed (ctx.files()) and that
1901 # match the file filtering conditions.
1917 # match the file filtering conditions.
1902
1918
1903 if match.always() or allfiles:
1919 if match.always() or allfiles:
1904 # No files, no patterns. Display all revs.
1920 # No files, no patterns. Display all revs.
1905 wanted = revs
1921 wanted = revs
1906 elif not slowpath:
1922 elif not slowpath:
1907 # We only have to read through the filelog to find wanted revisions
1923 # We only have to read through the filelog to find wanted revisions
1908
1924
1909 try:
1925 try:
1910 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1926 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1911 except FileWalkError:
1927 except FileWalkError:
1912 slowpath = True
1928 slowpath = True
1913
1929
1914 # We decided to fall back to the slowpath because at least one
1930 # We decided to fall back to the slowpath because at least one
1915 # of the paths was not a file. Check to see if at least one of them
1931 # of the paths was not a file. Check to see if at least one of them
1916 # existed in history, otherwise simply return
1932 # existed in history, otherwise simply return
1917 for path in match.files():
1933 for path in match.files():
1918 if path == '.' or path in repo.store:
1934 if path == '.' or path in repo.store:
1919 break
1935 break
1920 else:
1936 else:
1921 return []
1937 return []
1922
1938
1923 if slowpath:
1939 if slowpath:
1924 # We have to read the changelog to match filenames against
1940 # We have to read the changelog to match filenames against
1925 # changed files
1941 # changed files
1926
1942
1927 if follow:
1943 if follow:
1928 raise error.Abort(_('can only follow copies/renames for explicit '
1944 raise error.Abort(_('can only follow copies/renames for explicit '
1929 'filenames'))
1945 'filenames'))
1930
1946
1931 # The slow path checks files modified in every changeset.
1947 # The slow path checks files modified in every changeset.
1932 # This is really slow on large repos, so compute the set lazily.
1948 # This is really slow on large repos, so compute the set lazily.
1933 class lazywantedset(object):
1949 class lazywantedset(object):
1934 def __init__(self):
1950 def __init__(self):
1935 self.set = set()
1951 self.set = set()
1936 self.revs = set(revs)
1952 self.revs = set(revs)
1937
1953
1938 # No need to worry about locality here because it will be accessed
1954 # No need to worry about locality here because it will be accessed
1939 # in the same order as the increasing window below.
1955 # in the same order as the increasing window below.
1940 def __contains__(self, value):
1956 def __contains__(self, value):
1941 if value in self.set:
1957 if value in self.set:
1942 return True
1958 return True
1943 elif not value in self.revs:
1959 elif not value in self.revs:
1944 return False
1960 return False
1945 else:
1961 else:
1946 self.revs.discard(value)
1962 self.revs.discard(value)
1947 ctx = change(value)
1963 ctx = change(value)
1948 if allfiles:
1964 if allfiles:
1949 matches = list(ctx.manifest().walk(match))
1965 matches = list(ctx.manifest().walk(match))
1950 else:
1966 else:
1951 matches = [f for f in ctx.files() if match(f)]
1967 matches = [f for f in ctx.files() if match(f)]
1952 if matches:
1968 if matches:
1953 fncache[value] = matches
1969 fncache[value] = matches
1954 self.set.add(value)
1970 self.set.add(value)
1955 return True
1971 return True
1956 return False
1972 return False
1957
1973
1958 def discard(self, value):
1974 def discard(self, value):
1959 self.revs.discard(value)
1975 self.revs.discard(value)
1960 self.set.discard(value)
1976 self.set.discard(value)
1961
1977
1962 wanted = lazywantedset()
1978 wanted = lazywantedset()
1963
1979
1964 # it might be worthwhile to do this in the iterator if the rev range
1980 # it might be worthwhile to do this in the iterator if the rev range
1965 # is descending and the prune args are all within that range
1981 # is descending and the prune args are all within that range
1966 for rev in opts.get('prune', ()):
1982 for rev in opts.get('prune', ()):
1967 rev = repo[rev].rev()
1983 rev = repo[rev].rev()
1968 ff = _followfilter(repo)
1984 ff = _followfilter(repo)
1969 stop = min(revs[0], revs[-1])
1985 stop = min(revs[0], revs[-1])
1970 for x in pycompat.xrange(rev, stop - 1, -1):
1986 for x in pycompat.xrange(rev, stop - 1, -1):
1971 if ff.match(x):
1987 if ff.match(x):
1972 wanted = wanted - [x]
1988 wanted = wanted - [x]
1973
1989
1974 # Now that wanted is correctly initialized, we can iterate over the
1990 # Now that wanted is correctly initialized, we can iterate over the
1975 # revision range, yielding only revisions in wanted.
1991 # revision range, yielding only revisions in wanted.
1976 def iterate():
1992 def iterate():
1977 if follow and match.always():
1993 if follow and match.always():
1978 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1994 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1979 def want(rev):
1995 def want(rev):
1980 return ff.match(rev) and rev in wanted
1996 return ff.match(rev) and rev in wanted
1981 else:
1997 else:
1982 def want(rev):
1998 def want(rev):
1983 return rev in wanted
1999 return rev in wanted
1984
2000
1985 it = iter(revs)
2001 it = iter(revs)
1986 stopiteration = False
2002 stopiteration = False
1987 for windowsize in increasingwindows():
2003 for windowsize in increasingwindows():
1988 nrevs = []
2004 nrevs = []
1989 for i in pycompat.xrange(windowsize):
2005 for i in pycompat.xrange(windowsize):
1990 rev = next(it, None)
2006 rev = next(it, None)
1991 if rev is None:
2007 if rev is None:
1992 stopiteration = True
2008 stopiteration = True
1993 break
2009 break
1994 elif want(rev):
2010 elif want(rev):
1995 nrevs.append(rev)
2011 nrevs.append(rev)
1996 for rev in sorted(nrevs):
2012 for rev in sorted(nrevs):
1997 fns = fncache.get(rev)
2013 fns = fncache.get(rev)
1998 ctx = change(rev)
2014 ctx = change(rev)
1999 if not fns:
2015 if not fns:
2000 def fns_generator():
2016 def fns_generator():
2001 if allfiles:
2017 if allfiles:
2002 fiter = iter(ctx)
2018 fiter = iter(ctx)
2003 else:
2019 else:
2004 fiter = ctx.files()
2020 fiter = ctx.files()
2005 for f in fiter:
2021 for f in fiter:
2006 if match(f):
2022 if match(f):
2007 yield f
2023 yield f
2008 fns = fns_generator()
2024 fns = fns_generator()
2009 prepare(ctx, fns)
2025 prepare(ctx, fns)
2010 for rev in nrevs:
2026 for rev in nrevs:
2011 yield change(rev)
2027 yield change(rev)
2012
2028
2013 if stopiteration:
2029 if stopiteration:
2014 break
2030 break
2015
2031
2016 return iterate()
2032 return iterate()
2017
2033
2018 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2034 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2019 bad = []
2035 bad = []
2020
2036
2021 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2037 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2022 names = []
2038 names = []
2023 wctx = repo[None]
2039 wctx = repo[None]
2024 cca = None
2040 cca = None
2025 abort, warn = scmutil.checkportabilityalert(ui)
2041 abort, warn = scmutil.checkportabilityalert(ui)
2026 if abort or warn:
2042 if abort or warn:
2027 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2043 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2028
2044
2029 match = repo.narrowmatch(match, includeexact=True)
2045 match = repo.narrowmatch(match, includeexact=True)
2030 badmatch = matchmod.badmatch(match, badfn)
2046 badmatch = matchmod.badmatch(match, badfn)
2031 dirstate = repo.dirstate
2047 dirstate = repo.dirstate
2032 # We don't want to just call wctx.walk here, since it would return a lot of
2048 # We don't want to just call wctx.walk here, since it would return a lot of
2033 # clean files, which we aren't interested in and takes time.
2049 # clean files, which we aren't interested in and takes time.
2034 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2050 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2035 unknown=True, ignored=False, full=False)):
2051 unknown=True, ignored=False, full=False)):
2036 exact = match.exact(f)
2052 exact = match.exact(f)
2037 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2053 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2038 if cca:
2054 if cca:
2039 cca(f)
2055 cca(f)
2040 names.append(f)
2056 names.append(f)
2041 if ui.verbose or not exact:
2057 if ui.verbose or not exact:
2042 ui.status(_('adding %s\n') % uipathfn(f),
2058 ui.status(_('adding %s\n') % uipathfn(f),
2043 label='ui.addremove.added')
2059 label='ui.addremove.added')
2044
2060
2045 for subpath in sorted(wctx.substate):
2061 for subpath in sorted(wctx.substate):
2046 sub = wctx.sub(subpath)
2062 sub = wctx.sub(subpath)
2047 try:
2063 try:
2048 submatch = matchmod.subdirmatcher(subpath, match)
2064 submatch = matchmod.subdirmatcher(subpath, match)
2049 subprefix = repo.wvfs.reljoin(prefix, subpath)
2065 subprefix = repo.wvfs.reljoin(prefix, subpath)
2050 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2066 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2051 if opts.get(r'subrepos'):
2067 if opts.get(r'subrepos'):
2052 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2068 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2053 **opts))
2069 **opts))
2054 else:
2070 else:
2055 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2071 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2056 **opts))
2072 **opts))
2057 except error.LookupError:
2073 except error.LookupError:
2058 ui.status(_("skipping missing subrepository: %s\n")
2074 ui.status(_("skipping missing subrepository: %s\n")
2059 % uipathfn(subpath))
2075 % uipathfn(subpath))
2060
2076
2061 if not opts.get(r'dry_run'):
2077 if not opts.get(r'dry_run'):
2062 rejected = wctx.add(names, prefix)
2078 rejected = wctx.add(names, prefix)
2063 bad.extend(f for f in rejected if f in match.files())
2079 bad.extend(f for f in rejected if f in match.files())
2064 return bad
2080 return bad
2065
2081
2066 def addwebdirpath(repo, serverpath, webconf):
2082 def addwebdirpath(repo, serverpath, webconf):
2067 webconf[serverpath] = repo.root
2083 webconf[serverpath] = repo.root
2068 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2084 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2069
2085
2070 for r in repo.revs('filelog("path:.hgsub")'):
2086 for r in repo.revs('filelog("path:.hgsub")'):
2071 ctx = repo[r]
2087 ctx = repo[r]
2072 for subpath in ctx.substate:
2088 for subpath in ctx.substate:
2073 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2089 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2074
2090
2075 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2091 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2076 interactive):
2092 interactive):
2077 if dryrun and interactive:
2093 if dryrun and interactive:
2078 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2094 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2079 bad = []
2095 bad = []
2080 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2096 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2081 wctx = repo[None]
2097 wctx = repo[None]
2082 forgot = []
2098 forgot = []
2083
2099
2084 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2100 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2085 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2101 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2086 if explicitonly:
2102 if explicitonly:
2087 forget = [f for f in forget if match.exact(f)]
2103 forget = [f for f in forget if match.exact(f)]
2088
2104
2089 for subpath in sorted(wctx.substate):
2105 for subpath in sorted(wctx.substate):
2090 sub = wctx.sub(subpath)
2106 sub = wctx.sub(subpath)
2091 submatch = matchmod.subdirmatcher(subpath, match)
2107 submatch = matchmod.subdirmatcher(subpath, match)
2092 subprefix = repo.wvfs.reljoin(prefix, subpath)
2108 subprefix = repo.wvfs.reljoin(prefix, subpath)
2093 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2109 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2094 try:
2110 try:
2095 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2111 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2096 dryrun=dryrun,
2112 dryrun=dryrun,
2097 interactive=interactive)
2113 interactive=interactive)
2098 bad.extend([subpath + '/' + f for f in subbad])
2114 bad.extend([subpath + '/' + f for f in subbad])
2099 forgot.extend([subpath + '/' + f for f in subforgot])
2115 forgot.extend([subpath + '/' + f for f in subforgot])
2100 except error.LookupError:
2116 except error.LookupError:
2101 ui.status(_("skipping missing subrepository: %s\n")
2117 ui.status(_("skipping missing subrepository: %s\n")
2102 % uipathfn(subpath))
2118 % uipathfn(subpath))
2103
2119
2104 if not explicitonly:
2120 if not explicitonly:
2105 for f in match.files():
2121 for f in match.files():
2106 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2122 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2107 if f not in forgot:
2123 if f not in forgot:
2108 if repo.wvfs.exists(f):
2124 if repo.wvfs.exists(f):
2109 # Don't complain if the exact case match wasn't given.
2125 # Don't complain if the exact case match wasn't given.
2110 # But don't do this until after checking 'forgot', so
2126 # But don't do this until after checking 'forgot', so
2111 # that subrepo files aren't normalized, and this op is
2127 # that subrepo files aren't normalized, and this op is
2112 # purely from data cached by the status walk above.
2128 # purely from data cached by the status walk above.
2113 if repo.dirstate.normalize(f) in repo.dirstate:
2129 if repo.dirstate.normalize(f) in repo.dirstate:
2114 continue
2130 continue
2115 ui.warn(_('not removing %s: '
2131 ui.warn(_('not removing %s: '
2116 'file is already untracked\n')
2132 'file is already untracked\n')
2117 % uipathfn(f))
2133 % uipathfn(f))
2118 bad.append(f)
2134 bad.append(f)
2119
2135
2120 if interactive:
2136 if interactive:
2121 responses = _('[Ynsa?]'
2137 responses = _('[Ynsa?]'
2122 '$$ &Yes, forget this file'
2138 '$$ &Yes, forget this file'
2123 '$$ &No, skip this file'
2139 '$$ &No, skip this file'
2124 '$$ &Skip remaining files'
2140 '$$ &Skip remaining files'
2125 '$$ Include &all remaining files'
2141 '$$ Include &all remaining files'
2126 '$$ &? (display help)')
2142 '$$ &? (display help)')
2127 for filename in forget[:]:
2143 for filename in forget[:]:
2128 r = ui.promptchoice(_('forget %s %s') %
2144 r = ui.promptchoice(_('forget %s %s') %
2129 (uipathfn(filename), responses))
2145 (uipathfn(filename), responses))
2130 if r == 4: # ?
2146 if r == 4: # ?
2131 while r == 4:
2147 while r == 4:
2132 for c, t in ui.extractchoices(responses)[1]:
2148 for c, t in ui.extractchoices(responses)[1]:
2133 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2149 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2134 r = ui.promptchoice(_('forget %s %s') %
2150 r = ui.promptchoice(_('forget %s %s') %
2135 (uipathfn(filename), responses))
2151 (uipathfn(filename), responses))
2136 if r == 0: # yes
2152 if r == 0: # yes
2137 continue
2153 continue
2138 elif r == 1: # no
2154 elif r == 1: # no
2139 forget.remove(filename)
2155 forget.remove(filename)
2140 elif r == 2: # Skip
2156 elif r == 2: # Skip
2141 fnindex = forget.index(filename)
2157 fnindex = forget.index(filename)
2142 del forget[fnindex:]
2158 del forget[fnindex:]
2143 break
2159 break
2144 elif r == 3: # All
2160 elif r == 3: # All
2145 break
2161 break
2146
2162
2147 for f in forget:
2163 for f in forget:
2148 if ui.verbose or not match.exact(f) or interactive:
2164 if ui.verbose or not match.exact(f) or interactive:
2149 ui.status(_('removing %s\n') % uipathfn(f),
2165 ui.status(_('removing %s\n') % uipathfn(f),
2150 label='ui.addremove.removed')
2166 label='ui.addremove.removed')
2151
2167
2152 if not dryrun:
2168 if not dryrun:
2153 rejected = wctx.forget(forget, prefix)
2169 rejected = wctx.forget(forget, prefix)
2154 bad.extend(f for f in rejected if f in match.files())
2170 bad.extend(f for f in rejected if f in match.files())
2155 forgot.extend(f for f in forget if f not in rejected)
2171 forgot.extend(f for f in forget if f not in rejected)
2156 return bad, forgot
2172 return bad, forgot
2157
2173
2158 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2174 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2159 ret = 1
2175 ret = 1
2160
2176
2161 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2177 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2162 for f in ctx.matches(m):
2178 for f in ctx.matches(m):
2163 fm.startitem()
2179 fm.startitem()
2164 fm.context(ctx=ctx)
2180 fm.context(ctx=ctx)
2165 if needsfctx:
2181 if needsfctx:
2166 fc = ctx[f]
2182 fc = ctx[f]
2167 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2183 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2168 fm.data(path=f)
2184 fm.data(path=f)
2169 fm.plain(fmt % uipathfn(f))
2185 fm.plain(fmt % uipathfn(f))
2170 ret = 0
2186 ret = 0
2171
2187
2172 for subpath in sorted(ctx.substate):
2188 for subpath in sorted(ctx.substate):
2173 submatch = matchmod.subdirmatcher(subpath, m)
2189 submatch = matchmod.subdirmatcher(subpath, m)
2174 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2190 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2175 if (subrepos or m.exact(subpath) or any(submatch.files())):
2191 if (subrepos or m.exact(subpath) or any(submatch.files())):
2176 sub = ctx.sub(subpath)
2192 sub = ctx.sub(subpath)
2177 try:
2193 try:
2178 recurse = m.exact(subpath) or subrepos
2194 recurse = m.exact(subpath) or subrepos
2179 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2195 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2180 recurse) == 0:
2196 recurse) == 0:
2181 ret = 0
2197 ret = 0
2182 except error.LookupError:
2198 except error.LookupError:
2183 ui.status(_("skipping missing subrepository: %s\n")
2199 ui.status(_("skipping missing subrepository: %s\n")
2184 % uipathfn(subpath))
2200 % uipathfn(subpath))
2185
2201
2186 return ret
2202 return ret
2187
2203
2188 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2204 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2189 warnings=None):
2205 warnings=None):
2190 ret = 0
2206 ret = 0
2191 s = repo.status(match=m, clean=True)
2207 s = repo.status(match=m, clean=True)
2192 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2208 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2193
2209
2194 wctx = repo[None]
2210 wctx = repo[None]
2195
2211
2196 if warnings is None:
2212 if warnings is None:
2197 warnings = []
2213 warnings = []
2198 warn = True
2214 warn = True
2199 else:
2215 else:
2200 warn = False
2216 warn = False
2201
2217
2202 subs = sorted(wctx.substate)
2218 subs = sorted(wctx.substate)
2203 progress = ui.makeprogress(_('searching'), total=len(subs),
2219 progress = ui.makeprogress(_('searching'), total=len(subs),
2204 unit=_('subrepos'))
2220 unit=_('subrepos'))
2205 for subpath in subs:
2221 for subpath in subs:
2206 submatch = matchmod.subdirmatcher(subpath, m)
2222 submatch = matchmod.subdirmatcher(subpath, m)
2207 subprefix = repo.wvfs.reljoin(prefix, subpath)
2223 subprefix = repo.wvfs.reljoin(prefix, subpath)
2208 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2224 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2209 if subrepos or m.exact(subpath) or any(submatch.files()):
2225 if subrepos or m.exact(subpath) or any(submatch.files()):
2210 progress.increment()
2226 progress.increment()
2211 sub = wctx.sub(subpath)
2227 sub = wctx.sub(subpath)
2212 try:
2228 try:
2213 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2229 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2214 force, subrepos, dryrun, warnings):
2230 force, subrepos, dryrun, warnings):
2215 ret = 1
2231 ret = 1
2216 except error.LookupError:
2232 except error.LookupError:
2217 warnings.append(_("skipping missing subrepository: %s\n")
2233 warnings.append(_("skipping missing subrepository: %s\n")
2218 % uipathfn(subpath))
2234 % uipathfn(subpath))
2219 progress.complete()
2235 progress.complete()
2220
2236
2221 # warn about failure to delete explicit files/dirs
2237 # warn about failure to delete explicit files/dirs
2222 deleteddirs = util.dirs(deleted)
2238 deleteddirs = util.dirs(deleted)
2223 files = m.files()
2239 files = m.files()
2224 progress = ui.makeprogress(_('deleting'), total=len(files),
2240 progress = ui.makeprogress(_('deleting'), total=len(files),
2225 unit=_('files'))
2241 unit=_('files'))
2226 for f in files:
2242 for f in files:
2227 def insubrepo():
2243 def insubrepo():
2228 for subpath in wctx.substate:
2244 for subpath in wctx.substate:
2229 if f.startswith(subpath + '/'):
2245 if f.startswith(subpath + '/'):
2230 return True
2246 return True
2231 return False
2247 return False
2232
2248
2233 progress.increment()
2249 progress.increment()
2234 isdir = f in deleteddirs or wctx.hasdir(f)
2250 isdir = f in deleteddirs or wctx.hasdir(f)
2235 if (f in repo.dirstate or isdir or f == '.'
2251 if (f in repo.dirstate or isdir or f == '.'
2236 or insubrepo() or f in subs):
2252 or insubrepo() or f in subs):
2237 continue
2253 continue
2238
2254
2239 if repo.wvfs.exists(f):
2255 if repo.wvfs.exists(f):
2240 if repo.wvfs.isdir(f):
2256 if repo.wvfs.isdir(f):
2241 warnings.append(_('not removing %s: no tracked files\n')
2257 warnings.append(_('not removing %s: no tracked files\n')
2242 % uipathfn(f))
2258 % uipathfn(f))
2243 else:
2259 else:
2244 warnings.append(_('not removing %s: file is untracked\n')
2260 warnings.append(_('not removing %s: file is untracked\n')
2245 % uipathfn(f))
2261 % uipathfn(f))
2246 # missing files will generate a warning elsewhere
2262 # missing files will generate a warning elsewhere
2247 ret = 1
2263 ret = 1
2248 progress.complete()
2264 progress.complete()
2249
2265
2250 if force:
2266 if force:
2251 list = modified + deleted + clean + added
2267 list = modified + deleted + clean + added
2252 elif after:
2268 elif after:
2253 list = deleted
2269 list = deleted
2254 remaining = modified + added + clean
2270 remaining = modified + added + clean
2255 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2271 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2256 unit=_('files'))
2272 unit=_('files'))
2257 for f in remaining:
2273 for f in remaining:
2258 progress.increment()
2274 progress.increment()
2259 if ui.verbose or (f in files):
2275 if ui.verbose or (f in files):
2260 warnings.append(_('not removing %s: file still exists\n')
2276 warnings.append(_('not removing %s: file still exists\n')
2261 % uipathfn(f))
2277 % uipathfn(f))
2262 ret = 1
2278 ret = 1
2263 progress.complete()
2279 progress.complete()
2264 else:
2280 else:
2265 list = deleted + clean
2281 list = deleted + clean
2266 progress = ui.makeprogress(_('skipping'),
2282 progress = ui.makeprogress(_('skipping'),
2267 total=(len(modified) + len(added)),
2283 total=(len(modified) + len(added)),
2268 unit=_('files'))
2284 unit=_('files'))
2269 for f in modified:
2285 for f in modified:
2270 progress.increment()
2286 progress.increment()
2271 warnings.append(_('not removing %s: file is modified (use -f'
2287 warnings.append(_('not removing %s: file is modified (use -f'
2272 ' to force removal)\n') % uipathfn(f))
2288 ' to force removal)\n') % uipathfn(f))
2273 ret = 1
2289 ret = 1
2274 for f in added:
2290 for f in added:
2275 progress.increment()
2291 progress.increment()
2276 warnings.append(_("not removing %s: file has been marked for add"
2292 warnings.append(_("not removing %s: file has been marked for add"
2277 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2293 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2278 ret = 1
2294 ret = 1
2279 progress.complete()
2295 progress.complete()
2280
2296
2281 list = sorted(list)
2297 list = sorted(list)
2282 progress = ui.makeprogress(_('deleting'), total=len(list),
2298 progress = ui.makeprogress(_('deleting'), total=len(list),
2283 unit=_('files'))
2299 unit=_('files'))
2284 for f in list:
2300 for f in list:
2285 if ui.verbose or not m.exact(f):
2301 if ui.verbose or not m.exact(f):
2286 progress.increment()
2302 progress.increment()
2287 ui.status(_('removing %s\n') % uipathfn(f),
2303 ui.status(_('removing %s\n') % uipathfn(f),
2288 label='ui.addremove.removed')
2304 label='ui.addremove.removed')
2289 progress.complete()
2305 progress.complete()
2290
2306
2291 if not dryrun:
2307 if not dryrun:
2292 with repo.wlock():
2308 with repo.wlock():
2293 if not after:
2309 if not after:
2294 for f in list:
2310 for f in list:
2295 if f in added:
2311 if f in added:
2296 continue # we never unlink added files on remove
2312 continue # we never unlink added files on remove
2297 rmdir = repo.ui.configbool('experimental',
2313 rmdir = repo.ui.configbool('experimental',
2298 'removeemptydirs')
2314 'removeemptydirs')
2299 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2315 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2300 repo[None].forget(list)
2316 repo[None].forget(list)
2301
2317
2302 if warn:
2318 if warn:
2303 for warning in warnings:
2319 for warning in warnings:
2304 ui.warn(warning)
2320 ui.warn(warning)
2305
2321
2306 return ret
2322 return ret
2307
2323
2308 def _catfmtneedsdata(fm):
2324 def _catfmtneedsdata(fm):
2309 return not fm.datahint() or 'data' in fm.datahint()
2325 return not fm.datahint() or 'data' in fm.datahint()
2310
2326
2311 def _updatecatformatter(fm, ctx, matcher, path, decode):
2327 def _updatecatformatter(fm, ctx, matcher, path, decode):
2312 """Hook for adding data to the formatter used by ``hg cat``.
2328 """Hook for adding data to the formatter used by ``hg cat``.
2313
2329
2314 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2330 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2315 this method first."""
2331 this method first."""
2316
2332
2317 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2333 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2318 # wasn't requested.
2334 # wasn't requested.
2319 data = b''
2335 data = b''
2320 if _catfmtneedsdata(fm):
2336 if _catfmtneedsdata(fm):
2321 data = ctx[path].data()
2337 data = ctx[path].data()
2322 if decode:
2338 if decode:
2323 data = ctx.repo().wwritedata(path, data)
2339 data = ctx.repo().wwritedata(path, data)
2324 fm.startitem()
2340 fm.startitem()
2325 fm.context(ctx=ctx)
2341 fm.context(ctx=ctx)
2326 fm.write('data', '%s', data)
2342 fm.write('data', '%s', data)
2327 fm.data(path=path)
2343 fm.data(path=path)
2328
2344
2329 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2345 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2330 err = 1
2346 err = 1
2331 opts = pycompat.byteskwargs(opts)
2347 opts = pycompat.byteskwargs(opts)
2332
2348
2333 def write(path):
2349 def write(path):
2334 filename = None
2350 filename = None
2335 if fntemplate:
2351 if fntemplate:
2336 filename = makefilename(ctx, fntemplate,
2352 filename = makefilename(ctx, fntemplate,
2337 pathname=os.path.join(prefix, path))
2353 pathname=os.path.join(prefix, path))
2338 # attempt to create the directory if it does not already exist
2354 # attempt to create the directory if it does not already exist
2339 try:
2355 try:
2340 os.makedirs(os.path.dirname(filename))
2356 os.makedirs(os.path.dirname(filename))
2341 except OSError:
2357 except OSError:
2342 pass
2358 pass
2343 with formatter.maybereopen(basefm, filename) as fm:
2359 with formatter.maybereopen(basefm, filename) as fm:
2344 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2360 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2345
2361
2346 # Automation often uses hg cat on single files, so special case it
2362 # Automation often uses hg cat on single files, so special case it
2347 # for performance to avoid the cost of parsing the manifest.
2363 # for performance to avoid the cost of parsing the manifest.
2348 if len(matcher.files()) == 1 and not matcher.anypats():
2364 if len(matcher.files()) == 1 and not matcher.anypats():
2349 file = matcher.files()[0]
2365 file = matcher.files()[0]
2350 mfl = repo.manifestlog
2366 mfl = repo.manifestlog
2351 mfnode = ctx.manifestnode()
2367 mfnode = ctx.manifestnode()
2352 try:
2368 try:
2353 if mfnode and mfl[mfnode].find(file)[0]:
2369 if mfnode and mfl[mfnode].find(file)[0]:
2354 if _catfmtneedsdata(basefm):
2370 if _catfmtneedsdata(basefm):
2355 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2371 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2356 write(file)
2372 write(file)
2357 return 0
2373 return 0
2358 except KeyError:
2374 except KeyError:
2359 pass
2375 pass
2360
2376
2361 if _catfmtneedsdata(basefm):
2377 if _catfmtneedsdata(basefm):
2362 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2378 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2363
2379
2364 for abs in ctx.walk(matcher):
2380 for abs in ctx.walk(matcher):
2365 write(abs)
2381 write(abs)
2366 err = 0
2382 err = 0
2367
2383
2368 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2384 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2369 for subpath in sorted(ctx.substate):
2385 for subpath in sorted(ctx.substate):
2370 sub = ctx.sub(subpath)
2386 sub = ctx.sub(subpath)
2371 try:
2387 try:
2372 submatch = matchmod.subdirmatcher(subpath, matcher)
2388 submatch = matchmod.subdirmatcher(subpath, matcher)
2373 subprefix = os.path.join(prefix, subpath)
2389 subprefix = os.path.join(prefix, subpath)
2374 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2390 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2375 **pycompat.strkwargs(opts)):
2391 **pycompat.strkwargs(opts)):
2376 err = 0
2392 err = 0
2377 except error.RepoLookupError:
2393 except error.RepoLookupError:
2378 ui.status(_("skipping missing subrepository: %s\n") %
2394 ui.status(_("skipping missing subrepository: %s\n") %
2379 uipathfn(subpath))
2395 uipathfn(subpath))
2380
2396
2381 return err
2397 return err
2382
2398
2383 def commit(ui, repo, commitfunc, pats, opts):
2399 def commit(ui, repo, commitfunc, pats, opts):
2384 '''commit the specified files or all outstanding changes'''
2400 '''commit the specified files or all outstanding changes'''
2385 date = opts.get('date')
2401 date = opts.get('date')
2386 if date:
2402 if date:
2387 opts['date'] = dateutil.parsedate(date)
2403 opts['date'] = dateutil.parsedate(date)
2388 message = logmessage(ui, opts)
2404 message = logmessage(ui, opts)
2389 matcher = scmutil.match(repo[None], pats, opts)
2405 matcher = scmutil.match(repo[None], pats, opts)
2390
2406
2391 dsguard = None
2407 dsguard = None
2392 # extract addremove carefully -- this function can be called from a command
2408 # extract addremove carefully -- this function can be called from a command
2393 # that doesn't support addremove
2409 # that doesn't support addremove
2394 if opts.get('addremove'):
2410 if opts.get('addremove'):
2395 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2411 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2396 with dsguard or util.nullcontextmanager():
2412 with dsguard or util.nullcontextmanager():
2397 if dsguard:
2413 if dsguard:
2398 relative = scmutil.anypats(pats, opts)
2414 relative = scmutil.anypats(pats, opts)
2399 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2415 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2400 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2416 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2401 raise error.Abort(
2417 raise error.Abort(
2402 _("failed to mark all new/missing files as added/removed"))
2418 _("failed to mark all new/missing files as added/removed"))
2403
2419
2404 return commitfunc(ui, repo, message, matcher, opts)
2420 return commitfunc(ui, repo, message, matcher, opts)
2405
2421
2406 def samefile(f, ctx1, ctx2):
2422 def samefile(f, ctx1, ctx2):
2407 if f in ctx1.manifest():
2423 if f in ctx1.manifest():
2408 a = ctx1.filectx(f)
2424 a = ctx1.filectx(f)
2409 if f in ctx2.manifest():
2425 if f in ctx2.manifest():
2410 b = ctx2.filectx(f)
2426 b = ctx2.filectx(f)
2411 return (not a.cmp(b)
2427 return (not a.cmp(b)
2412 and a.flags() == b.flags())
2428 and a.flags() == b.flags())
2413 else:
2429 else:
2414 return False
2430 return False
2415 else:
2431 else:
2416 return f not in ctx2.manifest()
2432 return f not in ctx2.manifest()
2417
2433
2418 def amend(ui, repo, old, extra, pats, opts):
2434 def amend(ui, repo, old, extra, pats, opts):
2419 # avoid cycle context -> subrepo -> cmdutil
2435 # avoid cycle context -> subrepo -> cmdutil
2420 from . import context
2436 from . import context
2421
2437
2422 # amend will reuse the existing user if not specified, but the obsolete
2438 # amend will reuse the existing user if not specified, but the obsolete
2423 # marker creation requires that the current user's name is specified.
2439 # marker creation requires that the current user's name is specified.
2424 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2440 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2425 ui.username() # raise exception if username not set
2441 ui.username() # raise exception if username not set
2426
2442
2427 ui.note(_('amending changeset %s\n') % old)
2443 ui.note(_('amending changeset %s\n') % old)
2428 base = old.p1()
2444 base = old.p1()
2429
2445
2430 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2446 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2431 # Participating changesets:
2447 # Participating changesets:
2432 #
2448 #
2433 # wctx o - workingctx that contains changes from working copy
2449 # wctx o - workingctx that contains changes from working copy
2434 # | to go into amending commit
2450 # | to go into amending commit
2435 # |
2451 # |
2436 # old o - changeset to amend
2452 # old o - changeset to amend
2437 # |
2453 # |
2438 # base o - first parent of the changeset to amend
2454 # base o - first parent of the changeset to amend
2439 wctx = repo[None]
2455 wctx = repo[None]
2440
2456
2441 # Copy to avoid mutating input
2457 # Copy to avoid mutating input
2442 extra = extra.copy()
2458 extra = extra.copy()
2443 # Update extra dict from amended commit (e.g. to preserve graft
2459 # Update extra dict from amended commit (e.g. to preserve graft
2444 # source)
2460 # source)
2445 extra.update(old.extra())
2461 extra.update(old.extra())
2446
2462
2447 # Also update it from the from the wctx
2463 # Also update it from the from the wctx
2448 extra.update(wctx.extra())
2464 extra.update(wctx.extra())
2449
2465
2450 user = opts.get('user') or old.user()
2466 user = opts.get('user') or old.user()
2451
2467
2452 datemaydiffer = False # date-only change should be ignored?
2468 datemaydiffer = False # date-only change should be ignored?
2453 if opts.get('date') and opts.get('currentdate'):
2469 if opts.get('date') and opts.get('currentdate'):
2454 raise error.Abort(_('--date and --currentdate are mutually '
2470 raise error.Abort(_('--date and --currentdate are mutually '
2455 'exclusive'))
2471 'exclusive'))
2456 if opts.get('date'):
2472 if opts.get('date'):
2457 date = dateutil.parsedate(opts.get('date'))
2473 date = dateutil.parsedate(opts.get('date'))
2458 elif opts.get('currentdate'):
2474 elif opts.get('currentdate'):
2459 date = dateutil.makedate()
2475 date = dateutil.makedate()
2460 elif (ui.configbool('rewrite', 'update-timestamp')
2476 elif (ui.configbool('rewrite', 'update-timestamp')
2461 and opts.get('currentdate') is None):
2477 and opts.get('currentdate') is None):
2462 date = dateutil.makedate()
2478 date = dateutil.makedate()
2463 datemaydiffer = True
2479 datemaydiffer = True
2464 else:
2480 else:
2465 date = old.date()
2481 date = old.date()
2466
2482
2467 if len(old.parents()) > 1:
2483 if len(old.parents()) > 1:
2468 # ctx.files() isn't reliable for merges, so fall back to the
2484 # ctx.files() isn't reliable for merges, so fall back to the
2469 # slower repo.status() method
2485 # slower repo.status() method
2470 files = {fn for st in base.status(old)[:3] for fn in st}
2486 files = {fn for st in base.status(old)[:3] for fn in st}
2471 else:
2487 else:
2472 files = set(old.files())
2488 files = set(old.files())
2473
2489
2474 # add/remove the files to the working copy if the "addremove" option
2490 # add/remove the files to the working copy if the "addremove" option
2475 # was specified.
2491 # was specified.
2476 matcher = scmutil.match(wctx, pats, opts)
2492 matcher = scmutil.match(wctx, pats, opts)
2477 relative = scmutil.anypats(pats, opts)
2493 relative = scmutil.anypats(pats, opts)
2478 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2494 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2479 if (opts.get('addremove')
2495 if (opts.get('addremove')
2480 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2496 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2481 raise error.Abort(
2497 raise error.Abort(
2482 _("failed to mark all new/missing files as added/removed"))
2498 _("failed to mark all new/missing files as added/removed"))
2483
2499
2484 # Check subrepos. This depends on in-place wctx._status update in
2500 # Check subrepos. This depends on in-place wctx._status update in
2485 # subrepo.precommit(). To minimize the risk of this hack, we do
2501 # subrepo.precommit(). To minimize the risk of this hack, we do
2486 # nothing if .hgsub does not exist.
2502 # nothing if .hgsub does not exist.
2487 if '.hgsub' in wctx or '.hgsub' in old:
2503 if '.hgsub' in wctx or '.hgsub' in old:
2488 subs, commitsubs, newsubstate = subrepoutil.precommit(
2504 subs, commitsubs, newsubstate = subrepoutil.precommit(
2489 ui, wctx, wctx._status, matcher)
2505 ui, wctx, wctx._status, matcher)
2490 # amend should abort if commitsubrepos is enabled
2506 # amend should abort if commitsubrepos is enabled
2491 assert not commitsubs
2507 assert not commitsubs
2492 if subs:
2508 if subs:
2493 subrepoutil.writestate(repo, newsubstate)
2509 subrepoutil.writestate(repo, newsubstate)
2494
2510
2495 ms = mergemod.mergestate.read(repo)
2511 ms = mergemod.mergestate.read(repo)
2496 mergeutil.checkunresolved(ms)
2512 mergeutil.checkunresolved(ms)
2497
2513
2498 filestoamend = set(f for f in wctx.files() if matcher(f))
2514 filestoamend = set(f for f in wctx.files() if matcher(f))
2499
2515
2500 changes = (len(filestoamend) > 0)
2516 changes = (len(filestoamend) > 0)
2501 if changes:
2517 if changes:
2502 # Recompute copies (avoid recording a -> b -> a)
2518 # Recompute copies (avoid recording a -> b -> a)
2503 copied = copies.pathcopies(base, wctx, matcher)
2519 copied = copies.pathcopies(base, wctx, matcher)
2504 if old.p2:
2520 if old.p2:
2505 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2521 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2506
2522
2507 # Prune files which were reverted by the updates: if old
2523 # Prune files which were reverted by the updates: if old
2508 # introduced file X and the file was renamed in the working
2524 # introduced file X and the file was renamed in the working
2509 # copy, then those two files are the same and
2525 # copy, then those two files are the same and
2510 # we can discard X from our list of files. Likewise if X
2526 # we can discard X from our list of files. Likewise if X
2511 # was removed, it's no longer relevant. If X is missing (aka
2527 # was removed, it's no longer relevant. If X is missing (aka
2512 # deleted), old X must be preserved.
2528 # deleted), old X must be preserved.
2513 files.update(filestoamend)
2529 files.update(filestoamend)
2514 files = [f for f in files if (f not in filestoamend
2530 files = [f for f in files if (f not in filestoamend
2515 or not samefile(f, wctx, base))]
2531 or not samefile(f, wctx, base))]
2516
2532
2517 def filectxfn(repo, ctx_, path):
2533 def filectxfn(repo, ctx_, path):
2518 try:
2534 try:
2519 # If the file being considered is not amongst the files
2535 # If the file being considered is not amongst the files
2520 # to be amended, we should return the file context from the
2536 # to be amended, we should return the file context from the
2521 # old changeset. This avoids issues when only some files in
2537 # old changeset. This avoids issues when only some files in
2522 # the working copy are being amended but there are also
2538 # the working copy are being amended but there are also
2523 # changes to other files from the old changeset.
2539 # changes to other files from the old changeset.
2524 if path not in filestoamend:
2540 if path not in filestoamend:
2525 return old.filectx(path)
2541 return old.filectx(path)
2526
2542
2527 # Return None for removed files.
2543 # Return None for removed files.
2528 if path in wctx.removed():
2544 if path in wctx.removed():
2529 return None
2545 return None
2530
2546
2531 fctx = wctx[path]
2547 fctx = wctx[path]
2532 flags = fctx.flags()
2548 flags = fctx.flags()
2533 mctx = context.memfilectx(repo, ctx_,
2549 mctx = context.memfilectx(repo, ctx_,
2534 fctx.path(), fctx.data(),
2550 fctx.path(), fctx.data(),
2535 islink='l' in flags,
2551 islink='l' in flags,
2536 isexec='x' in flags,
2552 isexec='x' in flags,
2537 copysource=copied.get(path))
2553 copysource=copied.get(path))
2538 return mctx
2554 return mctx
2539 except KeyError:
2555 except KeyError:
2540 return None
2556 return None
2541 else:
2557 else:
2542 ui.note(_('copying changeset %s to %s\n') % (old, base))
2558 ui.note(_('copying changeset %s to %s\n') % (old, base))
2543
2559
2544 # Use version of files as in the old cset
2560 # Use version of files as in the old cset
2545 def filectxfn(repo, ctx_, path):
2561 def filectxfn(repo, ctx_, path):
2546 try:
2562 try:
2547 return old.filectx(path)
2563 return old.filectx(path)
2548 except KeyError:
2564 except KeyError:
2549 return None
2565 return None
2550
2566
2551 # See if we got a message from -m or -l, if not, open the editor with
2567 # See if we got a message from -m or -l, if not, open the editor with
2552 # the message of the changeset to amend.
2568 # the message of the changeset to amend.
2553 message = logmessage(ui, opts)
2569 message = logmessage(ui, opts)
2554
2570
2555 editform = mergeeditform(old, 'commit.amend')
2571 editform = mergeeditform(old, 'commit.amend')
2556
2572
2557 if not message:
2573 if not message:
2558 message = old.description()
2574 message = old.description()
2559 # Default if message isn't provided and --edit is not passed is to
2575 # Default if message isn't provided and --edit is not passed is to
2560 # invoke editor, but allow --no-edit. If somehow we don't have any
2576 # invoke editor, but allow --no-edit. If somehow we don't have any
2561 # description, let's always start the editor.
2577 # description, let's always start the editor.
2562 doedit = not message or opts.get('edit') in [True, None]
2578 doedit = not message or opts.get('edit') in [True, None]
2563 else:
2579 else:
2564 # Default if message is provided is to not invoke editor, but allow
2580 # Default if message is provided is to not invoke editor, but allow
2565 # --edit.
2581 # --edit.
2566 doedit = opts.get('edit') is True
2582 doedit = opts.get('edit') is True
2567 editor = getcommiteditor(edit=doedit, editform=editform)
2583 editor = getcommiteditor(edit=doedit, editform=editform)
2568
2584
2569 pureextra = extra.copy()
2585 pureextra = extra.copy()
2570 extra['amend_source'] = old.hex()
2586 extra['amend_source'] = old.hex()
2571
2587
2572 new = context.memctx(repo,
2588 new = context.memctx(repo,
2573 parents=[base.node(), old.p2().node()],
2589 parents=[base.node(), old.p2().node()],
2574 text=message,
2590 text=message,
2575 files=files,
2591 files=files,
2576 filectxfn=filectxfn,
2592 filectxfn=filectxfn,
2577 user=user,
2593 user=user,
2578 date=date,
2594 date=date,
2579 extra=extra,
2595 extra=extra,
2580 editor=editor)
2596 editor=editor)
2581
2597
2582 newdesc = changelog.stripdesc(new.description())
2598 newdesc = changelog.stripdesc(new.description())
2583 if ((not changes)
2599 if ((not changes)
2584 and newdesc == old.description()
2600 and newdesc == old.description()
2585 and user == old.user()
2601 and user == old.user()
2586 and (date == old.date() or datemaydiffer)
2602 and (date == old.date() or datemaydiffer)
2587 and pureextra == old.extra()):
2603 and pureextra == old.extra()):
2588 # nothing changed. continuing here would create a new node
2604 # nothing changed. continuing here would create a new node
2589 # anyway because of the amend_source noise.
2605 # anyway because of the amend_source noise.
2590 #
2606 #
2591 # This not what we expect from amend.
2607 # This not what we expect from amend.
2592 return old.node()
2608 return old.node()
2593
2609
2594 commitphase = None
2610 commitphase = None
2595 if opts.get('secret'):
2611 if opts.get('secret'):
2596 commitphase = phases.secret
2612 commitphase = phases.secret
2597 newid = repo.commitctx(new)
2613 newid = repo.commitctx(new)
2598
2614
2599 # Reroute the working copy parent to the new changeset
2615 # Reroute the working copy parent to the new changeset
2600 repo.setparents(newid, nullid)
2616 repo.setparents(newid, nullid)
2601 mapping = {old.node(): (newid,)}
2617 mapping = {old.node(): (newid,)}
2602 obsmetadata = None
2618 obsmetadata = None
2603 if opts.get('note'):
2619 if opts.get('note'):
2604 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2620 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2605 backup = ui.configbool('rewrite', 'backup-bundle')
2621 backup = ui.configbool('rewrite', 'backup-bundle')
2606 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2622 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2607 fixphase=True, targetphase=commitphase,
2623 fixphase=True, targetphase=commitphase,
2608 backup=backup)
2624 backup=backup)
2609
2625
2610 # Fixing the dirstate because localrepo.commitctx does not update
2626 # Fixing the dirstate because localrepo.commitctx does not update
2611 # it. This is rather convenient because we did not need to update
2627 # it. This is rather convenient because we did not need to update
2612 # the dirstate for all the files in the new commit which commitctx
2628 # the dirstate for all the files in the new commit which commitctx
2613 # could have done if it updated the dirstate. Now, we can
2629 # could have done if it updated the dirstate. Now, we can
2614 # selectively update the dirstate only for the amended files.
2630 # selectively update the dirstate only for the amended files.
2615 dirstate = repo.dirstate
2631 dirstate = repo.dirstate
2616
2632
2617 # Update the state of the files which were added and
2633 # Update the state of the files which were added and
2618 # and modified in the amend to "normal" in the dirstate.
2634 # and modified in the amend to "normal" in the dirstate.
2619 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2635 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2620 for f in normalfiles:
2636 for f in normalfiles:
2621 dirstate.normal(f)
2637 dirstate.normal(f)
2622
2638
2623 # Update the state of files which were removed in the amend
2639 # Update the state of files which were removed in the amend
2624 # to "removed" in the dirstate.
2640 # to "removed" in the dirstate.
2625 removedfiles = set(wctx.removed()) & filestoamend
2641 removedfiles = set(wctx.removed()) & filestoamend
2626 for f in removedfiles:
2642 for f in removedfiles:
2627 dirstate.drop(f)
2643 dirstate.drop(f)
2628
2644
2629 return newid
2645 return newid
2630
2646
2631 def commiteditor(repo, ctx, subs, editform=''):
2647 def commiteditor(repo, ctx, subs, editform=''):
2632 if ctx.description():
2648 if ctx.description():
2633 return ctx.description()
2649 return ctx.description()
2634 return commitforceeditor(repo, ctx, subs, editform=editform,
2650 return commitforceeditor(repo, ctx, subs, editform=editform,
2635 unchangedmessagedetection=True)
2651 unchangedmessagedetection=True)
2636
2652
2637 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2653 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2638 editform='', unchangedmessagedetection=False):
2654 editform='', unchangedmessagedetection=False):
2639 if not extramsg:
2655 if not extramsg:
2640 extramsg = _("Leave message empty to abort commit.")
2656 extramsg = _("Leave message empty to abort commit.")
2641
2657
2642 forms = [e for e in editform.split('.') if e]
2658 forms = [e for e in editform.split('.') if e]
2643 forms.insert(0, 'changeset')
2659 forms.insert(0, 'changeset')
2644 templatetext = None
2660 templatetext = None
2645 while forms:
2661 while forms:
2646 ref = '.'.join(forms)
2662 ref = '.'.join(forms)
2647 if repo.ui.config('committemplate', ref):
2663 if repo.ui.config('committemplate', ref):
2648 templatetext = committext = buildcommittemplate(
2664 templatetext = committext = buildcommittemplate(
2649 repo, ctx, subs, extramsg, ref)
2665 repo, ctx, subs, extramsg, ref)
2650 break
2666 break
2651 forms.pop()
2667 forms.pop()
2652 else:
2668 else:
2653 committext = buildcommittext(repo, ctx, subs, extramsg)
2669 committext = buildcommittext(repo, ctx, subs, extramsg)
2654
2670
2655 # run editor in the repository root
2671 # run editor in the repository root
2656 olddir = encoding.getcwd()
2672 olddir = encoding.getcwd()
2657 os.chdir(repo.root)
2673 os.chdir(repo.root)
2658
2674
2659 # make in-memory changes visible to external process
2675 # make in-memory changes visible to external process
2660 tr = repo.currenttransaction()
2676 tr = repo.currenttransaction()
2661 repo.dirstate.write(tr)
2677 repo.dirstate.write(tr)
2662 pending = tr and tr.writepending() and repo.root
2678 pending = tr and tr.writepending() and repo.root
2663
2679
2664 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2680 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2665 editform=editform, pending=pending,
2681 editform=editform, pending=pending,
2666 repopath=repo.path, action='commit')
2682 repopath=repo.path, action='commit')
2667 text = editortext
2683 text = editortext
2668
2684
2669 # strip away anything below this special string (used for editors that want
2685 # strip away anything below this special string (used for editors that want
2670 # to display the diff)
2686 # to display the diff)
2671 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2687 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2672 if stripbelow:
2688 if stripbelow:
2673 text = text[:stripbelow.start()]
2689 text = text[:stripbelow.start()]
2674
2690
2675 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2691 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2676 os.chdir(olddir)
2692 os.chdir(olddir)
2677
2693
2678 if finishdesc:
2694 if finishdesc:
2679 text = finishdesc(text)
2695 text = finishdesc(text)
2680 if not text.strip():
2696 if not text.strip():
2681 raise error.Abort(_("empty commit message"))
2697 raise error.Abort(_("empty commit message"))
2682 if unchangedmessagedetection and editortext == templatetext:
2698 if unchangedmessagedetection and editortext == templatetext:
2683 raise error.Abort(_("commit message unchanged"))
2699 raise error.Abort(_("commit message unchanged"))
2684
2700
2685 return text
2701 return text
2686
2702
2687 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2703 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2688 ui = repo.ui
2704 ui = repo.ui
2689 spec = formatter.templatespec(ref, None, None)
2705 spec = formatter.templatespec(ref, None, None)
2690 t = logcmdutil.changesettemplater(ui, repo, spec)
2706 t = logcmdutil.changesettemplater(ui, repo, spec)
2691 t.t.cache.update((k, templater.unquotestring(v))
2707 t.t.cache.update((k, templater.unquotestring(v))
2692 for k, v in repo.ui.configitems('committemplate'))
2708 for k, v in repo.ui.configitems('committemplate'))
2693
2709
2694 if not extramsg:
2710 if not extramsg:
2695 extramsg = '' # ensure that extramsg is string
2711 extramsg = '' # ensure that extramsg is string
2696
2712
2697 ui.pushbuffer()
2713 ui.pushbuffer()
2698 t.show(ctx, extramsg=extramsg)
2714 t.show(ctx, extramsg=extramsg)
2699 return ui.popbuffer()
2715 return ui.popbuffer()
2700
2716
2701 def hgprefix(msg):
2717 def hgprefix(msg):
2702 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2718 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2703
2719
2704 def buildcommittext(repo, ctx, subs, extramsg):
2720 def buildcommittext(repo, ctx, subs, extramsg):
2705 edittext = []
2721 edittext = []
2706 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2722 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2707 if ctx.description():
2723 if ctx.description():
2708 edittext.append(ctx.description())
2724 edittext.append(ctx.description())
2709 edittext.append("")
2725 edittext.append("")
2710 edittext.append("") # Empty line between message and comments.
2726 edittext.append("") # Empty line between message and comments.
2711 edittext.append(hgprefix(_("Enter commit message."
2727 edittext.append(hgprefix(_("Enter commit message."
2712 " Lines beginning with 'HG:' are removed.")))
2728 " Lines beginning with 'HG:' are removed.")))
2713 edittext.append(hgprefix(extramsg))
2729 edittext.append(hgprefix(extramsg))
2714 edittext.append("HG: --")
2730 edittext.append("HG: --")
2715 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2731 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2716 if ctx.p2():
2732 if ctx.p2():
2717 edittext.append(hgprefix(_("branch merge")))
2733 edittext.append(hgprefix(_("branch merge")))
2718 if ctx.branch():
2734 if ctx.branch():
2719 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2735 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2720 if bookmarks.isactivewdirparent(repo):
2736 if bookmarks.isactivewdirparent(repo):
2721 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2737 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2722 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2738 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2723 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2739 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2724 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2740 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2725 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2741 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2726 if not added and not modified and not removed:
2742 if not added and not modified and not removed:
2727 edittext.append(hgprefix(_("no files changed")))
2743 edittext.append(hgprefix(_("no files changed")))
2728 edittext.append("")
2744 edittext.append("")
2729
2745
2730 return "\n".join(edittext)
2746 return "\n".join(edittext)
2731
2747
2732 def commitstatus(repo, node, branch, bheads=None, opts=None):
2748 def commitstatus(repo, node, branch, bheads=None, opts=None):
2733 if opts is None:
2749 if opts is None:
2734 opts = {}
2750 opts = {}
2735 ctx = repo[node]
2751 ctx = repo[node]
2736 parents = ctx.parents()
2752 parents = ctx.parents()
2737
2753
2738 if (not opts.get('amend') and bheads and node not in bheads and not
2754 if (not opts.get('amend') and bheads and node not in bheads and not
2739 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2755 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2740 repo.ui.status(_('created new head\n'))
2756 repo.ui.status(_('created new head\n'))
2741 # The message is not printed for initial roots. For the other
2757 # The message is not printed for initial roots. For the other
2742 # changesets, it is printed in the following situations:
2758 # changesets, it is printed in the following situations:
2743 #
2759 #
2744 # Par column: for the 2 parents with ...
2760 # Par column: for the 2 parents with ...
2745 # N: null or no parent
2761 # N: null or no parent
2746 # B: parent is on another named branch
2762 # B: parent is on another named branch
2747 # C: parent is a regular non head changeset
2763 # C: parent is a regular non head changeset
2748 # H: parent was a branch head of the current branch
2764 # H: parent was a branch head of the current branch
2749 # Msg column: whether we print "created new head" message
2765 # Msg column: whether we print "created new head" message
2750 # In the following, it is assumed that there already exists some
2766 # In the following, it is assumed that there already exists some
2751 # initial branch heads of the current branch, otherwise nothing is
2767 # initial branch heads of the current branch, otherwise nothing is
2752 # printed anyway.
2768 # printed anyway.
2753 #
2769 #
2754 # Par Msg Comment
2770 # Par Msg Comment
2755 # N N y additional topo root
2771 # N N y additional topo root
2756 #
2772 #
2757 # B N y additional branch root
2773 # B N y additional branch root
2758 # C N y additional topo head
2774 # C N y additional topo head
2759 # H N n usual case
2775 # H N n usual case
2760 #
2776 #
2761 # B B y weird additional branch root
2777 # B B y weird additional branch root
2762 # C B y branch merge
2778 # C B y branch merge
2763 # H B n merge with named branch
2779 # H B n merge with named branch
2764 #
2780 #
2765 # C C y additional head from merge
2781 # C C y additional head from merge
2766 # C H n merge with a head
2782 # C H n merge with a head
2767 #
2783 #
2768 # H H n head merge: head count decreases
2784 # H H n head merge: head count decreases
2769
2785
2770 if not opts.get('close_branch'):
2786 if not opts.get('close_branch'):
2771 for r in parents:
2787 for r in parents:
2772 if r.closesbranch() and r.branch() == branch:
2788 if r.closesbranch() and r.branch() == branch:
2773 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2789 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2774
2790
2775 if repo.ui.debugflag:
2791 if repo.ui.debugflag:
2776 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2792 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2777 elif repo.ui.verbose:
2793 elif repo.ui.verbose:
2778 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2794 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2779
2795
2780 def postcommitstatus(repo, pats, opts):
2796 def postcommitstatus(repo, pats, opts):
2781 return repo.status(match=scmutil.match(repo[None], pats, opts))
2797 return repo.status(match=scmutil.match(repo[None], pats, opts))
2782
2798
2783 def revert(ui, repo, ctx, parents, *pats, **opts):
2799 def revert(ui, repo, ctx, parents, *pats, **opts):
2784 opts = pycompat.byteskwargs(opts)
2800 opts = pycompat.byteskwargs(opts)
2785 parent, p2 = parents
2801 parent, p2 = parents
2786 node = ctx.node()
2802 node = ctx.node()
2787
2803
2788 mf = ctx.manifest()
2804 mf = ctx.manifest()
2789 if node == p2:
2805 if node == p2:
2790 parent = p2
2806 parent = p2
2791
2807
2792 # need all matching names in dirstate and manifest of target rev,
2808 # need all matching names in dirstate and manifest of target rev,
2793 # so have to walk both. do not print errors if files exist in one
2809 # so have to walk both. do not print errors if files exist in one
2794 # but not other. in both cases, filesets should be evaluated against
2810 # but not other. in both cases, filesets should be evaluated against
2795 # workingctx to get consistent result (issue4497). this means 'set:**'
2811 # workingctx to get consistent result (issue4497). this means 'set:**'
2796 # cannot be used to select missing files from target rev.
2812 # cannot be used to select missing files from target rev.
2797
2813
2798 # `names` is a mapping for all elements in working copy and target revision
2814 # `names` is a mapping for all elements in working copy and target revision
2799 # The mapping is in the form:
2815 # The mapping is in the form:
2800 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2816 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2801 names = {}
2817 names = {}
2802 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2818 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2803
2819
2804 with repo.wlock():
2820 with repo.wlock():
2805 ## filling of the `names` mapping
2821 ## filling of the `names` mapping
2806 # walk dirstate to fill `names`
2822 # walk dirstate to fill `names`
2807
2823
2808 interactive = opts.get('interactive', False)
2824 interactive = opts.get('interactive', False)
2809 wctx = repo[None]
2825 wctx = repo[None]
2810 m = scmutil.match(wctx, pats, opts)
2826 m = scmutil.match(wctx, pats, opts)
2811
2827
2812 # we'll need this later
2828 # we'll need this later
2813 targetsubs = sorted(s for s in wctx.substate if m(s))
2829 targetsubs = sorted(s for s in wctx.substate if m(s))
2814
2830
2815 if not m.always():
2831 if not m.always():
2816 matcher = matchmod.badmatch(m, lambda x, y: False)
2832 matcher = matchmod.badmatch(m, lambda x, y: False)
2817 for abs in wctx.walk(matcher):
2833 for abs in wctx.walk(matcher):
2818 names[abs] = m.exact(abs)
2834 names[abs] = m.exact(abs)
2819
2835
2820 # walk target manifest to fill `names`
2836 # walk target manifest to fill `names`
2821
2837
2822 def badfn(path, msg):
2838 def badfn(path, msg):
2823 if path in names:
2839 if path in names:
2824 return
2840 return
2825 if path in ctx.substate:
2841 if path in ctx.substate:
2826 return
2842 return
2827 path_ = path + '/'
2843 path_ = path + '/'
2828 for f in names:
2844 for f in names:
2829 if f.startswith(path_):
2845 if f.startswith(path_):
2830 return
2846 return
2831 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2847 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2832
2848
2833 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2849 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2834 if abs not in names:
2850 if abs not in names:
2835 names[abs] = m.exact(abs)
2851 names[abs] = m.exact(abs)
2836
2852
2837 # Find status of all file in `names`.
2853 # Find status of all file in `names`.
2838 m = scmutil.matchfiles(repo, names)
2854 m = scmutil.matchfiles(repo, names)
2839
2855
2840 changes = repo.status(node1=node, match=m,
2856 changes = repo.status(node1=node, match=m,
2841 unknown=True, ignored=True, clean=True)
2857 unknown=True, ignored=True, clean=True)
2842 else:
2858 else:
2843 changes = repo.status(node1=node, match=m)
2859 changes = repo.status(node1=node, match=m)
2844 for kind in changes:
2860 for kind in changes:
2845 for abs in kind:
2861 for abs in kind:
2846 names[abs] = m.exact(abs)
2862 names[abs] = m.exact(abs)
2847
2863
2848 m = scmutil.matchfiles(repo, names)
2864 m = scmutil.matchfiles(repo, names)
2849
2865
2850 modified = set(changes.modified)
2866 modified = set(changes.modified)
2851 added = set(changes.added)
2867 added = set(changes.added)
2852 removed = set(changes.removed)
2868 removed = set(changes.removed)
2853 _deleted = set(changes.deleted)
2869 _deleted = set(changes.deleted)
2854 unknown = set(changes.unknown)
2870 unknown = set(changes.unknown)
2855 unknown.update(changes.ignored)
2871 unknown.update(changes.ignored)
2856 clean = set(changes.clean)
2872 clean = set(changes.clean)
2857 modadded = set()
2873 modadded = set()
2858
2874
2859 # We need to account for the state of the file in the dirstate,
2875 # We need to account for the state of the file in the dirstate,
2860 # even when we revert against something else than parent. This will
2876 # even when we revert against something else than parent. This will
2861 # slightly alter the behavior of revert (doing back up or not, delete
2877 # slightly alter the behavior of revert (doing back up or not, delete
2862 # or just forget etc).
2878 # or just forget etc).
2863 if parent == node:
2879 if parent == node:
2864 dsmodified = modified
2880 dsmodified = modified
2865 dsadded = added
2881 dsadded = added
2866 dsremoved = removed
2882 dsremoved = removed
2867 # store all local modifications, useful later for rename detection
2883 # store all local modifications, useful later for rename detection
2868 localchanges = dsmodified | dsadded
2884 localchanges = dsmodified | dsadded
2869 modified, added, removed = set(), set(), set()
2885 modified, added, removed = set(), set(), set()
2870 else:
2886 else:
2871 changes = repo.status(node1=parent, match=m)
2887 changes = repo.status(node1=parent, match=m)
2872 dsmodified = set(changes.modified)
2888 dsmodified = set(changes.modified)
2873 dsadded = set(changes.added)
2889 dsadded = set(changes.added)
2874 dsremoved = set(changes.removed)
2890 dsremoved = set(changes.removed)
2875 # store all local modifications, useful later for rename detection
2891 # store all local modifications, useful later for rename detection
2876 localchanges = dsmodified | dsadded
2892 localchanges = dsmodified | dsadded
2877
2893
2878 # only take into account for removes between wc and target
2894 # only take into account for removes between wc and target
2879 clean |= dsremoved - removed
2895 clean |= dsremoved - removed
2880 dsremoved &= removed
2896 dsremoved &= removed
2881 # distinct between dirstate remove and other
2897 # distinct between dirstate remove and other
2882 removed -= dsremoved
2898 removed -= dsremoved
2883
2899
2884 modadded = added & dsmodified
2900 modadded = added & dsmodified
2885 added -= modadded
2901 added -= modadded
2886
2902
2887 # tell newly modified apart.
2903 # tell newly modified apart.
2888 dsmodified &= modified
2904 dsmodified &= modified
2889 dsmodified |= modified & dsadded # dirstate added may need backup
2905 dsmodified |= modified & dsadded # dirstate added may need backup
2890 modified -= dsmodified
2906 modified -= dsmodified
2891
2907
2892 # We need to wait for some post-processing to update this set
2908 # We need to wait for some post-processing to update this set
2893 # before making the distinction. The dirstate will be used for
2909 # before making the distinction. The dirstate will be used for
2894 # that purpose.
2910 # that purpose.
2895 dsadded = added
2911 dsadded = added
2896
2912
2897 # in case of merge, files that are actually added can be reported as
2913 # in case of merge, files that are actually added can be reported as
2898 # modified, we need to post process the result
2914 # modified, we need to post process the result
2899 if p2 != nullid:
2915 if p2 != nullid:
2900 mergeadd = set(dsmodified)
2916 mergeadd = set(dsmodified)
2901 for path in dsmodified:
2917 for path in dsmodified:
2902 if path in mf:
2918 if path in mf:
2903 mergeadd.remove(path)
2919 mergeadd.remove(path)
2904 dsadded |= mergeadd
2920 dsadded |= mergeadd
2905 dsmodified -= mergeadd
2921 dsmodified -= mergeadd
2906
2922
2907 # if f is a rename, update `names` to also revert the source
2923 # if f is a rename, update `names` to also revert the source
2908 for f in localchanges:
2924 for f in localchanges:
2909 src = repo.dirstate.copied(f)
2925 src = repo.dirstate.copied(f)
2910 # XXX should we check for rename down to target node?
2926 # XXX should we check for rename down to target node?
2911 if src and src not in names and repo.dirstate[src] == 'r':
2927 if src and src not in names and repo.dirstate[src] == 'r':
2912 dsremoved.add(src)
2928 dsremoved.add(src)
2913 names[src] = True
2929 names[src] = True
2914
2930
2915 # determine the exact nature of the deleted changesets
2931 # determine the exact nature of the deleted changesets
2916 deladded = set(_deleted)
2932 deladded = set(_deleted)
2917 for path in _deleted:
2933 for path in _deleted:
2918 if path in mf:
2934 if path in mf:
2919 deladded.remove(path)
2935 deladded.remove(path)
2920 deleted = _deleted - deladded
2936 deleted = _deleted - deladded
2921
2937
2922 # distinguish between file to forget and the other
2938 # distinguish between file to forget and the other
2923 added = set()
2939 added = set()
2924 for abs in dsadded:
2940 for abs in dsadded:
2925 if repo.dirstate[abs] != 'a':
2941 if repo.dirstate[abs] != 'a':
2926 added.add(abs)
2942 added.add(abs)
2927 dsadded -= added
2943 dsadded -= added
2928
2944
2929 for abs in deladded:
2945 for abs in deladded:
2930 if repo.dirstate[abs] == 'a':
2946 if repo.dirstate[abs] == 'a':
2931 dsadded.add(abs)
2947 dsadded.add(abs)
2932 deladded -= dsadded
2948 deladded -= dsadded
2933
2949
2934 # For files marked as removed, we check if an unknown file is present at
2950 # For files marked as removed, we check if an unknown file is present at
2935 # the same path. If a such file exists it may need to be backed up.
2951 # the same path. If a such file exists it may need to be backed up.
2936 # Making the distinction at this stage helps have simpler backup
2952 # Making the distinction at this stage helps have simpler backup
2937 # logic.
2953 # logic.
2938 removunk = set()
2954 removunk = set()
2939 for abs in removed:
2955 for abs in removed:
2940 target = repo.wjoin(abs)
2956 target = repo.wjoin(abs)
2941 if os.path.lexists(target):
2957 if os.path.lexists(target):
2942 removunk.add(abs)
2958 removunk.add(abs)
2943 removed -= removunk
2959 removed -= removunk
2944
2960
2945 dsremovunk = set()
2961 dsremovunk = set()
2946 for abs in dsremoved:
2962 for abs in dsremoved:
2947 target = repo.wjoin(abs)
2963 target = repo.wjoin(abs)
2948 if os.path.lexists(target):
2964 if os.path.lexists(target):
2949 dsremovunk.add(abs)
2965 dsremovunk.add(abs)
2950 dsremoved -= dsremovunk
2966 dsremoved -= dsremovunk
2951
2967
2952 # action to be actually performed by revert
2968 # action to be actually performed by revert
2953 # (<list of file>, message>) tuple
2969 # (<list of file>, message>) tuple
2954 actions = {'revert': ([], _('reverting %s\n')),
2970 actions = {'revert': ([], _('reverting %s\n')),
2955 'add': ([], _('adding %s\n')),
2971 'add': ([], _('adding %s\n')),
2956 'remove': ([], _('removing %s\n')),
2972 'remove': ([], _('removing %s\n')),
2957 'drop': ([], _('removing %s\n')),
2973 'drop': ([], _('removing %s\n')),
2958 'forget': ([], _('forgetting %s\n')),
2974 'forget': ([], _('forgetting %s\n')),
2959 'undelete': ([], _('undeleting %s\n')),
2975 'undelete': ([], _('undeleting %s\n')),
2960 'noop': (None, _('no changes needed to %s\n')),
2976 'noop': (None, _('no changes needed to %s\n')),
2961 'unknown': (None, _('file not managed: %s\n')),
2977 'unknown': (None, _('file not managed: %s\n')),
2962 }
2978 }
2963
2979
2964 # "constant" that convey the backup strategy.
2980 # "constant" that convey the backup strategy.
2965 # All set to `discard` if `no-backup` is set do avoid checking
2981 # All set to `discard` if `no-backup` is set do avoid checking
2966 # no_backup lower in the code.
2982 # no_backup lower in the code.
2967 # These values are ordered for comparison purposes
2983 # These values are ordered for comparison purposes
2968 backupinteractive = 3 # do backup if interactively modified
2984 backupinteractive = 3 # do backup if interactively modified
2969 backup = 2 # unconditionally do backup
2985 backup = 2 # unconditionally do backup
2970 check = 1 # check if the existing file differs from target
2986 check = 1 # check if the existing file differs from target
2971 discard = 0 # never do backup
2987 discard = 0 # never do backup
2972 if opts.get('no_backup'):
2988 if opts.get('no_backup'):
2973 backupinteractive = backup = check = discard
2989 backupinteractive = backup = check = discard
2974 if interactive:
2990 if interactive:
2975 dsmodifiedbackup = backupinteractive
2991 dsmodifiedbackup = backupinteractive
2976 else:
2992 else:
2977 dsmodifiedbackup = backup
2993 dsmodifiedbackup = backup
2978 tobackup = set()
2994 tobackup = set()
2979
2995
2980 backupanddel = actions['remove']
2996 backupanddel = actions['remove']
2981 if not opts.get('no_backup'):
2997 if not opts.get('no_backup'):
2982 backupanddel = actions['drop']
2998 backupanddel = actions['drop']
2983
2999
2984 disptable = (
3000 disptable = (
2985 # dispatch table:
3001 # dispatch table:
2986 # file state
3002 # file state
2987 # action
3003 # action
2988 # make backup
3004 # make backup
2989
3005
2990 ## Sets that results that will change file on disk
3006 ## Sets that results that will change file on disk
2991 # Modified compared to target, no local change
3007 # Modified compared to target, no local change
2992 (modified, actions['revert'], discard),
3008 (modified, actions['revert'], discard),
2993 # Modified compared to target, but local file is deleted
3009 # Modified compared to target, but local file is deleted
2994 (deleted, actions['revert'], discard),
3010 (deleted, actions['revert'], discard),
2995 # Modified compared to target, local change
3011 # Modified compared to target, local change
2996 (dsmodified, actions['revert'], dsmodifiedbackup),
3012 (dsmodified, actions['revert'], dsmodifiedbackup),
2997 # Added since target
3013 # Added since target
2998 (added, actions['remove'], discard),
3014 (added, actions['remove'], discard),
2999 # Added in working directory
3015 # Added in working directory
3000 (dsadded, actions['forget'], discard),
3016 (dsadded, actions['forget'], discard),
3001 # Added since target, have local modification
3017 # Added since target, have local modification
3002 (modadded, backupanddel, backup),
3018 (modadded, backupanddel, backup),
3003 # Added since target but file is missing in working directory
3019 # Added since target but file is missing in working directory
3004 (deladded, actions['drop'], discard),
3020 (deladded, actions['drop'], discard),
3005 # Removed since target, before working copy parent
3021 # Removed since target, before working copy parent
3006 (removed, actions['add'], discard),
3022 (removed, actions['add'], discard),
3007 # Same as `removed` but an unknown file exists at the same path
3023 # Same as `removed` but an unknown file exists at the same path
3008 (removunk, actions['add'], check),
3024 (removunk, actions['add'], check),
3009 # Removed since targe, marked as such in working copy parent
3025 # Removed since targe, marked as such in working copy parent
3010 (dsremoved, actions['undelete'], discard),
3026 (dsremoved, actions['undelete'], discard),
3011 # Same as `dsremoved` but an unknown file exists at the same path
3027 # Same as `dsremoved` but an unknown file exists at the same path
3012 (dsremovunk, actions['undelete'], check),
3028 (dsremovunk, actions['undelete'], check),
3013 ## the following sets does not result in any file changes
3029 ## the following sets does not result in any file changes
3014 # File with no modification
3030 # File with no modification
3015 (clean, actions['noop'], discard),
3031 (clean, actions['noop'], discard),
3016 # Existing file, not tracked anywhere
3032 # Existing file, not tracked anywhere
3017 (unknown, actions['unknown'], discard),
3033 (unknown, actions['unknown'], discard),
3018 )
3034 )
3019
3035
3020 for abs, exact in sorted(names.items()):
3036 for abs, exact in sorted(names.items()):
3021 # target file to be touch on disk (relative to cwd)
3037 # target file to be touch on disk (relative to cwd)
3022 target = repo.wjoin(abs)
3038 target = repo.wjoin(abs)
3023 # search the entry in the dispatch table.
3039 # search the entry in the dispatch table.
3024 # if the file is in any of these sets, it was touched in the working
3040 # if the file is in any of these sets, it was touched in the working
3025 # directory parent and we are sure it needs to be reverted.
3041 # directory parent and we are sure it needs to be reverted.
3026 for table, (xlist, msg), dobackup in disptable:
3042 for table, (xlist, msg), dobackup in disptable:
3027 if abs not in table:
3043 if abs not in table:
3028 continue
3044 continue
3029 if xlist is not None:
3045 if xlist is not None:
3030 xlist.append(abs)
3046 xlist.append(abs)
3031 if dobackup:
3047 if dobackup:
3032 # If in interactive mode, don't automatically create
3048 # If in interactive mode, don't automatically create
3033 # .orig files (issue4793)
3049 # .orig files (issue4793)
3034 if dobackup == backupinteractive:
3050 if dobackup == backupinteractive:
3035 tobackup.add(abs)
3051 tobackup.add(abs)
3036 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3052 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3037 absbakname = scmutil.backuppath(ui, repo, abs)
3053 absbakname = scmutil.backuppath(ui, repo, abs)
3038 bakname = os.path.relpath(absbakname,
3054 bakname = os.path.relpath(absbakname,
3039 start=repo.root)
3055 start=repo.root)
3040 ui.note(_('saving current version of %s as %s\n') %
3056 ui.note(_('saving current version of %s as %s\n') %
3041 (uipathfn(abs), uipathfn(bakname)))
3057 (uipathfn(abs), uipathfn(bakname)))
3042 if not opts.get('dry_run'):
3058 if not opts.get('dry_run'):
3043 if interactive:
3059 if interactive:
3044 util.copyfile(target, absbakname)
3060 util.copyfile(target, absbakname)
3045 else:
3061 else:
3046 util.rename(target, absbakname)
3062 util.rename(target, absbakname)
3047 if opts.get('dry_run'):
3063 if opts.get('dry_run'):
3048 if ui.verbose or not exact:
3064 if ui.verbose or not exact:
3049 ui.status(msg % uipathfn(abs))
3065 ui.status(msg % uipathfn(abs))
3050 elif exact:
3066 elif exact:
3051 ui.warn(msg % uipathfn(abs))
3067 ui.warn(msg % uipathfn(abs))
3052 break
3068 break
3053
3069
3054 if not opts.get('dry_run'):
3070 if not opts.get('dry_run'):
3055 needdata = ('revert', 'add', 'undelete')
3071 needdata = ('revert', 'add', 'undelete')
3056 oplist = [actions[name][0] for name in needdata]
3072 oplist = [actions[name][0] for name in needdata]
3057 prefetch = scmutil.prefetchfiles
3073 prefetch = scmutil.prefetchfiles
3058 matchfiles = scmutil.matchfiles
3074 matchfiles = scmutil.matchfiles
3059 prefetch(repo, [ctx.rev()],
3075 prefetch(repo, [ctx.rev()],
3060 matchfiles(repo,
3076 matchfiles(repo,
3061 [f for sublist in oplist for f in sublist]))
3077 [f for sublist in oplist for f in sublist]))
3062 match = scmutil.match(repo[None], pats)
3078 match = scmutil.match(repo[None], pats)
3063 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3079 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3064 match, interactive, tobackup)
3080 match, interactive, tobackup)
3065
3081
3066 if targetsubs:
3082 if targetsubs:
3067 # Revert the subrepos on the revert list
3083 # Revert the subrepos on the revert list
3068 for sub in targetsubs:
3084 for sub in targetsubs:
3069 try:
3085 try:
3070 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3086 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3071 **pycompat.strkwargs(opts))
3087 **pycompat.strkwargs(opts))
3072 except KeyError:
3088 except KeyError:
3073 raise error.Abort("subrepository '%s' does not exist in %s!"
3089 raise error.Abort("subrepository '%s' does not exist in %s!"
3074 % (sub, short(ctx.node())))
3090 % (sub, short(ctx.node())))
3075
3091
3076 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3092 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3077 match, interactive=False, tobackup=None):
3093 match, interactive=False, tobackup=None):
3078 """function that actually perform all the actions computed for revert
3094 """function that actually perform all the actions computed for revert
3079
3095
3080 This is an independent function to let extension to plug in and react to
3096 This is an independent function to let extension to plug in and react to
3081 the imminent revert.
3097 the imminent revert.
3082
3098
3083 Make sure you have the working directory locked when calling this function.
3099 Make sure you have the working directory locked when calling this function.
3084 """
3100 """
3085 parent, p2 = parents
3101 parent, p2 = parents
3086 node = ctx.node()
3102 node = ctx.node()
3087 excluded_files = []
3103 excluded_files = []
3088
3104
3089 def checkout(f):
3105 def checkout(f):
3090 fc = ctx[f]
3106 fc = ctx[f]
3091 repo.wwrite(f, fc.data(), fc.flags())
3107 repo.wwrite(f, fc.data(), fc.flags())
3092
3108
3093 def doremove(f):
3109 def doremove(f):
3094 try:
3110 try:
3095 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3111 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3096 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3112 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3097 except OSError:
3113 except OSError:
3098 pass
3114 pass
3099 repo.dirstate.remove(f)
3115 repo.dirstate.remove(f)
3100
3116
3101 def prntstatusmsg(action, f):
3117 def prntstatusmsg(action, f):
3102 exact = names[f]
3118 exact = names[f]
3103 if repo.ui.verbose or not exact:
3119 if repo.ui.verbose or not exact:
3104 repo.ui.status(actions[action][1] % uipathfn(f))
3120 repo.ui.status(actions[action][1] % uipathfn(f))
3105
3121
3106 audit_path = pathutil.pathauditor(repo.root, cached=True)
3122 audit_path = pathutil.pathauditor(repo.root, cached=True)
3107 for f in actions['forget'][0]:
3123 for f in actions['forget'][0]:
3108 if interactive:
3124 if interactive:
3109 choice = repo.ui.promptchoice(
3125 choice = repo.ui.promptchoice(
3110 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3126 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3111 if choice == 0:
3127 if choice == 0:
3112 prntstatusmsg('forget', f)
3128 prntstatusmsg('forget', f)
3113 repo.dirstate.drop(f)
3129 repo.dirstate.drop(f)
3114 else:
3130 else:
3115 excluded_files.append(f)
3131 excluded_files.append(f)
3116 else:
3132 else:
3117 prntstatusmsg('forget', f)
3133 prntstatusmsg('forget', f)
3118 repo.dirstate.drop(f)
3134 repo.dirstate.drop(f)
3119 for f in actions['remove'][0]:
3135 for f in actions['remove'][0]:
3120 audit_path(f)
3136 audit_path(f)
3121 if interactive:
3137 if interactive:
3122 choice = repo.ui.promptchoice(
3138 choice = repo.ui.promptchoice(
3123 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3139 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3124 if choice == 0:
3140 if choice == 0:
3125 prntstatusmsg('remove', f)
3141 prntstatusmsg('remove', f)
3126 doremove(f)
3142 doremove(f)
3127 else:
3143 else:
3128 excluded_files.append(f)
3144 excluded_files.append(f)
3129 else:
3145 else:
3130 prntstatusmsg('remove', f)
3146 prntstatusmsg('remove', f)
3131 doremove(f)
3147 doremove(f)
3132 for f in actions['drop'][0]:
3148 for f in actions['drop'][0]:
3133 audit_path(f)
3149 audit_path(f)
3134 prntstatusmsg('drop', f)
3150 prntstatusmsg('drop', f)
3135 repo.dirstate.remove(f)
3151 repo.dirstate.remove(f)
3136
3152
3137 normal = None
3153 normal = None
3138 if node == parent:
3154 if node == parent:
3139 # We're reverting to our parent. If possible, we'd like status
3155 # We're reverting to our parent. If possible, we'd like status
3140 # to report the file as clean. We have to use normallookup for
3156 # to report the file as clean. We have to use normallookup for
3141 # merges to avoid losing information about merged/dirty files.
3157 # merges to avoid losing information about merged/dirty files.
3142 if p2 != nullid:
3158 if p2 != nullid:
3143 normal = repo.dirstate.normallookup
3159 normal = repo.dirstate.normallookup
3144 else:
3160 else:
3145 normal = repo.dirstate.normal
3161 normal = repo.dirstate.normal
3146
3162
3147 newlyaddedandmodifiedfiles = set()
3163 newlyaddedandmodifiedfiles = set()
3148 if interactive:
3164 if interactive:
3149 # Prompt the user for changes to revert
3165 # Prompt the user for changes to revert
3150 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3166 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3151 m = scmutil.matchfiles(repo, torevert)
3167 m = scmutil.matchfiles(repo, torevert)
3152 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3168 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3153 section='commands',
3169 section='commands',
3154 configprefix='revert.interactive.')
3170 configprefix='revert.interactive.')
3155 diffopts.nodates = True
3171 diffopts.nodates = True
3156 diffopts.git = True
3172 diffopts.git = True
3157 operation = 'apply'
3173 operation = 'apply'
3158 if node == parent:
3174 if node == parent:
3159 if repo.ui.configbool('experimental',
3175 if repo.ui.configbool('experimental',
3160 'revert.interactive.select-to-keep'):
3176 'revert.interactive.select-to-keep'):
3161 operation = 'keep'
3177 operation = 'keep'
3162 else:
3178 else:
3163 operation = 'discard'
3179 operation = 'discard'
3164
3180
3165 if operation == 'apply':
3181 if operation == 'apply':
3166 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3182 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3167 else:
3183 else:
3168 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3184 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3169 originalchunks = patch.parsepatch(diff)
3185 originalchunks = patch.parsepatch(diff)
3170
3186
3171 try:
3187 try:
3172
3188
3173 chunks, opts = recordfilter(repo.ui, originalchunks, match,
3189 chunks, opts = recordfilter(repo.ui, originalchunks, match,
3174 operation=operation)
3190 operation=operation)
3175 if operation == 'discard':
3191 if operation == 'discard':
3176 chunks = patch.reversehunks(chunks)
3192 chunks = patch.reversehunks(chunks)
3177
3193
3178 except error.PatchError as err:
3194 except error.PatchError as err:
3179 raise error.Abort(_('error parsing patch: %s') % err)
3195 raise error.Abort(_('error parsing patch: %s') % err)
3180
3196
3181 # FIXME: when doing an interactive revert of a copy, there's no way of
3197 # FIXME: when doing an interactive revert of a copy, there's no way of
3182 # performing a partial revert of the added file, the only option is
3198 # performing a partial revert of the added file, the only option is
3183 # "remove added file <name> (Yn)?", so we don't need to worry about the
3199 # "remove added file <name> (Yn)?", so we don't need to worry about the
3184 # alsorestore value. Ideally we'd be able to partially revert
3200 # alsorestore value. Ideally we'd be able to partially revert
3185 # copied/renamed files.
3201 # copied/renamed files.
3186 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3202 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3187 chunks, originalchunks)
3203 chunks, originalchunks)
3188 if tobackup is None:
3204 if tobackup is None:
3189 tobackup = set()
3205 tobackup = set()
3190 # Apply changes
3206 # Apply changes
3191 fp = stringio()
3207 fp = stringio()
3192 # chunks are serialized per file, but files aren't sorted
3208 # chunks are serialized per file, but files aren't sorted
3193 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3209 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3194 prntstatusmsg('revert', f)
3210 prntstatusmsg('revert', f)
3195 files = set()
3211 files = set()
3196 for c in chunks:
3212 for c in chunks:
3197 if ishunk(c):
3213 if ishunk(c):
3198 abs = c.header.filename()
3214 abs = c.header.filename()
3199 # Create a backup file only if this hunk should be backed up
3215 # Create a backup file only if this hunk should be backed up
3200 if c.header.filename() in tobackup:
3216 if c.header.filename() in tobackup:
3201 target = repo.wjoin(abs)
3217 target = repo.wjoin(abs)
3202 bakname = scmutil.backuppath(repo.ui, repo, abs)
3218 bakname = scmutil.backuppath(repo.ui, repo, abs)
3203 util.copyfile(target, bakname)
3219 util.copyfile(target, bakname)
3204 tobackup.remove(abs)
3220 tobackup.remove(abs)
3205 if abs not in files:
3221 if abs not in files:
3206 files.add(abs)
3222 files.add(abs)
3207 if operation == 'keep':
3223 if operation == 'keep':
3208 checkout(abs)
3224 checkout(abs)
3209 c.write(fp)
3225 c.write(fp)
3210 dopatch = fp.tell()
3226 dopatch = fp.tell()
3211 fp.seek(0)
3227 fp.seek(0)
3212 if dopatch:
3228 if dopatch:
3213 try:
3229 try:
3214 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3230 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3215 except error.PatchError as err:
3231 except error.PatchError as err:
3216 raise error.Abort(pycompat.bytestr(err))
3232 raise error.Abort(pycompat.bytestr(err))
3217 del fp
3233 del fp
3218 else:
3234 else:
3219 for f in actions['revert'][0]:
3235 for f in actions['revert'][0]:
3220 prntstatusmsg('revert', f)
3236 prntstatusmsg('revert', f)
3221 checkout(f)
3237 checkout(f)
3222 if normal:
3238 if normal:
3223 normal(f)
3239 normal(f)
3224
3240
3225 for f in actions['add'][0]:
3241 for f in actions['add'][0]:
3226 # Don't checkout modified files, they are already created by the diff
3242 # Don't checkout modified files, they are already created by the diff
3227 if f not in newlyaddedandmodifiedfiles:
3243 if f not in newlyaddedandmodifiedfiles:
3228 prntstatusmsg('add', f)
3244 prntstatusmsg('add', f)
3229 checkout(f)
3245 checkout(f)
3230 repo.dirstate.add(f)
3246 repo.dirstate.add(f)
3231
3247
3232 normal = repo.dirstate.normallookup
3248 normal = repo.dirstate.normallookup
3233 if node == parent and p2 == nullid:
3249 if node == parent and p2 == nullid:
3234 normal = repo.dirstate.normal
3250 normal = repo.dirstate.normal
3235 for f in actions['undelete'][0]:
3251 for f in actions['undelete'][0]:
3236 if interactive:
3252 if interactive:
3237 choice = repo.ui.promptchoice(
3253 choice = repo.ui.promptchoice(
3238 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3254 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3239 if choice == 0:
3255 if choice == 0:
3240 prntstatusmsg('undelete', f)
3256 prntstatusmsg('undelete', f)
3241 checkout(f)
3257 checkout(f)
3242 normal(f)
3258 normal(f)
3243 else:
3259 else:
3244 excluded_files.append(f)
3260 excluded_files.append(f)
3245 else:
3261 else:
3246 prntstatusmsg('undelete', f)
3262 prntstatusmsg('undelete', f)
3247 checkout(f)
3263 checkout(f)
3248 normal(f)
3264 normal(f)
3249
3265
3250 copied = copies.pathcopies(repo[parent], ctx)
3266 copied = copies.pathcopies(repo[parent], ctx)
3251
3267
3252 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3268 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3253 if f in copied:
3269 if f in copied:
3254 repo.dirstate.copy(copied[f], f)
3270 repo.dirstate.copy(copied[f], f)
3255
3271
3256 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3272 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3257 # commands.outgoing. "missing" is "missing" of the result of
3273 # commands.outgoing. "missing" is "missing" of the result of
3258 # "findcommonoutgoing()"
3274 # "findcommonoutgoing()"
3259 outgoinghooks = util.hooks()
3275 outgoinghooks = util.hooks()
3260
3276
3261 # a list of (ui, repo) functions called by commands.summary
3277 # a list of (ui, repo) functions called by commands.summary
3262 summaryhooks = util.hooks()
3278 summaryhooks = util.hooks()
3263
3279
3264 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3280 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3265 #
3281 #
3266 # functions should return tuple of booleans below, if 'changes' is None:
3282 # functions should return tuple of booleans below, if 'changes' is None:
3267 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3283 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3268 #
3284 #
3269 # otherwise, 'changes' is a tuple of tuples below:
3285 # otherwise, 'changes' is a tuple of tuples below:
3270 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3286 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3271 # - (desturl, destbranch, destpeer, outgoing)
3287 # - (desturl, destbranch, destpeer, outgoing)
3272 summaryremotehooks = util.hooks()
3288 summaryremotehooks = util.hooks()
3273
3289
3274
3290
3275 def checkunfinished(repo, commit=False, skipmerge=False):
3291 def checkunfinished(repo, commit=False, skipmerge=False):
3276 '''Look for an unfinished multistep operation, like graft, and abort
3292 '''Look for an unfinished multistep operation, like graft, and abort
3277 if found. It's probably good to check this right before
3293 if found. It's probably good to check this right before
3278 bailifchanged().
3294 bailifchanged().
3279 '''
3295 '''
3280 # Check for non-clearable states first, so things like rebase will take
3296 # Check for non-clearable states first, so things like rebase will take
3281 # precedence over update.
3297 # precedence over update.
3282 for state in statemod._unfinishedstates:
3298 for state in statemod._unfinishedstates:
3283 if (state._clearable or (commit and state._allowcommit) or
3299 if (state._clearable or (commit and state._allowcommit) or
3284 state._reportonly):
3300 state._reportonly):
3285 continue
3301 continue
3286 if state.isunfinished(repo):
3302 if state.isunfinished(repo):
3287 raise error.Abort(state.msg(), hint=state.hint())
3303 raise error.Abort(state.msg(), hint=state.hint())
3288
3304
3289 for s in statemod._unfinishedstates:
3305 for s in statemod._unfinishedstates:
3290 if (not s._clearable or (commit and s._allowcommit) or
3306 if (not s._clearable or (commit and s._allowcommit) or
3291 (s._opname == 'merge' and skipmerge) or s._reportonly):
3307 (s._opname == 'merge' and skipmerge) or s._reportonly):
3292 continue
3308 continue
3293 if s.isunfinished(repo):
3309 if s.isunfinished(repo):
3294 raise error.Abort(s.msg(), hint=s.hint())
3310 raise error.Abort(s.msg(), hint=s.hint())
3295
3311
3296 def clearunfinished(repo):
3312 def clearunfinished(repo):
3297 '''Check for unfinished operations (as above), and clear the ones
3313 '''Check for unfinished operations (as above), and clear the ones
3298 that are clearable.
3314 that are clearable.
3299 '''
3315 '''
3300 for state in statemod._unfinishedstates:
3316 for state in statemod._unfinishedstates:
3301 if state._reportonly:
3317 if state._reportonly:
3302 continue
3318 continue
3303 if not state._clearable and state.isunfinished(repo):
3319 if not state._clearable and state.isunfinished(repo):
3304 raise error.Abort(state.msg(), hint=state.hint())
3320 raise error.Abort(state.msg(), hint=state.hint())
3305
3321
3306 for s in statemod._unfinishedstates:
3322 for s in statemod._unfinishedstates:
3307 if s._opname == 'merge' or state._reportonly:
3323 if s._opname == 'merge' or state._reportonly:
3308 continue
3324 continue
3309 if s._clearable and s.isunfinished(repo):
3325 if s._clearable and s.isunfinished(repo):
3310 util.unlink(repo.vfs.join(s._fname))
3326 util.unlink(repo.vfs.join(s._fname))
3311
3327
3312 def getunfinishedstate(repo):
3328 def getunfinishedstate(repo):
3313 ''' Checks for unfinished operations and returns statecheck object
3329 ''' Checks for unfinished operations and returns statecheck object
3314 for it'''
3330 for it'''
3315 for state in statemod._unfinishedstates:
3331 for state in statemod._unfinishedstates:
3316 if state.isunfinished(repo):
3332 if state.isunfinished(repo):
3317 return state
3333 return state
3318 return None
3334 return None
3319
3335
3320 def howtocontinue(repo):
3336 def howtocontinue(repo):
3321 '''Check for an unfinished operation and return the command to finish
3337 '''Check for an unfinished operation and return the command to finish
3322 it.
3338 it.
3323
3339
3324 statemod._unfinishedstates list is checked for an unfinished operation
3340 statemod._unfinishedstates list is checked for an unfinished operation
3325 and the corresponding message to finish it is generated if a method to
3341 and the corresponding message to finish it is generated if a method to
3326 continue is supported by the operation.
3342 continue is supported by the operation.
3327
3343
3328 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3344 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3329 a boolean.
3345 a boolean.
3330 '''
3346 '''
3331 contmsg = _("continue: %s")
3347 contmsg = _("continue: %s")
3332 for state in statemod._unfinishedstates:
3348 for state in statemod._unfinishedstates:
3333 if not state._continueflag:
3349 if not state._continueflag:
3334 continue
3350 continue
3335 if state.isunfinished(repo):
3351 if state.isunfinished(repo):
3336 return contmsg % state.continuemsg(), True
3352 return contmsg % state.continuemsg(), True
3337 if repo[None].dirty(missing=True, merge=False, branch=False):
3353 if repo[None].dirty(missing=True, merge=False, branch=False):
3338 return contmsg % _("hg commit"), False
3354 return contmsg % _("hg commit"), False
3339 return None, None
3355 return None, None
3340
3356
3341 def checkafterresolved(repo):
3357 def checkafterresolved(repo):
3342 '''Inform the user about the next action after completing hg resolve
3358 '''Inform the user about the next action after completing hg resolve
3343
3359
3344 If there's a an unfinished operation that supports continue flag,
3360 If there's a an unfinished operation that supports continue flag,
3345 howtocontinue will yield repo.ui.warn as the reporter.
3361 howtocontinue will yield repo.ui.warn as the reporter.
3346
3362
3347 Otherwise, it will yield repo.ui.note.
3363 Otherwise, it will yield repo.ui.note.
3348 '''
3364 '''
3349 msg, warning = howtocontinue(repo)
3365 msg, warning = howtocontinue(repo)
3350 if msg is not None:
3366 if msg is not None:
3351 if warning:
3367 if warning:
3352 repo.ui.warn("%s\n" % msg)
3368 repo.ui.warn("%s\n" % msg)
3353 else:
3369 else:
3354 repo.ui.note("%s\n" % msg)
3370 repo.ui.note("%s\n" % msg)
3355
3371
3356 def wrongtooltocontinue(repo, task):
3372 def wrongtooltocontinue(repo, task):
3357 '''Raise an abort suggesting how to properly continue if there is an
3373 '''Raise an abort suggesting how to properly continue if there is an
3358 active task.
3374 active task.
3359
3375
3360 Uses howtocontinue() to find the active task.
3376 Uses howtocontinue() to find the active task.
3361
3377
3362 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3378 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3363 a hint.
3379 a hint.
3364 '''
3380 '''
3365 after = howtocontinue(repo)
3381 after = howtocontinue(repo)
3366 hint = None
3382 hint = None
3367 if after[1]:
3383 if after[1]:
3368 hint = after[0]
3384 hint = after[0]
3369 raise error.Abort(_('no %s in progress') % task, hint=hint)
3385 raise error.Abort(_('no %s in progress') % task, hint=hint)
3370
3386
3371 def abortgraft(ui, repo, graftstate):
3387 def abortgraft(ui, repo, graftstate):
3372 """abort the interrupted graft and rollbacks to the state before interrupted
3388 """abort the interrupted graft and rollbacks to the state before interrupted
3373 graft"""
3389 graft"""
3374 if not graftstate.exists():
3390 if not graftstate.exists():
3375 raise error.Abort(_("no interrupted graft to abort"))
3391 raise error.Abort(_("no interrupted graft to abort"))
3376 statedata = readgraftstate(repo, graftstate)
3392 statedata = readgraftstate(repo, graftstate)
3377 newnodes = statedata.get('newnodes')
3393 newnodes = statedata.get('newnodes')
3378 if newnodes is None:
3394 if newnodes is None:
3379 # and old graft state which does not have all the data required to abort
3395 # and old graft state which does not have all the data required to abort
3380 # the graft
3396 # the graft
3381 raise error.Abort(_("cannot abort using an old graftstate"))
3397 raise error.Abort(_("cannot abort using an old graftstate"))
3382
3398
3383 # changeset from which graft operation was started
3399 # changeset from which graft operation was started
3384 if len(newnodes) > 0:
3400 if len(newnodes) > 0:
3385 startctx = repo[newnodes[0]].p1()
3401 startctx = repo[newnodes[0]].p1()
3386 else:
3402 else:
3387 startctx = repo['.']
3403 startctx = repo['.']
3388 # whether to strip or not
3404 # whether to strip or not
3389 cleanup = False
3405 cleanup = False
3390 from . import hg
3406 from . import hg
3391 if newnodes:
3407 if newnodes:
3392 newnodes = [repo[r].rev() for r in newnodes]
3408 newnodes = [repo[r].rev() for r in newnodes]
3393 cleanup = True
3409 cleanup = True
3394 # checking that none of the newnodes turned public or is public
3410 # checking that none of the newnodes turned public or is public
3395 immutable = [c for c in newnodes if not repo[c].mutable()]
3411 immutable = [c for c in newnodes if not repo[c].mutable()]
3396 if immutable:
3412 if immutable:
3397 repo.ui.warn(_("cannot clean up public changesets %s\n")
3413 repo.ui.warn(_("cannot clean up public changesets %s\n")
3398 % ', '.join(bytes(repo[r]) for r in immutable),
3414 % ', '.join(bytes(repo[r]) for r in immutable),
3399 hint=_("see 'hg help phases' for details"))
3415 hint=_("see 'hg help phases' for details"))
3400 cleanup = False
3416 cleanup = False
3401
3417
3402 # checking that no new nodes are created on top of grafted revs
3418 # checking that no new nodes are created on top of grafted revs
3403 desc = set(repo.changelog.descendants(newnodes))
3419 desc = set(repo.changelog.descendants(newnodes))
3404 if desc - set(newnodes):
3420 if desc - set(newnodes):
3405 repo.ui.warn(_("new changesets detected on destination "
3421 repo.ui.warn(_("new changesets detected on destination "
3406 "branch, can't strip\n"))
3422 "branch, can't strip\n"))
3407 cleanup = False
3423 cleanup = False
3408
3424
3409 if cleanup:
3425 if cleanup:
3410 with repo.wlock(), repo.lock():
3426 with repo.wlock(), repo.lock():
3411 hg.updaterepo(repo, startctx.node(), overwrite=True)
3427 hg.updaterepo(repo, startctx.node(), overwrite=True)
3412 # stripping the new nodes created
3428 # stripping the new nodes created
3413 strippoints = [c.node() for c in repo.set("roots(%ld)",
3429 strippoints = [c.node() for c in repo.set("roots(%ld)",
3414 newnodes)]
3430 newnodes)]
3415 repair.strip(repo.ui, repo, strippoints, backup=False)
3431 repair.strip(repo.ui, repo, strippoints, backup=False)
3416
3432
3417 if not cleanup:
3433 if not cleanup:
3418 # we don't update to the startnode if we can't strip
3434 # we don't update to the startnode if we can't strip
3419 startctx = repo['.']
3435 startctx = repo['.']
3420 hg.updaterepo(repo, startctx.node(), overwrite=True)
3436 hg.updaterepo(repo, startctx.node(), overwrite=True)
3421
3437
3422 ui.status(_("graft aborted\n"))
3438 ui.status(_("graft aborted\n"))
3423 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
3439 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
3424 graftstate.delete()
3440 graftstate.delete()
3425 return 0
3441 return 0
3426
3442
3427 def readgraftstate(repo, graftstate):
3443 def readgraftstate(repo, graftstate):
3428 """read the graft state file and return a dict of the data stored in it"""
3444 """read the graft state file and return a dict of the data stored in it"""
3429 try:
3445 try:
3430 return graftstate.read()
3446 return graftstate.read()
3431 except error.CorruptedState:
3447 except error.CorruptedState:
3432 nodes = repo.vfs.read('graftstate').splitlines()
3448 nodes = repo.vfs.read('graftstate').splitlines()
3433 return {'nodes': nodes}
3449 return {'nodes': nodes}
3434
3450
3435 def hgabortgraft(ui, repo):
3451 def hgabortgraft(ui, repo):
3436 """ abort logic for aborting graft using 'hg abort'"""
3452 """ abort logic for aborting graft using 'hg abort'"""
3437 with repo.wlock():
3453 with repo.wlock():
3438 graftstate = statemod.cmdstate(repo, 'graftstate')
3454 graftstate = statemod.cmdstate(repo, 'graftstate')
3439 return abortgraft(ui, repo, graftstate)
3455 return abortgraft(ui, repo, graftstate)
@@ -1,6450 +1,6451 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from . 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 revsetlang,
56 revsetlang,
57 rewriteutil,
57 rewriteutil,
58 scmutil,
58 scmutil,
59 server,
59 server,
60 shelve as shelvemod,
60 shelve as shelvemod,
61 state as statemod,
61 state as statemod,
62 streamclone,
62 streamclone,
63 tags as tagsmod,
63 tags as tagsmod,
64 ui as uimod,
64 ui as uimod,
65 util,
65 util,
66 verify as verifymod,
66 verify as verifymod,
67 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 commitopts3 = cmdutil.commitopts3
121 formatteropts = cmdutil.formatteropts
122 formatteropts = cmdutil.formatteropts
122 templateopts = cmdutil.templateopts
123 templateopts = cmdutil.templateopts
123 logopts = cmdutil.logopts
124 logopts = cmdutil.logopts
124 diffopts = cmdutil.diffopts
125 diffopts = cmdutil.diffopts
125 diffwsopts = cmdutil.diffwsopts
126 diffwsopts = cmdutil.diffwsopts
126 diffopts2 = cmdutil.diffopts2
127 diffopts2 = cmdutil.diffopts2
127 mergetoolopts = cmdutil.mergetoolopts
128 mergetoolopts = cmdutil.mergetoolopts
128 similarityopts = cmdutil.similarityopts
129 similarityopts = cmdutil.similarityopts
129 subrepoopts = cmdutil.subrepoopts
130 subrepoopts = cmdutil.subrepoopts
130 debugrevlogopts = cmdutil.debugrevlogopts
131 debugrevlogopts = cmdutil.debugrevlogopts
131
132
132 # Commands start here, listed alphabetically
133 # Commands start here, listed alphabetically
133
134
134 @command('abort',
135 @command('abort',
135 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
136 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
136 helpbasic=True)
137 helpbasic=True)
137 def abort(ui, repo, **opts):
138 def abort(ui, repo, **opts):
138 """abort an unfinished operation (EXPERIMENTAL)
139 """abort an unfinished operation (EXPERIMENTAL)
139
140
140 Aborts a multistep operation like graft, histedit, rebase, merge,
141 Aborts a multistep operation like graft, histedit, rebase, merge,
141 and unshelve if they are in an unfinished state.
142 and unshelve if they are in an unfinished state.
142
143
143 use --dry-run/-n to dry run the command.
144 use --dry-run/-n to dry run the command.
144 """
145 """
145 dryrun = opts.get(r'dry_run')
146 dryrun = opts.get(r'dry_run')
146 abortstate = cmdutil.getunfinishedstate(repo)
147 abortstate = cmdutil.getunfinishedstate(repo)
147 if not abortstate:
148 if not abortstate:
148 raise error.Abort(_('no operation in progress'))
149 raise error.Abort(_('no operation in progress'))
149 if not abortstate.abortfunc:
150 if not abortstate.abortfunc:
150 raise error.Abort((_("%s in progress but does not support 'hg abort'") %
151 raise error.Abort((_("%s in progress but does not support 'hg abort'") %
151 (abortstate._opname)), hint=abortstate.hint())
152 (abortstate._opname)), hint=abortstate.hint())
152 if dryrun:
153 if dryrun:
153 ui.status(_('%s in progress, will be aborted\n') % (abortstate._opname))
154 ui.status(_('%s in progress, will be aborted\n') % (abortstate._opname))
154 return
155 return
155 return abortstate.abortfunc(ui, repo)
156 return abortstate.abortfunc(ui, repo)
156
157
157 @command('add',
158 @command('add',
158 walkopts + subrepoopts + dryrunopts,
159 walkopts + subrepoopts + dryrunopts,
159 _('[OPTION]... [FILE]...'),
160 _('[OPTION]... [FILE]...'),
160 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
161 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
161 helpbasic=True, inferrepo=True)
162 helpbasic=True, inferrepo=True)
162 def add(ui, repo, *pats, **opts):
163 def add(ui, repo, *pats, **opts):
163 """add the specified files on the next commit
164 """add the specified files on the next commit
164
165
165 Schedule files to be version controlled and added to the
166 Schedule files to be version controlled and added to the
166 repository.
167 repository.
167
168
168 The files will be added to the repository at the next commit. To
169 The files will be added to the repository at the next commit. To
169 undo an add before that, see :hg:`forget`.
170 undo an add before that, see :hg:`forget`.
170
171
171 If no names are given, add all files to the repository (except
172 If no names are given, add all files to the repository (except
172 files matching ``.hgignore``).
173 files matching ``.hgignore``).
173
174
174 .. container:: verbose
175 .. container:: verbose
175
176
176 Examples:
177 Examples:
177
178
178 - New (unknown) files are added
179 - New (unknown) files are added
179 automatically by :hg:`add`::
180 automatically by :hg:`add`::
180
181
181 $ ls
182 $ ls
182 foo.c
183 foo.c
183 $ hg status
184 $ hg status
184 ? foo.c
185 ? foo.c
185 $ hg add
186 $ hg add
186 adding foo.c
187 adding foo.c
187 $ hg status
188 $ hg status
188 A foo.c
189 A foo.c
189
190
190 - Specific files to be added can be specified::
191 - Specific files to be added can be specified::
191
192
192 $ ls
193 $ ls
193 bar.c foo.c
194 bar.c foo.c
194 $ hg status
195 $ hg status
195 ? bar.c
196 ? bar.c
196 ? foo.c
197 ? foo.c
197 $ hg add bar.c
198 $ hg add bar.c
198 $ hg status
199 $ hg status
199 A bar.c
200 A bar.c
200 ? foo.c
201 ? foo.c
201
202
202 Returns 0 if all files are successfully added.
203 Returns 0 if all files are successfully added.
203 """
204 """
204
205
205 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
206 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
206 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
207 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
207 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
208 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
208 return rejected and 1 or 0
209 return rejected and 1 or 0
209
210
210 @command('addremove',
211 @command('addremove',
211 similarityopts + subrepoopts + walkopts + dryrunopts,
212 similarityopts + subrepoopts + walkopts + dryrunopts,
212 _('[OPTION]... [FILE]...'),
213 _('[OPTION]... [FILE]...'),
213 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
214 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
214 inferrepo=True)
215 inferrepo=True)
215 def addremove(ui, repo, *pats, **opts):
216 def addremove(ui, repo, *pats, **opts):
216 """add all new files, delete all missing files
217 """add all new files, delete all missing files
217
218
218 Add all new files and remove all missing files from the
219 Add all new files and remove all missing files from the
219 repository.
220 repository.
220
221
221 Unless names are given, new files are ignored if they match any of
222 Unless names are given, new files are ignored if they match any of
222 the patterns in ``.hgignore``. As with add, these changes take
223 the patterns in ``.hgignore``. As with add, these changes take
223 effect at the next commit.
224 effect at the next commit.
224
225
225 Use the -s/--similarity option to detect renamed files. This
226 Use the -s/--similarity option to detect renamed files. This
226 option takes a percentage between 0 (disabled) and 100 (files must
227 option takes a percentage between 0 (disabled) and 100 (files must
227 be identical) as its parameter. With a parameter greater than 0,
228 be identical) as its parameter. With a parameter greater than 0,
228 this compares every removed file with every added file and records
229 this compares every removed file with every added file and records
229 those similar enough as renames. Detecting renamed files this way
230 those similar enough as renames. Detecting renamed files this way
230 can be expensive. After using this option, :hg:`status -C` can be
231 can be expensive. After using this option, :hg:`status -C` can be
231 used to check which files were identified as moved or renamed. If
232 used to check which files were identified as moved or renamed. If
232 not specified, -s/--similarity defaults to 100 and only renames of
233 not specified, -s/--similarity defaults to 100 and only renames of
233 identical files are detected.
234 identical files are detected.
234
235
235 .. container:: verbose
236 .. container:: verbose
236
237
237 Examples:
238 Examples:
238
239
239 - A number of files (bar.c and foo.c) are new,
240 - A number of files (bar.c and foo.c) are new,
240 while foobar.c has been removed (without using :hg:`remove`)
241 while foobar.c has been removed (without using :hg:`remove`)
241 from the repository::
242 from the repository::
242
243
243 $ ls
244 $ ls
244 bar.c foo.c
245 bar.c foo.c
245 $ hg status
246 $ hg status
246 ! foobar.c
247 ! foobar.c
247 ? bar.c
248 ? bar.c
248 ? foo.c
249 ? foo.c
249 $ hg addremove
250 $ hg addremove
250 adding bar.c
251 adding bar.c
251 adding foo.c
252 adding foo.c
252 removing foobar.c
253 removing foobar.c
253 $ hg status
254 $ hg status
254 A bar.c
255 A bar.c
255 A foo.c
256 A foo.c
256 R foobar.c
257 R foobar.c
257
258
258 - A file foobar.c was moved to foo.c without using :hg:`rename`.
259 - A file foobar.c was moved to foo.c without using :hg:`rename`.
259 Afterwards, it was edited slightly::
260 Afterwards, it was edited slightly::
260
261
261 $ ls
262 $ ls
262 foo.c
263 foo.c
263 $ hg status
264 $ hg status
264 ! foobar.c
265 ! foobar.c
265 ? foo.c
266 ? foo.c
266 $ hg addremove --similarity 90
267 $ hg addremove --similarity 90
267 removing foobar.c
268 removing foobar.c
268 adding foo.c
269 adding foo.c
269 recording removal of foobar.c as rename to foo.c (94% similar)
270 recording removal of foobar.c as rename to foo.c (94% similar)
270 $ hg status -C
271 $ hg status -C
271 A foo.c
272 A foo.c
272 foobar.c
273 foobar.c
273 R foobar.c
274 R foobar.c
274
275
275 Returns 0 if all files are successfully added.
276 Returns 0 if all files are successfully added.
276 """
277 """
277 opts = pycompat.byteskwargs(opts)
278 opts = pycompat.byteskwargs(opts)
278 if not opts.get('similarity'):
279 if not opts.get('similarity'):
279 opts['similarity'] = '100'
280 opts['similarity'] = '100'
280 matcher = scmutil.match(repo[None], pats, opts)
281 matcher = scmutil.match(repo[None], pats, opts)
281 relative = scmutil.anypats(pats, opts)
282 relative = scmutil.anypats(pats, opts)
282 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
283 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
283 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
284 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
284
285
285 @command('annotate|blame',
286 @command('annotate|blame',
286 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
287 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
287 ('', 'follow', None,
288 ('', 'follow', None,
288 _('follow copies/renames and list the filename (DEPRECATED)')),
289 _('follow copies/renames and list the filename (DEPRECATED)')),
289 ('', 'no-follow', None, _("don't follow copies and renames")),
290 ('', 'no-follow', None, _("don't follow copies and renames")),
290 ('a', 'text', None, _('treat all files as text')),
291 ('a', 'text', None, _('treat all files as text')),
291 ('u', 'user', None, _('list the author (long with -v)')),
292 ('u', 'user', None, _('list the author (long with -v)')),
292 ('f', 'file', None, _('list the filename')),
293 ('f', 'file', None, _('list the filename')),
293 ('d', 'date', None, _('list the date (short with -q)')),
294 ('d', 'date', None, _('list the date (short with -q)')),
294 ('n', 'number', None, _('list the revision number (default)')),
295 ('n', 'number', None, _('list the revision number (default)')),
295 ('c', 'changeset', None, _('list the changeset')),
296 ('c', 'changeset', None, _('list the changeset')),
296 ('l', 'line-number', None, _('show line number at the first appearance')),
297 ('l', 'line-number', None, _('show line number at the first appearance')),
297 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
298 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
298 ] + diffwsopts + walkopts + formatteropts,
299 ] + diffwsopts + walkopts + formatteropts,
299 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
300 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
300 helpcategory=command.CATEGORY_FILE_CONTENTS,
301 helpcategory=command.CATEGORY_FILE_CONTENTS,
301 helpbasic=True, inferrepo=True)
302 helpbasic=True, inferrepo=True)
302 def annotate(ui, repo, *pats, **opts):
303 def annotate(ui, repo, *pats, **opts):
303 """show changeset information by line for each file
304 """show changeset information by line for each file
304
305
305 List changes in files, showing the revision id responsible for
306 List changes in files, showing the revision id responsible for
306 each line.
307 each line.
307
308
308 This command is useful for discovering when a change was made and
309 This command is useful for discovering when a change was made and
309 by whom.
310 by whom.
310
311
311 If you include --file, --user, or --date, the revision number is
312 If you include --file, --user, or --date, the revision number is
312 suppressed unless you also include --number.
313 suppressed unless you also include --number.
313
314
314 Without the -a/--text option, annotate will avoid processing files
315 Without the -a/--text option, annotate will avoid processing files
315 it detects as binary. With -a, annotate will annotate the file
316 it detects as binary. With -a, annotate will annotate the file
316 anyway, although the results will probably be neither useful
317 anyway, although the results will probably be neither useful
317 nor desirable.
318 nor desirable.
318
319
319 .. container:: verbose
320 .. container:: verbose
320
321
321 Template:
322 Template:
322
323
323 The following keywords are supported in addition to the common template
324 The following keywords are supported in addition to the common template
324 keywords and functions. See also :hg:`help templates`.
325 keywords and functions. See also :hg:`help templates`.
325
326
326 :lines: List of lines with annotation data.
327 :lines: List of lines with annotation data.
327 :path: String. Repository-absolute path of the specified file.
328 :path: String. Repository-absolute path of the specified file.
328
329
329 And each entry of ``{lines}`` provides the following sub-keywords in
330 And each entry of ``{lines}`` provides the following sub-keywords in
330 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
331 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
331
332
332 :line: String. Line content.
333 :line: String. Line content.
333 :lineno: Integer. Line number at that revision.
334 :lineno: Integer. Line number at that revision.
334 :path: String. Repository-absolute path of the file at that revision.
335 :path: String. Repository-absolute path of the file at that revision.
335
336
336 See :hg:`help templates.operators` for the list expansion syntax.
337 See :hg:`help templates.operators` for the list expansion syntax.
337
338
338 Returns 0 on success.
339 Returns 0 on success.
339 """
340 """
340 opts = pycompat.byteskwargs(opts)
341 opts = pycompat.byteskwargs(opts)
341 if not pats:
342 if not pats:
342 raise error.Abort(_('at least one filename or pattern is required'))
343 raise error.Abort(_('at least one filename or pattern is required'))
343
344
344 if opts.get('follow'):
345 if opts.get('follow'):
345 # --follow is deprecated and now just an alias for -f/--file
346 # --follow is deprecated and now just an alias for -f/--file
346 # to mimic the behavior of Mercurial before version 1.5
347 # to mimic the behavior of Mercurial before version 1.5
347 opts['file'] = True
348 opts['file'] = True
348
349
349 if (not opts.get('user') and not opts.get('changeset')
350 if (not opts.get('user') and not opts.get('changeset')
350 and not opts.get('date') and not opts.get('file')):
351 and not opts.get('date') and not opts.get('file')):
351 opts['number'] = True
352 opts['number'] = True
352
353
353 linenumber = opts.get('line_number') is not None
354 linenumber = opts.get('line_number') is not None
354 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
355 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
355 raise error.Abort(_('at least one of -n/-c is required for -l'))
356 raise error.Abort(_('at least one of -n/-c is required for -l'))
356
357
357 rev = opts.get('rev')
358 rev = opts.get('rev')
358 if rev:
359 if rev:
359 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
360 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
360 ctx = scmutil.revsingle(repo, rev)
361 ctx = scmutil.revsingle(repo, rev)
361
362
362 ui.pager('annotate')
363 ui.pager('annotate')
363 rootfm = ui.formatter('annotate', opts)
364 rootfm = ui.formatter('annotate', opts)
364 if ui.debugflag:
365 if ui.debugflag:
365 shorthex = pycompat.identity
366 shorthex = pycompat.identity
366 else:
367 else:
367 def shorthex(h):
368 def shorthex(h):
368 return h[:12]
369 return h[:12]
369 if ui.quiet:
370 if ui.quiet:
370 datefunc = dateutil.shortdate
371 datefunc = dateutil.shortdate
371 else:
372 else:
372 datefunc = dateutil.datestr
373 datefunc = dateutil.datestr
373 if ctx.rev() is None:
374 if ctx.rev() is None:
374 if opts.get('changeset'):
375 if opts.get('changeset'):
375 # omit "+" suffix which is appended to node hex
376 # omit "+" suffix which is appended to node hex
376 def formatrev(rev):
377 def formatrev(rev):
377 if rev == wdirrev:
378 if rev == wdirrev:
378 return '%d' % ctx.p1().rev()
379 return '%d' % ctx.p1().rev()
379 else:
380 else:
380 return '%d' % rev
381 return '%d' % rev
381 else:
382 else:
382 def formatrev(rev):
383 def formatrev(rev):
383 if rev == wdirrev:
384 if rev == wdirrev:
384 return '%d+' % ctx.p1().rev()
385 return '%d+' % ctx.p1().rev()
385 else:
386 else:
386 return '%d ' % rev
387 return '%d ' % rev
387 def formathex(h):
388 def formathex(h):
388 if h == wdirhex:
389 if h == wdirhex:
389 return '%s+' % shorthex(hex(ctx.p1().node()))
390 return '%s+' % shorthex(hex(ctx.p1().node()))
390 else:
391 else:
391 return '%s ' % shorthex(h)
392 return '%s ' % shorthex(h)
392 else:
393 else:
393 formatrev = b'%d'.__mod__
394 formatrev = b'%d'.__mod__
394 formathex = shorthex
395 formathex = shorthex
395
396
396 opmap = [
397 opmap = [
397 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
398 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
398 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
399 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
399 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
400 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
400 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
401 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
401 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
402 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
402 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
403 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
403 ]
404 ]
404 opnamemap = {
405 opnamemap = {
405 'rev': 'number',
406 'rev': 'number',
406 'node': 'changeset',
407 'node': 'changeset',
407 'path': 'file',
408 'path': 'file',
408 'lineno': 'line_number',
409 'lineno': 'line_number',
409 }
410 }
410
411
411 if rootfm.isplain():
412 if rootfm.isplain():
412 def makefunc(get, fmt):
413 def makefunc(get, fmt):
413 return lambda x: fmt(get(x))
414 return lambda x: fmt(get(x))
414 else:
415 else:
415 def makefunc(get, fmt):
416 def makefunc(get, fmt):
416 return get
417 return get
417 datahint = rootfm.datahint()
418 datahint = rootfm.datahint()
418 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
419 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
419 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
420 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
420 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
421 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
421 fields = ' '.join(fn for fn, sep, get, fmt in opmap
422 fields = ' '.join(fn for fn, sep, get, fmt in opmap
422 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
423 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
423
424
424 def bad(x, y):
425 def bad(x, y):
425 raise error.Abort("%s: %s" % (x, y))
426 raise error.Abort("%s: %s" % (x, y))
426
427
427 m = scmutil.match(ctx, pats, opts, badfn=bad)
428 m = scmutil.match(ctx, pats, opts, badfn=bad)
428
429
429 follow = not opts.get('no_follow')
430 follow = not opts.get('no_follow')
430 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
431 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
431 whitespace=True)
432 whitespace=True)
432 skiprevs = opts.get('skip')
433 skiprevs = opts.get('skip')
433 if skiprevs:
434 if skiprevs:
434 skiprevs = scmutil.revrange(repo, skiprevs)
435 skiprevs = scmutil.revrange(repo, skiprevs)
435
436
436 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
437 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
437 for abs in ctx.walk(m):
438 for abs in ctx.walk(m):
438 fctx = ctx[abs]
439 fctx = ctx[abs]
439 rootfm.startitem()
440 rootfm.startitem()
440 rootfm.data(path=abs)
441 rootfm.data(path=abs)
441 if not opts.get('text') and fctx.isbinary():
442 if not opts.get('text') and fctx.isbinary():
442 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
443 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
443 continue
444 continue
444
445
445 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
446 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
446 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
447 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
447 diffopts=diffopts)
448 diffopts=diffopts)
448 if not lines:
449 if not lines:
449 fm.end()
450 fm.end()
450 continue
451 continue
451 formats = []
452 formats = []
452 pieces = []
453 pieces = []
453
454
454 for f, sep in funcmap:
455 for f, sep in funcmap:
455 l = [f(n) for n in lines]
456 l = [f(n) for n in lines]
456 if fm.isplain():
457 if fm.isplain():
457 sizes = [encoding.colwidth(x) for x in l]
458 sizes = [encoding.colwidth(x) for x in l]
458 ml = max(sizes)
459 ml = max(sizes)
459 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
460 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
460 else:
461 else:
461 formats.append(['%s' for x in l])
462 formats.append(['%s' for x in l])
462 pieces.append(l)
463 pieces.append(l)
463
464
464 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
465 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
465 fm.startitem()
466 fm.startitem()
466 fm.context(fctx=n.fctx)
467 fm.context(fctx=n.fctx)
467 fm.write(fields, "".join(f), *p)
468 fm.write(fields, "".join(f), *p)
468 if n.skip:
469 if n.skip:
469 fmt = "* %s"
470 fmt = "* %s"
470 else:
471 else:
471 fmt = ": %s"
472 fmt = ": %s"
472 fm.write('line', fmt, n.text)
473 fm.write('line', fmt, n.text)
473
474
474 if not lines[-1].text.endswith('\n'):
475 if not lines[-1].text.endswith('\n'):
475 fm.plain('\n')
476 fm.plain('\n')
476 fm.end()
477 fm.end()
477
478
478 rootfm.end()
479 rootfm.end()
479
480
480 @command('archive',
481 @command('archive',
481 [('', 'no-decode', None, _('do not pass files through decoders')),
482 [('', 'no-decode', None, _('do not pass files through decoders')),
482 ('p', 'prefix', '', _('directory prefix for files in archive'),
483 ('p', 'prefix', '', _('directory prefix for files in archive'),
483 _('PREFIX')),
484 _('PREFIX')),
484 ('r', 'rev', '', _('revision to distribute'), _('REV')),
485 ('r', 'rev', '', _('revision to distribute'), _('REV')),
485 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
486 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
486 ] + subrepoopts + walkopts,
487 ] + subrepoopts + walkopts,
487 _('[OPTION]... DEST'),
488 _('[OPTION]... DEST'),
488 helpcategory=command.CATEGORY_IMPORT_EXPORT)
489 helpcategory=command.CATEGORY_IMPORT_EXPORT)
489 def archive(ui, repo, dest, **opts):
490 def archive(ui, repo, dest, **opts):
490 '''create an unversioned archive of a repository revision
491 '''create an unversioned archive of a repository revision
491
492
492 By default, the revision used is the parent of the working
493 By default, the revision used is the parent of the working
493 directory; use -r/--rev to specify a different revision.
494 directory; use -r/--rev to specify a different revision.
494
495
495 The archive type is automatically detected based on file
496 The archive type is automatically detected based on file
496 extension (to override, use -t/--type).
497 extension (to override, use -t/--type).
497
498
498 .. container:: verbose
499 .. container:: verbose
499
500
500 Examples:
501 Examples:
501
502
502 - create a zip file containing the 1.0 release::
503 - create a zip file containing the 1.0 release::
503
504
504 hg archive -r 1.0 project-1.0.zip
505 hg archive -r 1.0 project-1.0.zip
505
506
506 - create a tarball excluding .hg files::
507 - create a tarball excluding .hg files::
507
508
508 hg archive project.tar.gz -X ".hg*"
509 hg archive project.tar.gz -X ".hg*"
509
510
510 Valid types are:
511 Valid types are:
511
512
512 :``files``: a directory full of files (default)
513 :``files``: a directory full of files (default)
513 :``tar``: tar archive, uncompressed
514 :``tar``: tar archive, uncompressed
514 :``tbz2``: tar archive, compressed using bzip2
515 :``tbz2``: tar archive, compressed using bzip2
515 :``tgz``: tar archive, compressed using gzip
516 :``tgz``: tar archive, compressed using gzip
516 :``uzip``: zip archive, uncompressed
517 :``uzip``: zip archive, uncompressed
517 :``zip``: zip archive, compressed using deflate
518 :``zip``: zip archive, compressed using deflate
518
519
519 The exact name of the destination archive or directory is given
520 The exact name of the destination archive or directory is given
520 using a format string; see :hg:`help export` for details.
521 using a format string; see :hg:`help export` for details.
521
522
522 Each member added to an archive file has a directory prefix
523 Each member added to an archive file has a directory prefix
523 prepended. Use -p/--prefix to specify a format string for the
524 prepended. Use -p/--prefix to specify a format string for the
524 prefix. The default is the basename of the archive, with suffixes
525 prefix. The default is the basename of the archive, with suffixes
525 removed.
526 removed.
526
527
527 Returns 0 on success.
528 Returns 0 on success.
528 '''
529 '''
529
530
530 opts = pycompat.byteskwargs(opts)
531 opts = pycompat.byteskwargs(opts)
531 rev = opts.get('rev')
532 rev = opts.get('rev')
532 if rev:
533 if rev:
533 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
534 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
534 ctx = scmutil.revsingle(repo, rev)
535 ctx = scmutil.revsingle(repo, rev)
535 if not ctx:
536 if not ctx:
536 raise error.Abort(_('no working directory: please specify a revision'))
537 raise error.Abort(_('no working directory: please specify a revision'))
537 node = ctx.node()
538 node = ctx.node()
538 dest = cmdutil.makefilename(ctx, dest)
539 dest = cmdutil.makefilename(ctx, dest)
539 if os.path.realpath(dest) == repo.root:
540 if os.path.realpath(dest) == repo.root:
540 raise error.Abort(_('repository root cannot be destination'))
541 raise error.Abort(_('repository root cannot be destination'))
541
542
542 kind = opts.get('type') or archival.guesskind(dest) or 'files'
543 kind = opts.get('type') or archival.guesskind(dest) or 'files'
543 prefix = opts.get('prefix')
544 prefix = opts.get('prefix')
544
545
545 if dest == '-':
546 if dest == '-':
546 if kind == 'files':
547 if kind == 'files':
547 raise error.Abort(_('cannot archive plain files to stdout'))
548 raise error.Abort(_('cannot archive plain files to stdout'))
548 dest = cmdutil.makefileobj(ctx, dest)
549 dest = cmdutil.makefileobj(ctx, dest)
549 if not prefix:
550 if not prefix:
550 prefix = os.path.basename(repo.root) + '-%h'
551 prefix = os.path.basename(repo.root) + '-%h'
551
552
552 prefix = cmdutil.makefilename(ctx, prefix)
553 prefix = cmdutil.makefilename(ctx, prefix)
553 match = scmutil.match(ctx, [], opts)
554 match = scmutil.match(ctx, [], opts)
554 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
555 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
555 match, prefix, subrepos=opts.get('subrepos'))
556 match, prefix, subrepos=opts.get('subrepos'))
556
557
557 @command('backout',
558 @command('backout',
558 [('', 'merge', None, _('merge with old dirstate parent after backout')),
559 [('', 'merge', None, _('merge with old dirstate parent after backout')),
559 ('', 'commit', None,
560 ('', 'commit', None,
560 _('commit if no conflicts were encountered (DEPRECATED)')),
561 _('commit if no conflicts were encountered (DEPRECATED)')),
561 ('', 'no-commit', None, _('do not commit')),
562 ('', 'no-commit', None, _('do not commit')),
562 ('', 'parent', '',
563 ('', 'parent', '',
563 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
564 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
564 ('r', 'rev', '', _('revision to backout'), _('REV')),
565 ('r', 'rev', '', _('revision to backout'), _('REV')),
565 ('e', 'edit', False, _('invoke editor on commit messages')),
566 ('e', 'edit', False, _('invoke editor on commit messages')),
566 ] + mergetoolopts + walkopts + commitopts + commitopts2,
567 ] + mergetoolopts + walkopts + commitopts + commitopts2,
567 _('[OPTION]... [-r] REV'),
568 _('[OPTION]... [-r] REV'),
568 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
569 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
569 def backout(ui, repo, node=None, rev=None, **opts):
570 def backout(ui, repo, node=None, rev=None, **opts):
570 '''reverse effect of earlier changeset
571 '''reverse effect of earlier changeset
571
572
572 Prepare a new changeset with the effect of REV undone in the
573 Prepare a new changeset with the effect of REV undone in the
573 current working directory. If no conflicts were encountered,
574 current working directory. If no conflicts were encountered,
574 it will be committed immediately.
575 it will be committed immediately.
575
576
576 If REV is the parent of the working directory, then this new changeset
577 If REV is the parent of the working directory, then this new changeset
577 is committed automatically (unless --no-commit is specified).
578 is committed automatically (unless --no-commit is specified).
578
579
579 .. note::
580 .. note::
580
581
581 :hg:`backout` cannot be used to fix either an unwanted or
582 :hg:`backout` cannot be used to fix either an unwanted or
582 incorrect merge.
583 incorrect merge.
583
584
584 .. container:: verbose
585 .. container:: verbose
585
586
586 Examples:
587 Examples:
587
588
588 - Reverse the effect of the parent of the working directory.
589 - Reverse the effect of the parent of the working directory.
589 This backout will be committed immediately::
590 This backout will be committed immediately::
590
591
591 hg backout -r .
592 hg backout -r .
592
593
593 - Reverse the effect of previous bad revision 23::
594 - Reverse the effect of previous bad revision 23::
594
595
595 hg backout -r 23
596 hg backout -r 23
596
597
597 - Reverse the effect of previous bad revision 23 and
598 - Reverse the effect of previous bad revision 23 and
598 leave changes uncommitted::
599 leave changes uncommitted::
599
600
600 hg backout -r 23 --no-commit
601 hg backout -r 23 --no-commit
601 hg commit -m "Backout revision 23"
602 hg commit -m "Backout revision 23"
602
603
603 By default, the pending changeset will have one parent,
604 By default, the pending changeset will have one parent,
604 maintaining a linear history. With --merge, the pending
605 maintaining a linear history. With --merge, the pending
605 changeset will instead have two parents: the old parent of the
606 changeset will instead have two parents: the old parent of the
606 working directory and a new child of REV that simply undoes REV.
607 working directory and a new child of REV that simply undoes REV.
607
608
608 Before version 1.7, the behavior without --merge was equivalent
609 Before version 1.7, the behavior without --merge was equivalent
609 to specifying --merge followed by :hg:`update --clean .` to
610 to specifying --merge followed by :hg:`update --clean .` to
610 cancel the merge and leave the child of REV as a head to be
611 cancel the merge and leave the child of REV as a head to be
611 merged separately.
612 merged separately.
612
613
613 See :hg:`help dates` for a list of formats valid for -d/--date.
614 See :hg:`help dates` for a list of formats valid for -d/--date.
614
615
615 See :hg:`help revert` for a way to restore files to the state
616 See :hg:`help revert` for a way to restore files to the state
616 of another revision.
617 of another revision.
617
618
618 Returns 0 on success, 1 if nothing to backout or there are unresolved
619 Returns 0 on success, 1 if nothing to backout or there are unresolved
619 files.
620 files.
620 '''
621 '''
621 with repo.wlock(), repo.lock():
622 with repo.wlock(), repo.lock():
622 return _dobackout(ui, repo, node, rev, **opts)
623 return _dobackout(ui, repo, node, rev, **opts)
623
624
624 def _dobackout(ui, repo, node=None, rev=None, **opts):
625 def _dobackout(ui, repo, node=None, rev=None, **opts):
625 opts = pycompat.byteskwargs(opts)
626 opts = pycompat.byteskwargs(opts)
626 if opts.get('commit') and opts.get('no_commit'):
627 if opts.get('commit') and opts.get('no_commit'):
627 raise error.Abort(_("cannot use --commit with --no-commit"))
628 raise error.Abort(_("cannot use --commit with --no-commit"))
628 if opts.get('merge') and opts.get('no_commit'):
629 if opts.get('merge') and opts.get('no_commit'):
629 raise error.Abort(_("cannot use --merge with --no-commit"))
630 raise error.Abort(_("cannot use --merge with --no-commit"))
630
631
631 if rev and node:
632 if rev and node:
632 raise error.Abort(_("please specify just one revision"))
633 raise error.Abort(_("please specify just one revision"))
633
634
634 if not rev:
635 if not rev:
635 rev = node
636 rev = node
636
637
637 if not rev:
638 if not rev:
638 raise error.Abort(_("please specify a revision to backout"))
639 raise error.Abort(_("please specify a revision to backout"))
639
640
640 date = opts.get('date')
641 date = opts.get('date')
641 if date:
642 if date:
642 opts['date'] = dateutil.parsedate(date)
643 opts['date'] = dateutil.parsedate(date)
643
644
644 cmdutil.checkunfinished(repo)
645 cmdutil.checkunfinished(repo)
645 cmdutil.bailifchanged(repo)
646 cmdutil.bailifchanged(repo)
646 node = scmutil.revsingle(repo, rev).node()
647 node = scmutil.revsingle(repo, rev).node()
647
648
648 op1, op2 = repo.dirstate.parents()
649 op1, op2 = repo.dirstate.parents()
649 if not repo.changelog.isancestor(node, op1):
650 if not repo.changelog.isancestor(node, op1):
650 raise error.Abort(_('cannot backout change that is not an ancestor'))
651 raise error.Abort(_('cannot backout change that is not an ancestor'))
651
652
652 p1, p2 = repo.changelog.parents(node)
653 p1, p2 = repo.changelog.parents(node)
653 if p1 == nullid:
654 if p1 == nullid:
654 raise error.Abort(_('cannot backout a change with no parents'))
655 raise error.Abort(_('cannot backout a change with no parents'))
655 if p2 != nullid:
656 if p2 != nullid:
656 if not opts.get('parent'):
657 if not opts.get('parent'):
657 raise error.Abort(_('cannot backout a merge changeset'))
658 raise error.Abort(_('cannot backout a merge changeset'))
658 p = repo.lookup(opts['parent'])
659 p = repo.lookup(opts['parent'])
659 if p not in (p1, p2):
660 if p not in (p1, p2):
660 raise error.Abort(_('%s is not a parent of %s') %
661 raise error.Abort(_('%s is not a parent of %s') %
661 (short(p), short(node)))
662 (short(p), short(node)))
662 parent = p
663 parent = p
663 else:
664 else:
664 if opts.get('parent'):
665 if opts.get('parent'):
665 raise error.Abort(_('cannot use --parent on non-merge changeset'))
666 raise error.Abort(_('cannot use --parent on non-merge changeset'))
666 parent = p1
667 parent = p1
667
668
668 # the backout should appear on the same branch
669 # the backout should appear on the same branch
669 branch = repo.dirstate.branch()
670 branch = repo.dirstate.branch()
670 bheads = repo.branchheads(branch)
671 bheads = repo.branchheads(branch)
671 rctx = scmutil.revsingle(repo, hex(parent))
672 rctx = scmutil.revsingle(repo, hex(parent))
672 if not opts.get('merge') and op1 != node:
673 if not opts.get('merge') and op1 != node:
673 with dirstateguard.dirstateguard(repo, 'backout'):
674 with dirstateguard.dirstateguard(repo, 'backout'):
674 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
675 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
675 with ui.configoverride(overrides, 'backout'):
676 with ui.configoverride(overrides, 'backout'):
676 stats = mergemod.update(repo, parent, branchmerge=True,
677 stats = mergemod.update(repo, parent, branchmerge=True,
677 force=True, ancestor=node,
678 force=True, ancestor=node,
678 mergeancestor=False)
679 mergeancestor=False)
679 repo.setparents(op1, op2)
680 repo.setparents(op1, op2)
680 hg._showstats(repo, stats)
681 hg._showstats(repo, stats)
681 if stats.unresolvedcount:
682 if stats.unresolvedcount:
682 repo.ui.status(_("use 'hg resolve' to retry unresolved "
683 repo.ui.status(_("use 'hg resolve' to retry unresolved "
683 "file merges\n"))
684 "file merges\n"))
684 return 1
685 return 1
685 else:
686 else:
686 hg.clean(repo, node, show_stats=False)
687 hg.clean(repo, node, show_stats=False)
687 repo.dirstate.setbranch(branch)
688 repo.dirstate.setbranch(branch)
688 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
689 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
689
690
690 if opts.get('no_commit'):
691 if opts.get('no_commit'):
691 msg = _("changeset %s backed out, "
692 msg = _("changeset %s backed out, "
692 "don't forget to commit.\n")
693 "don't forget to commit.\n")
693 ui.status(msg % short(node))
694 ui.status(msg % short(node))
694 return 0
695 return 0
695
696
696 def commitfunc(ui, repo, message, match, opts):
697 def commitfunc(ui, repo, message, match, opts):
697 editform = 'backout'
698 editform = 'backout'
698 e = cmdutil.getcommiteditor(editform=editform,
699 e = cmdutil.getcommiteditor(editform=editform,
699 **pycompat.strkwargs(opts))
700 **pycompat.strkwargs(opts))
700 if not message:
701 if not message:
701 # we don't translate commit messages
702 # we don't translate commit messages
702 message = "Backed out changeset %s" % short(node)
703 message = "Backed out changeset %s" % short(node)
703 e = cmdutil.getcommiteditor(edit=True, editform=editform)
704 e = cmdutil.getcommiteditor(edit=True, editform=editform)
704 return repo.commit(message, opts.get('user'), opts.get('date'),
705 return repo.commit(message, opts.get('user'), opts.get('date'),
705 match, editor=e)
706 match, editor=e)
706 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
707 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
707 if not newnode:
708 if not newnode:
708 ui.status(_("nothing changed\n"))
709 ui.status(_("nothing changed\n"))
709 return 1
710 return 1
710 cmdutil.commitstatus(repo, newnode, branch, bheads)
711 cmdutil.commitstatus(repo, newnode, branch, bheads)
711
712
712 def nice(node):
713 def nice(node):
713 return '%d:%s' % (repo.changelog.rev(node), short(node))
714 return '%d:%s' % (repo.changelog.rev(node), short(node))
714 ui.status(_('changeset %s backs out changeset %s\n') %
715 ui.status(_('changeset %s backs out changeset %s\n') %
715 (nice(repo.changelog.tip()), nice(node)))
716 (nice(repo.changelog.tip()), nice(node)))
716 if opts.get('merge') and op1 != node:
717 if opts.get('merge') and op1 != node:
717 hg.clean(repo, op1, show_stats=False)
718 hg.clean(repo, op1, show_stats=False)
718 ui.status(_('merging with changeset %s\n')
719 ui.status(_('merging with changeset %s\n')
719 % nice(repo.changelog.tip()))
720 % nice(repo.changelog.tip()))
720 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
721 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
721 with ui.configoverride(overrides, 'backout'):
722 with ui.configoverride(overrides, 'backout'):
722 return hg.merge(repo, hex(repo.changelog.tip()))
723 return hg.merge(repo, hex(repo.changelog.tip()))
723 return 0
724 return 0
724
725
725 @command('bisect',
726 @command('bisect',
726 [('r', 'reset', False, _('reset bisect state')),
727 [('r', 'reset', False, _('reset bisect state')),
727 ('g', 'good', False, _('mark changeset good')),
728 ('g', 'good', False, _('mark changeset good')),
728 ('b', 'bad', False, _('mark changeset bad')),
729 ('b', 'bad', False, _('mark changeset bad')),
729 ('s', 'skip', False, _('skip testing changeset')),
730 ('s', 'skip', False, _('skip testing changeset')),
730 ('e', 'extend', False, _('extend the bisect range')),
731 ('e', 'extend', False, _('extend the bisect range')),
731 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
732 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
732 ('U', 'noupdate', False, _('do not update to target'))],
733 ('U', 'noupdate', False, _('do not update to target'))],
733 _("[-gbsr] [-U] [-c CMD] [REV]"),
734 _("[-gbsr] [-U] [-c CMD] [REV]"),
734 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
735 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
735 def bisect(ui, repo, rev=None, extra=None, command=None,
736 def bisect(ui, repo, rev=None, extra=None, command=None,
736 reset=None, good=None, bad=None, skip=None, extend=None,
737 reset=None, good=None, bad=None, skip=None, extend=None,
737 noupdate=None):
738 noupdate=None):
738 """subdivision search of changesets
739 """subdivision search of changesets
739
740
740 This command helps to find changesets which introduce problems. To
741 This command helps to find changesets which introduce problems. To
741 use, mark the earliest changeset you know exhibits the problem as
742 use, mark the earliest changeset you know exhibits the problem as
742 bad, then mark the latest changeset which is free from the problem
743 bad, then mark the latest changeset which is free from the problem
743 as good. Bisect will update your working directory to a revision
744 as good. Bisect will update your working directory to a revision
744 for testing (unless the -U/--noupdate option is specified). Once
745 for testing (unless the -U/--noupdate option is specified). Once
745 you have performed tests, mark the working directory as good or
746 you have performed tests, mark the working directory as good or
746 bad, and bisect will either update to another candidate changeset
747 bad, and bisect will either update to another candidate changeset
747 or announce that it has found the bad revision.
748 or announce that it has found the bad revision.
748
749
749 As a shortcut, you can also use the revision argument to mark a
750 As a shortcut, you can also use the revision argument to mark a
750 revision as good or bad without checking it out first.
751 revision as good or bad without checking it out first.
751
752
752 If you supply a command, it will be used for automatic bisection.
753 If you supply a command, it will be used for automatic bisection.
753 The environment variable HG_NODE will contain the ID of the
754 The environment variable HG_NODE will contain the ID of the
754 changeset being tested. The exit status of the command will be
755 changeset being tested. The exit status of the command will be
755 used to mark revisions as good or bad: status 0 means good, 125
756 used to mark revisions as good or bad: status 0 means good, 125
756 means to skip the revision, 127 (command not found) will abort the
757 means to skip the revision, 127 (command not found) will abort the
757 bisection, and any other non-zero exit status means the revision
758 bisection, and any other non-zero exit status means the revision
758 is bad.
759 is bad.
759
760
760 .. container:: verbose
761 .. container:: verbose
761
762
762 Some examples:
763 Some examples:
763
764
764 - start a bisection with known bad revision 34, and good revision 12::
765 - start a bisection with known bad revision 34, and good revision 12::
765
766
766 hg bisect --bad 34
767 hg bisect --bad 34
767 hg bisect --good 12
768 hg bisect --good 12
768
769
769 - advance the current bisection by marking current revision as good or
770 - advance the current bisection by marking current revision as good or
770 bad::
771 bad::
771
772
772 hg bisect --good
773 hg bisect --good
773 hg bisect --bad
774 hg bisect --bad
774
775
775 - mark the current revision, or a known revision, to be skipped (e.g. if
776 - mark the current revision, or a known revision, to be skipped (e.g. if
776 that revision is not usable because of another issue)::
777 that revision is not usable because of another issue)::
777
778
778 hg bisect --skip
779 hg bisect --skip
779 hg bisect --skip 23
780 hg bisect --skip 23
780
781
781 - skip all revisions that do not touch directories ``foo`` or ``bar``::
782 - skip all revisions that do not touch directories ``foo`` or ``bar``::
782
783
783 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
784 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
784
785
785 - forget the current bisection::
786 - forget the current bisection::
786
787
787 hg bisect --reset
788 hg bisect --reset
788
789
789 - use 'make && make tests' to automatically find the first broken
790 - use 'make && make tests' to automatically find the first broken
790 revision::
791 revision::
791
792
792 hg bisect --reset
793 hg bisect --reset
793 hg bisect --bad 34
794 hg bisect --bad 34
794 hg bisect --good 12
795 hg bisect --good 12
795 hg bisect --command "make && make tests"
796 hg bisect --command "make && make tests"
796
797
797 - see all changesets whose states are already known in the current
798 - see all changesets whose states are already known in the current
798 bisection::
799 bisection::
799
800
800 hg log -r "bisect(pruned)"
801 hg log -r "bisect(pruned)"
801
802
802 - see the changeset currently being bisected (especially useful
803 - see the changeset currently being bisected (especially useful
803 if running with -U/--noupdate)::
804 if running with -U/--noupdate)::
804
805
805 hg log -r "bisect(current)"
806 hg log -r "bisect(current)"
806
807
807 - see all changesets that took part in the current bisection::
808 - see all changesets that took part in the current bisection::
808
809
809 hg log -r "bisect(range)"
810 hg log -r "bisect(range)"
810
811
811 - you can even get a nice graph::
812 - you can even get a nice graph::
812
813
813 hg log --graph -r "bisect(range)"
814 hg log --graph -r "bisect(range)"
814
815
815 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
816 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
816
817
817 Returns 0 on success.
818 Returns 0 on success.
818 """
819 """
819 # backward compatibility
820 # backward compatibility
820 if rev in "good bad reset init".split():
821 if rev in "good bad reset init".split():
821 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
822 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
822 cmd, rev, extra = rev, extra, None
823 cmd, rev, extra = rev, extra, None
823 if cmd == "good":
824 if cmd == "good":
824 good = True
825 good = True
825 elif cmd == "bad":
826 elif cmd == "bad":
826 bad = True
827 bad = True
827 else:
828 else:
828 reset = True
829 reset = True
829 elif extra:
830 elif extra:
830 raise error.Abort(_('incompatible arguments'))
831 raise error.Abort(_('incompatible arguments'))
831
832
832 incompatibles = {
833 incompatibles = {
833 '--bad': bad,
834 '--bad': bad,
834 '--command': bool(command),
835 '--command': bool(command),
835 '--extend': extend,
836 '--extend': extend,
836 '--good': good,
837 '--good': good,
837 '--reset': reset,
838 '--reset': reset,
838 '--skip': skip,
839 '--skip': skip,
839 }
840 }
840
841
841 enabled = [x for x in incompatibles if incompatibles[x]]
842 enabled = [x for x in incompatibles if incompatibles[x]]
842
843
843 if len(enabled) > 1:
844 if len(enabled) > 1:
844 raise error.Abort(_('%s and %s are incompatible') %
845 raise error.Abort(_('%s and %s are incompatible') %
845 tuple(sorted(enabled)[0:2]))
846 tuple(sorted(enabled)[0:2]))
846
847
847 if reset:
848 if reset:
848 hbisect.resetstate(repo)
849 hbisect.resetstate(repo)
849 return
850 return
850
851
851 state = hbisect.load_state(repo)
852 state = hbisect.load_state(repo)
852
853
853 # update state
854 # update state
854 if good or bad or skip:
855 if good or bad or skip:
855 if rev:
856 if rev:
856 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
857 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
857 else:
858 else:
858 nodes = [repo.lookup('.')]
859 nodes = [repo.lookup('.')]
859 if good:
860 if good:
860 state['good'] += nodes
861 state['good'] += nodes
861 elif bad:
862 elif bad:
862 state['bad'] += nodes
863 state['bad'] += nodes
863 elif skip:
864 elif skip:
864 state['skip'] += nodes
865 state['skip'] += nodes
865 hbisect.save_state(repo, state)
866 hbisect.save_state(repo, state)
866 if not (state['good'] and state['bad']):
867 if not (state['good'] and state['bad']):
867 return
868 return
868
869
869 def mayupdate(repo, node, show_stats=True):
870 def mayupdate(repo, node, show_stats=True):
870 """common used update sequence"""
871 """common used update sequence"""
871 if noupdate:
872 if noupdate:
872 return
873 return
873 cmdutil.checkunfinished(repo)
874 cmdutil.checkunfinished(repo)
874 cmdutil.bailifchanged(repo)
875 cmdutil.bailifchanged(repo)
875 return hg.clean(repo, node, show_stats=show_stats)
876 return hg.clean(repo, node, show_stats=show_stats)
876
877
877 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
878 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
878
879
879 if command:
880 if command:
880 changesets = 1
881 changesets = 1
881 if noupdate:
882 if noupdate:
882 try:
883 try:
883 node = state['current'][0]
884 node = state['current'][0]
884 except LookupError:
885 except LookupError:
885 raise error.Abort(_('current bisect revision is unknown - '
886 raise error.Abort(_('current bisect revision is unknown - '
886 'start a new bisect to fix'))
887 'start a new bisect to fix'))
887 else:
888 else:
888 node, p2 = repo.dirstate.parents()
889 node, p2 = repo.dirstate.parents()
889 if p2 != nullid:
890 if p2 != nullid:
890 raise error.Abort(_('current bisect revision is a merge'))
891 raise error.Abort(_('current bisect revision is a merge'))
891 if rev:
892 if rev:
892 node = repo[scmutil.revsingle(repo, rev, node)].node()
893 node = repo[scmutil.revsingle(repo, rev, node)].node()
893 try:
894 try:
894 while changesets:
895 while changesets:
895 # update state
896 # update state
896 state['current'] = [node]
897 state['current'] = [node]
897 hbisect.save_state(repo, state)
898 hbisect.save_state(repo, state)
898 status = ui.system(command, environ={'HG_NODE': hex(node)},
899 status = ui.system(command, environ={'HG_NODE': hex(node)},
899 blockedtag='bisect_check')
900 blockedtag='bisect_check')
900 if status == 125:
901 if status == 125:
901 transition = "skip"
902 transition = "skip"
902 elif status == 0:
903 elif status == 0:
903 transition = "good"
904 transition = "good"
904 # status < 0 means process was killed
905 # status < 0 means process was killed
905 elif status == 127:
906 elif status == 127:
906 raise error.Abort(_("failed to execute %s") % command)
907 raise error.Abort(_("failed to execute %s") % command)
907 elif status < 0:
908 elif status < 0:
908 raise error.Abort(_("%s killed") % command)
909 raise error.Abort(_("%s killed") % command)
909 else:
910 else:
910 transition = "bad"
911 transition = "bad"
911 state[transition].append(node)
912 state[transition].append(node)
912 ctx = repo[node]
913 ctx = repo[node]
913 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
914 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
914 transition))
915 transition))
915 hbisect.checkstate(state)
916 hbisect.checkstate(state)
916 # bisect
917 # bisect
917 nodes, changesets, bgood = hbisect.bisect(repo, state)
918 nodes, changesets, bgood = hbisect.bisect(repo, state)
918 # update to next check
919 # update to next check
919 node = nodes[0]
920 node = nodes[0]
920 mayupdate(repo, node, show_stats=False)
921 mayupdate(repo, node, show_stats=False)
921 finally:
922 finally:
922 state['current'] = [node]
923 state['current'] = [node]
923 hbisect.save_state(repo, state)
924 hbisect.save_state(repo, state)
924 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
925 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
925 return
926 return
926
927
927 hbisect.checkstate(state)
928 hbisect.checkstate(state)
928
929
929 # actually bisect
930 # actually bisect
930 nodes, changesets, good = hbisect.bisect(repo, state)
931 nodes, changesets, good = hbisect.bisect(repo, state)
931 if extend:
932 if extend:
932 if not changesets:
933 if not changesets:
933 extendnode = hbisect.extendrange(repo, state, nodes, good)
934 extendnode = hbisect.extendrange(repo, state, nodes, good)
934 if extendnode is not None:
935 if extendnode is not None:
935 ui.write(_("Extending search to changeset %d:%s\n")
936 ui.write(_("Extending search to changeset %d:%s\n")
936 % (extendnode.rev(), extendnode))
937 % (extendnode.rev(), extendnode))
937 state['current'] = [extendnode.node()]
938 state['current'] = [extendnode.node()]
938 hbisect.save_state(repo, state)
939 hbisect.save_state(repo, state)
939 return mayupdate(repo, extendnode.node())
940 return mayupdate(repo, extendnode.node())
940 raise error.Abort(_("nothing to extend"))
941 raise error.Abort(_("nothing to extend"))
941
942
942 if changesets == 0:
943 if changesets == 0:
943 hbisect.printresult(ui, repo, state, displayer, nodes, good)
944 hbisect.printresult(ui, repo, state, displayer, nodes, good)
944 else:
945 else:
945 assert len(nodes) == 1 # only a single node can be tested next
946 assert len(nodes) == 1 # only a single node can be tested next
946 node = nodes[0]
947 node = nodes[0]
947 # compute the approximate number of remaining tests
948 # compute the approximate number of remaining tests
948 tests, size = 0, 2
949 tests, size = 0, 2
949 while size <= changesets:
950 while size <= changesets:
950 tests, size = tests + 1, size * 2
951 tests, size = tests + 1, size * 2
951 rev = repo.changelog.rev(node)
952 rev = repo.changelog.rev(node)
952 ui.write(_("Testing changeset %d:%s "
953 ui.write(_("Testing changeset %d:%s "
953 "(%d changesets remaining, ~%d tests)\n")
954 "(%d changesets remaining, ~%d tests)\n")
954 % (rev, short(node), changesets, tests))
955 % (rev, short(node), changesets, tests))
955 state['current'] = [node]
956 state['current'] = [node]
956 hbisect.save_state(repo, state)
957 hbisect.save_state(repo, state)
957 return mayupdate(repo, node)
958 return mayupdate(repo, node)
958
959
959 @command('bookmarks|bookmark',
960 @command('bookmarks|bookmark',
960 [('f', 'force', False, _('force')),
961 [('f', 'force', False, _('force')),
961 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
962 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
962 ('d', 'delete', False, _('delete a given bookmark')),
963 ('d', 'delete', False, _('delete a given bookmark')),
963 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
964 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
964 ('i', 'inactive', False, _('mark a bookmark inactive')),
965 ('i', 'inactive', False, _('mark a bookmark inactive')),
965 ('l', 'list', False, _('list existing bookmarks')),
966 ('l', 'list', False, _('list existing bookmarks')),
966 ] + formatteropts,
967 ] + formatteropts,
967 _('hg bookmarks [OPTIONS]... [NAME]...'),
968 _('hg bookmarks [OPTIONS]... [NAME]...'),
968 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
969 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
969 def bookmark(ui, repo, *names, **opts):
970 def bookmark(ui, repo, *names, **opts):
970 '''create a new bookmark or list existing bookmarks
971 '''create a new bookmark or list existing bookmarks
971
972
972 Bookmarks are labels on changesets to help track lines of development.
973 Bookmarks are labels on changesets to help track lines of development.
973 Bookmarks are unversioned and can be moved, renamed and deleted.
974 Bookmarks are unversioned and can be moved, renamed and deleted.
974 Deleting or moving a bookmark has no effect on the associated changesets.
975 Deleting or moving a bookmark has no effect on the associated changesets.
975
976
976 Creating or updating to a bookmark causes it to be marked as 'active'.
977 Creating or updating to a bookmark causes it to be marked as 'active'.
977 The active bookmark is indicated with a '*'.
978 The active bookmark is indicated with a '*'.
978 When a commit is made, the active bookmark will advance to the new commit.
979 When a commit is made, the active bookmark will advance to the new commit.
979 A plain :hg:`update` will also advance an active bookmark, if possible.
980 A plain :hg:`update` will also advance an active bookmark, if possible.
980 Updating away from a bookmark will cause it to be deactivated.
981 Updating away from a bookmark will cause it to be deactivated.
981
982
982 Bookmarks can be pushed and pulled between repositories (see
983 Bookmarks can be pushed and pulled between repositories (see
983 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
984 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
984 diverged, a new 'divergent bookmark' of the form 'name@path' will
985 diverged, a new 'divergent bookmark' of the form 'name@path' will
985 be created. Using :hg:`merge` will resolve the divergence.
986 be created. Using :hg:`merge` will resolve the divergence.
986
987
987 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
988 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
988 the active bookmark's name.
989 the active bookmark's name.
989
990
990 A bookmark named '@' has the special property that :hg:`clone` will
991 A bookmark named '@' has the special property that :hg:`clone` will
991 check it out by default if it exists.
992 check it out by default if it exists.
992
993
993 .. container:: verbose
994 .. container:: verbose
994
995
995 Template:
996 Template:
996
997
997 The following keywords are supported in addition to the common template
998 The following keywords are supported in addition to the common template
998 keywords and functions such as ``{bookmark}``. See also
999 keywords and functions such as ``{bookmark}``. See also
999 :hg:`help templates`.
1000 :hg:`help templates`.
1000
1001
1001 :active: Boolean. True if the bookmark is active.
1002 :active: Boolean. True if the bookmark is active.
1002
1003
1003 Examples:
1004 Examples:
1004
1005
1005 - create an active bookmark for a new line of development::
1006 - create an active bookmark for a new line of development::
1006
1007
1007 hg book new-feature
1008 hg book new-feature
1008
1009
1009 - create an inactive bookmark as a place marker::
1010 - create an inactive bookmark as a place marker::
1010
1011
1011 hg book -i reviewed
1012 hg book -i reviewed
1012
1013
1013 - create an inactive bookmark on another changeset::
1014 - create an inactive bookmark on another changeset::
1014
1015
1015 hg book -r .^ tested
1016 hg book -r .^ tested
1016
1017
1017 - rename bookmark turkey to dinner::
1018 - rename bookmark turkey to dinner::
1018
1019
1019 hg book -m turkey dinner
1020 hg book -m turkey dinner
1020
1021
1021 - move the '@' bookmark from another branch::
1022 - move the '@' bookmark from another branch::
1022
1023
1023 hg book -f @
1024 hg book -f @
1024
1025
1025 - print only the active bookmark name::
1026 - print only the active bookmark name::
1026
1027
1027 hg book -ql .
1028 hg book -ql .
1028 '''
1029 '''
1029 opts = pycompat.byteskwargs(opts)
1030 opts = pycompat.byteskwargs(opts)
1030 force = opts.get('force')
1031 force = opts.get('force')
1031 rev = opts.get('rev')
1032 rev = opts.get('rev')
1032 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1033 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1033
1034
1034 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1035 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1035 if len(selactions) > 1:
1036 if len(selactions) > 1:
1036 raise error.Abort(_('--%s and --%s are incompatible')
1037 raise error.Abort(_('--%s and --%s are incompatible')
1037 % tuple(selactions[:2]))
1038 % tuple(selactions[:2]))
1038 if selactions:
1039 if selactions:
1039 action = selactions[0]
1040 action = selactions[0]
1040 elif names or rev:
1041 elif names or rev:
1041 action = 'add'
1042 action = 'add'
1042 elif inactive:
1043 elif inactive:
1043 action = 'inactive' # meaning deactivate
1044 action = 'inactive' # meaning deactivate
1044 else:
1045 else:
1045 action = 'list'
1046 action = 'list'
1046
1047
1047 if rev and action in {'delete', 'rename', 'list'}:
1048 if rev and action in {'delete', 'rename', 'list'}:
1048 raise error.Abort(_("--rev is incompatible with --%s") % action)
1049 raise error.Abort(_("--rev is incompatible with --%s") % action)
1049 if inactive and action in {'delete', 'list'}:
1050 if inactive and action in {'delete', 'list'}:
1050 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1051 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1051 if not names and action in {'add', 'delete'}:
1052 if not names and action in {'add', 'delete'}:
1052 raise error.Abort(_("bookmark name required"))
1053 raise error.Abort(_("bookmark name required"))
1053
1054
1054 if action in {'add', 'delete', 'rename', 'inactive'}:
1055 if action in {'add', 'delete', 'rename', 'inactive'}:
1055 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1056 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1056 if action == 'delete':
1057 if action == 'delete':
1057 names = pycompat.maplist(repo._bookmarks.expandname, names)
1058 names = pycompat.maplist(repo._bookmarks.expandname, names)
1058 bookmarks.delete(repo, tr, names)
1059 bookmarks.delete(repo, tr, names)
1059 elif action == 'rename':
1060 elif action == 'rename':
1060 if not names:
1061 if not names:
1061 raise error.Abort(_("new bookmark name required"))
1062 raise error.Abort(_("new bookmark name required"))
1062 elif len(names) > 1:
1063 elif len(names) > 1:
1063 raise error.Abort(_("only one new bookmark name allowed"))
1064 raise error.Abort(_("only one new bookmark name allowed"))
1064 oldname = repo._bookmarks.expandname(opts['rename'])
1065 oldname = repo._bookmarks.expandname(opts['rename'])
1065 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1066 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1066 elif action == 'add':
1067 elif action == 'add':
1067 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1068 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1068 elif action == 'inactive':
1069 elif action == 'inactive':
1069 if len(repo._bookmarks) == 0:
1070 if len(repo._bookmarks) == 0:
1070 ui.status(_("no bookmarks set\n"))
1071 ui.status(_("no bookmarks set\n"))
1071 elif not repo._activebookmark:
1072 elif not repo._activebookmark:
1072 ui.status(_("no active bookmark\n"))
1073 ui.status(_("no active bookmark\n"))
1073 else:
1074 else:
1074 bookmarks.deactivate(repo)
1075 bookmarks.deactivate(repo)
1075 elif action == 'list':
1076 elif action == 'list':
1076 names = pycompat.maplist(repo._bookmarks.expandname, names)
1077 names = pycompat.maplist(repo._bookmarks.expandname, names)
1077 with ui.formatter('bookmarks', opts) as fm:
1078 with ui.formatter('bookmarks', opts) as fm:
1078 bookmarks.printbookmarks(ui, repo, fm, names)
1079 bookmarks.printbookmarks(ui, repo, fm, names)
1079 else:
1080 else:
1080 raise error.ProgrammingError('invalid action: %s' % action)
1081 raise error.ProgrammingError('invalid action: %s' % action)
1081
1082
1082 @command('branch',
1083 @command('branch',
1083 [('f', 'force', None,
1084 [('f', 'force', None,
1084 _('set branch name even if it shadows an existing branch')),
1085 _('set branch name even if it shadows an existing branch')),
1085 ('C', 'clean', None, _('reset branch name to parent branch name')),
1086 ('C', 'clean', None, _('reset branch name to parent branch name')),
1086 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1087 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1087 ],
1088 ],
1088 _('[-fC] [NAME]'),
1089 _('[-fC] [NAME]'),
1089 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1090 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1090 def branch(ui, repo, label=None, **opts):
1091 def branch(ui, repo, label=None, **opts):
1091 """set or show the current branch name
1092 """set or show the current branch name
1092
1093
1093 .. note::
1094 .. note::
1094
1095
1095 Branch names are permanent and global. Use :hg:`bookmark` to create a
1096 Branch names are permanent and global. Use :hg:`bookmark` to create a
1096 light-weight bookmark instead. See :hg:`help glossary` for more
1097 light-weight bookmark instead. See :hg:`help glossary` for more
1097 information about named branches and bookmarks.
1098 information about named branches and bookmarks.
1098
1099
1099 With no argument, show the current branch name. With one argument,
1100 With no argument, show the current branch name. With one argument,
1100 set the working directory branch name (the branch will not exist
1101 set the working directory branch name (the branch will not exist
1101 in the repository until the next commit). Standard practice
1102 in the repository until the next commit). Standard practice
1102 recommends that primary development take place on the 'default'
1103 recommends that primary development take place on the 'default'
1103 branch.
1104 branch.
1104
1105
1105 Unless -f/--force is specified, branch will not let you set a
1106 Unless -f/--force is specified, branch will not let you set a
1106 branch name that already exists.
1107 branch name that already exists.
1107
1108
1108 Use -C/--clean to reset the working directory branch to that of
1109 Use -C/--clean to reset the working directory branch to that of
1109 the parent of the working directory, negating a previous branch
1110 the parent of the working directory, negating a previous branch
1110 change.
1111 change.
1111
1112
1112 Use the command :hg:`update` to switch to an existing branch. Use
1113 Use the command :hg:`update` to switch to an existing branch. Use
1113 :hg:`commit --close-branch` to mark this branch head as closed.
1114 :hg:`commit --close-branch` to mark this branch head as closed.
1114 When all heads of a branch are closed, the branch will be
1115 When all heads of a branch are closed, the branch will be
1115 considered closed.
1116 considered closed.
1116
1117
1117 Returns 0 on success.
1118 Returns 0 on success.
1118 """
1119 """
1119 opts = pycompat.byteskwargs(opts)
1120 opts = pycompat.byteskwargs(opts)
1120 revs = opts.get('rev')
1121 revs = opts.get('rev')
1121 if label:
1122 if label:
1122 label = label.strip()
1123 label = label.strip()
1123
1124
1124 if not opts.get('clean') and not label:
1125 if not opts.get('clean') and not label:
1125 if revs:
1126 if revs:
1126 raise error.Abort(_("no branch name specified for the revisions"))
1127 raise error.Abort(_("no branch name specified for the revisions"))
1127 ui.write("%s\n" % repo.dirstate.branch())
1128 ui.write("%s\n" % repo.dirstate.branch())
1128 return
1129 return
1129
1130
1130 with repo.wlock():
1131 with repo.wlock():
1131 if opts.get('clean'):
1132 if opts.get('clean'):
1132 label = repo['.'].branch()
1133 label = repo['.'].branch()
1133 repo.dirstate.setbranch(label)
1134 repo.dirstate.setbranch(label)
1134 ui.status(_('reset working directory to branch %s\n') % label)
1135 ui.status(_('reset working directory to branch %s\n') % label)
1135 elif label:
1136 elif label:
1136
1137
1137 scmutil.checknewlabel(repo, label, 'branch')
1138 scmutil.checknewlabel(repo, label, 'branch')
1138 if revs:
1139 if revs:
1139 return cmdutil.changebranch(ui, repo, revs, label)
1140 return cmdutil.changebranch(ui, repo, revs, label)
1140
1141
1141 if not opts.get('force') and label in repo.branchmap():
1142 if not opts.get('force') and label in repo.branchmap():
1142 if label not in [p.branch() for p in repo[None].parents()]:
1143 if label not in [p.branch() for p in repo[None].parents()]:
1143 raise error.Abort(_('a branch of the same name already'
1144 raise error.Abort(_('a branch of the same name already'
1144 ' exists'),
1145 ' exists'),
1145 # i18n: "it" refers to an existing branch
1146 # i18n: "it" refers to an existing branch
1146 hint=_("use 'hg update' to switch to it"))
1147 hint=_("use 'hg update' to switch to it"))
1147
1148
1148 repo.dirstate.setbranch(label)
1149 repo.dirstate.setbranch(label)
1149 ui.status(_('marked working directory as branch %s\n') % label)
1150 ui.status(_('marked working directory as branch %s\n') % label)
1150
1151
1151 # find any open named branches aside from default
1152 # find any open named branches aside from default
1152 for n, h, t, c in repo.branchmap().iterbranches():
1153 for n, h, t, c in repo.branchmap().iterbranches():
1153 if n != "default" and not c:
1154 if n != "default" and not c:
1154 return 0
1155 return 0
1155 ui.status(_('(branches are permanent and global, '
1156 ui.status(_('(branches are permanent and global, '
1156 'did you want a bookmark?)\n'))
1157 'did you want a bookmark?)\n'))
1157
1158
1158 @command('branches',
1159 @command('branches',
1159 [('a', 'active', False,
1160 [('a', 'active', False,
1160 _('show only branches that have unmerged heads (DEPRECATED)')),
1161 _('show only branches that have unmerged heads (DEPRECATED)')),
1161 ('c', 'closed', False, _('show normal and closed branches')),
1162 ('c', 'closed', False, _('show normal and closed branches')),
1162 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1163 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1163 ] + formatteropts,
1164 ] + formatteropts,
1164 _('[-c]'),
1165 _('[-c]'),
1165 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1166 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1166 intents={INTENT_READONLY})
1167 intents={INTENT_READONLY})
1167 def branches(ui, repo, active=False, closed=False, **opts):
1168 def branches(ui, repo, active=False, closed=False, **opts):
1168 """list repository named branches
1169 """list repository named branches
1169
1170
1170 List the repository's named branches, indicating which ones are
1171 List the repository's named branches, indicating which ones are
1171 inactive. If -c/--closed is specified, also list branches which have
1172 inactive. If -c/--closed is specified, also list branches which have
1172 been marked closed (see :hg:`commit --close-branch`).
1173 been marked closed (see :hg:`commit --close-branch`).
1173
1174
1174 Use the command :hg:`update` to switch to an existing branch.
1175 Use the command :hg:`update` to switch to an existing branch.
1175
1176
1176 .. container:: verbose
1177 .. container:: verbose
1177
1178
1178 Template:
1179 Template:
1179
1180
1180 The following keywords are supported in addition to the common template
1181 The following keywords are supported in addition to the common template
1181 keywords and functions such as ``{branch}``. See also
1182 keywords and functions such as ``{branch}``. See also
1182 :hg:`help templates`.
1183 :hg:`help templates`.
1183
1184
1184 :active: Boolean. True if the branch is active.
1185 :active: Boolean. True if the branch is active.
1185 :closed: Boolean. True if the branch is closed.
1186 :closed: Boolean. True if the branch is closed.
1186 :current: Boolean. True if it is the current branch.
1187 :current: Boolean. True if it is the current branch.
1187
1188
1188 Returns 0.
1189 Returns 0.
1189 """
1190 """
1190
1191
1191 opts = pycompat.byteskwargs(opts)
1192 opts = pycompat.byteskwargs(opts)
1192 revs = opts.get('rev')
1193 revs = opts.get('rev')
1193 selectedbranches = None
1194 selectedbranches = None
1194 if revs:
1195 if revs:
1195 revs = scmutil.revrange(repo, revs)
1196 revs = scmutil.revrange(repo, revs)
1196 getbi = repo.revbranchcache().branchinfo
1197 getbi = repo.revbranchcache().branchinfo
1197 selectedbranches = {getbi(r)[0] for r in revs}
1198 selectedbranches = {getbi(r)[0] for r in revs}
1198
1199
1199 ui.pager('branches')
1200 ui.pager('branches')
1200 fm = ui.formatter('branches', opts)
1201 fm = ui.formatter('branches', opts)
1201 hexfunc = fm.hexfunc
1202 hexfunc = fm.hexfunc
1202
1203
1203 allheads = set(repo.heads())
1204 allheads = set(repo.heads())
1204 branches = []
1205 branches = []
1205 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1206 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1206 if selectedbranches is not None and tag not in selectedbranches:
1207 if selectedbranches is not None and tag not in selectedbranches:
1207 continue
1208 continue
1208 isactive = False
1209 isactive = False
1209 if not isclosed:
1210 if not isclosed:
1210 openheads = set(repo.branchmap().iteropen(heads))
1211 openheads = set(repo.branchmap().iteropen(heads))
1211 isactive = bool(openheads & allheads)
1212 isactive = bool(openheads & allheads)
1212 branches.append((tag, repo[tip], isactive, not isclosed))
1213 branches.append((tag, repo[tip], isactive, not isclosed))
1213 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1214 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1214 reverse=True)
1215 reverse=True)
1215
1216
1216 for tag, ctx, isactive, isopen in branches:
1217 for tag, ctx, isactive, isopen in branches:
1217 if active and not isactive:
1218 if active and not isactive:
1218 continue
1219 continue
1219 if isactive:
1220 if isactive:
1220 label = 'branches.active'
1221 label = 'branches.active'
1221 notice = ''
1222 notice = ''
1222 elif not isopen:
1223 elif not isopen:
1223 if not closed:
1224 if not closed:
1224 continue
1225 continue
1225 label = 'branches.closed'
1226 label = 'branches.closed'
1226 notice = _(' (closed)')
1227 notice = _(' (closed)')
1227 else:
1228 else:
1228 label = 'branches.inactive'
1229 label = 'branches.inactive'
1229 notice = _(' (inactive)')
1230 notice = _(' (inactive)')
1230 current = (tag == repo.dirstate.branch())
1231 current = (tag == repo.dirstate.branch())
1231 if current:
1232 if current:
1232 label = 'branches.current'
1233 label = 'branches.current'
1233
1234
1234 fm.startitem()
1235 fm.startitem()
1235 fm.write('branch', '%s', tag, label=label)
1236 fm.write('branch', '%s', tag, label=label)
1236 rev = ctx.rev()
1237 rev = ctx.rev()
1237 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1238 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1238 fmt = ' ' * padsize + ' %d:%s'
1239 fmt = ' ' * padsize + ' %d:%s'
1239 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1240 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1240 label='log.changeset changeset.%s' % ctx.phasestr())
1241 label='log.changeset changeset.%s' % ctx.phasestr())
1241 fm.context(ctx=ctx)
1242 fm.context(ctx=ctx)
1242 fm.data(active=isactive, closed=not isopen, current=current)
1243 fm.data(active=isactive, closed=not isopen, current=current)
1243 if not ui.quiet:
1244 if not ui.quiet:
1244 fm.plain(notice)
1245 fm.plain(notice)
1245 fm.plain('\n')
1246 fm.plain('\n')
1246 fm.end()
1247 fm.end()
1247
1248
1248 @command('bundle',
1249 @command('bundle',
1249 [('f', 'force', None, _('run even when the destination is unrelated')),
1250 [('f', 'force', None, _('run even when the destination is unrelated')),
1250 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1251 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1251 _('REV')),
1252 _('REV')),
1252 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1253 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1253 _('BRANCH')),
1254 _('BRANCH')),
1254 ('', 'base', [],
1255 ('', 'base', [],
1255 _('a base changeset assumed to be available at the destination'),
1256 _('a base changeset assumed to be available at the destination'),
1256 _('REV')),
1257 _('REV')),
1257 ('a', 'all', None, _('bundle all changesets in the repository')),
1258 ('a', 'all', None, _('bundle all changesets in the repository')),
1258 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1259 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1259 ] + remoteopts,
1260 ] + remoteopts,
1260 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1261 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1261 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1262 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1262 def bundle(ui, repo, fname, dest=None, **opts):
1263 def bundle(ui, repo, fname, dest=None, **opts):
1263 """create a bundle file
1264 """create a bundle file
1264
1265
1265 Generate a bundle file containing data to be transferred to another
1266 Generate a bundle file containing data to be transferred to another
1266 repository.
1267 repository.
1267
1268
1268 To create a bundle containing all changesets, use -a/--all
1269 To create a bundle containing all changesets, use -a/--all
1269 (or --base null). Otherwise, hg assumes the destination will have
1270 (or --base null). Otherwise, hg assumes the destination will have
1270 all the nodes you specify with --base parameters. Otherwise, hg
1271 all the nodes you specify with --base parameters. Otherwise, hg
1271 will assume the repository has all the nodes in destination, or
1272 will assume the repository has all the nodes in destination, or
1272 default-push/default if no destination is specified, where destination
1273 default-push/default if no destination is specified, where destination
1273 is the repository you provide through DEST option.
1274 is the repository you provide through DEST option.
1274
1275
1275 You can change bundle format with the -t/--type option. See
1276 You can change bundle format with the -t/--type option. See
1276 :hg:`help bundlespec` for documentation on this format. By default,
1277 :hg:`help bundlespec` for documentation on this format. By default,
1277 the most appropriate format is used and compression defaults to
1278 the most appropriate format is used and compression defaults to
1278 bzip2.
1279 bzip2.
1279
1280
1280 The bundle file can then be transferred using conventional means
1281 The bundle file can then be transferred using conventional means
1281 and applied to another repository with the unbundle or pull
1282 and applied to another repository with the unbundle or pull
1282 command. This is useful when direct push and pull are not
1283 command. This is useful when direct push and pull are not
1283 available or when exporting an entire repository is undesirable.
1284 available or when exporting an entire repository is undesirable.
1284
1285
1285 Applying bundles preserves all changeset contents including
1286 Applying bundles preserves all changeset contents including
1286 permissions, copy/rename information, and revision history.
1287 permissions, copy/rename information, and revision history.
1287
1288
1288 Returns 0 on success, 1 if no changes found.
1289 Returns 0 on success, 1 if no changes found.
1289 """
1290 """
1290 opts = pycompat.byteskwargs(opts)
1291 opts = pycompat.byteskwargs(opts)
1291 revs = None
1292 revs = None
1292 if 'rev' in opts:
1293 if 'rev' in opts:
1293 revstrings = opts['rev']
1294 revstrings = opts['rev']
1294 revs = scmutil.revrange(repo, revstrings)
1295 revs = scmutil.revrange(repo, revstrings)
1295 if revstrings and not revs:
1296 if revstrings and not revs:
1296 raise error.Abort(_('no commits to bundle'))
1297 raise error.Abort(_('no commits to bundle'))
1297
1298
1298 bundletype = opts.get('type', 'bzip2').lower()
1299 bundletype = opts.get('type', 'bzip2').lower()
1299 try:
1300 try:
1300 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1301 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1301 except error.UnsupportedBundleSpecification as e:
1302 except error.UnsupportedBundleSpecification as e:
1302 raise error.Abort(pycompat.bytestr(e),
1303 raise error.Abort(pycompat.bytestr(e),
1303 hint=_("see 'hg help bundlespec' for supported "
1304 hint=_("see 'hg help bundlespec' for supported "
1304 "values for --type"))
1305 "values for --type"))
1305 cgversion = bundlespec.contentopts["cg.version"]
1306 cgversion = bundlespec.contentopts["cg.version"]
1306
1307
1307 # Packed bundles are a pseudo bundle format for now.
1308 # Packed bundles are a pseudo bundle format for now.
1308 if cgversion == 's1':
1309 if cgversion == 's1':
1309 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1310 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1310 hint=_("use 'hg debugcreatestreamclonebundle'"))
1311 hint=_("use 'hg debugcreatestreamclonebundle'"))
1311
1312
1312 if opts.get('all'):
1313 if opts.get('all'):
1313 if dest:
1314 if dest:
1314 raise error.Abort(_("--all is incompatible with specifying "
1315 raise error.Abort(_("--all is incompatible with specifying "
1315 "a destination"))
1316 "a destination"))
1316 if opts.get('base'):
1317 if opts.get('base'):
1317 ui.warn(_("ignoring --base because --all was specified\n"))
1318 ui.warn(_("ignoring --base because --all was specified\n"))
1318 base = [nullrev]
1319 base = [nullrev]
1319 else:
1320 else:
1320 base = scmutil.revrange(repo, opts.get('base'))
1321 base = scmutil.revrange(repo, opts.get('base'))
1321 if cgversion not in changegroup.supportedoutgoingversions(repo):
1322 if cgversion not in changegroup.supportedoutgoingversions(repo):
1322 raise error.Abort(_("repository does not support bundle version %s") %
1323 raise error.Abort(_("repository does not support bundle version %s") %
1323 cgversion)
1324 cgversion)
1324
1325
1325 if base:
1326 if base:
1326 if dest:
1327 if dest:
1327 raise error.Abort(_("--base is incompatible with specifying "
1328 raise error.Abort(_("--base is incompatible with specifying "
1328 "a destination"))
1329 "a destination"))
1329 common = [repo[rev].node() for rev in base]
1330 common = [repo[rev].node() for rev in base]
1330 heads = [repo[r].node() for r in revs] if revs else None
1331 heads = [repo[r].node() for r in revs] if revs else None
1331 outgoing = discovery.outgoing(repo, common, heads)
1332 outgoing = discovery.outgoing(repo, common, heads)
1332 else:
1333 else:
1333 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1334 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1334 dest, branches = hg.parseurl(dest, opts.get('branch'))
1335 dest, branches = hg.parseurl(dest, opts.get('branch'))
1335 other = hg.peer(repo, opts, dest)
1336 other = hg.peer(repo, opts, dest)
1336 revs = [repo[r].hex() for r in revs]
1337 revs = [repo[r].hex() for r in revs]
1337 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1338 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1338 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1339 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1339 outgoing = discovery.findcommonoutgoing(repo, other,
1340 outgoing = discovery.findcommonoutgoing(repo, other,
1340 onlyheads=heads,
1341 onlyheads=heads,
1341 force=opts.get('force'),
1342 force=opts.get('force'),
1342 portable=True)
1343 portable=True)
1343
1344
1344 if not outgoing.missing:
1345 if not outgoing.missing:
1345 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1346 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1346 return 1
1347 return 1
1347
1348
1348 if cgversion == '01': #bundle1
1349 if cgversion == '01': #bundle1
1349 bversion = 'HG10' + bundlespec.wirecompression
1350 bversion = 'HG10' + bundlespec.wirecompression
1350 bcompression = None
1351 bcompression = None
1351 elif cgversion in ('02', '03'):
1352 elif cgversion in ('02', '03'):
1352 bversion = 'HG20'
1353 bversion = 'HG20'
1353 bcompression = bundlespec.wirecompression
1354 bcompression = bundlespec.wirecompression
1354 else:
1355 else:
1355 raise error.ProgrammingError(
1356 raise error.ProgrammingError(
1356 'bundle: unexpected changegroup version %s' % cgversion)
1357 'bundle: unexpected changegroup version %s' % cgversion)
1357
1358
1358 # TODO compression options should be derived from bundlespec parsing.
1359 # TODO compression options should be derived from bundlespec parsing.
1359 # This is a temporary hack to allow adjusting bundle compression
1360 # This is a temporary hack to allow adjusting bundle compression
1360 # level without a) formalizing the bundlespec changes to declare it
1361 # level without a) formalizing the bundlespec changes to declare it
1361 # b) introducing a command flag.
1362 # b) introducing a command flag.
1362 compopts = {}
1363 compopts = {}
1363 complevel = ui.configint('experimental',
1364 complevel = ui.configint('experimental',
1364 'bundlecomplevel.' + bundlespec.compression)
1365 'bundlecomplevel.' + bundlespec.compression)
1365 if complevel is None:
1366 if complevel is None:
1366 complevel = ui.configint('experimental', 'bundlecomplevel')
1367 complevel = ui.configint('experimental', 'bundlecomplevel')
1367 if complevel is not None:
1368 if complevel is not None:
1368 compopts['level'] = complevel
1369 compopts['level'] = complevel
1369
1370
1370 # Allow overriding the bundling of obsmarker in phases through
1371 # Allow overriding the bundling of obsmarker in phases through
1371 # configuration while we don't have a bundle version that include them
1372 # configuration while we don't have a bundle version that include them
1372 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1373 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1373 bundlespec.contentopts['obsolescence'] = True
1374 bundlespec.contentopts['obsolescence'] = True
1374 if repo.ui.configbool('experimental', 'bundle-phases'):
1375 if repo.ui.configbool('experimental', 'bundle-phases'):
1375 bundlespec.contentopts['phases'] = True
1376 bundlespec.contentopts['phases'] = True
1376
1377
1377 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1378 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1378 bundlespec.contentopts, compression=bcompression,
1379 bundlespec.contentopts, compression=bcompression,
1379 compopts=compopts)
1380 compopts=compopts)
1380
1381
1381 @command('cat',
1382 @command('cat',
1382 [('o', 'output', '',
1383 [('o', 'output', '',
1383 _('print output to file with formatted name'), _('FORMAT')),
1384 _('print output to file with formatted name'), _('FORMAT')),
1384 ('r', 'rev', '', _('print the given revision'), _('REV')),
1385 ('r', 'rev', '', _('print the given revision'), _('REV')),
1385 ('', 'decode', None, _('apply any matching decode filter')),
1386 ('', 'decode', None, _('apply any matching decode filter')),
1386 ] + walkopts + formatteropts,
1387 ] + walkopts + formatteropts,
1387 _('[OPTION]... FILE...'),
1388 _('[OPTION]... FILE...'),
1388 helpcategory=command.CATEGORY_FILE_CONTENTS,
1389 helpcategory=command.CATEGORY_FILE_CONTENTS,
1389 inferrepo=True,
1390 inferrepo=True,
1390 intents={INTENT_READONLY})
1391 intents={INTENT_READONLY})
1391 def cat(ui, repo, file1, *pats, **opts):
1392 def cat(ui, repo, file1, *pats, **opts):
1392 """output the current or given revision of files
1393 """output the current or given revision of files
1393
1394
1394 Print the specified files as they were at the given revision. If
1395 Print the specified files as they were at the given revision. If
1395 no revision is given, the parent of the working directory is used.
1396 no revision is given, the parent of the working directory is used.
1396
1397
1397 Output may be to a file, in which case the name of the file is
1398 Output may be to a file, in which case the name of the file is
1398 given using a template string. See :hg:`help templates`. In addition
1399 given using a template string. See :hg:`help templates`. In addition
1399 to the common template keywords, the following formatting rules are
1400 to the common template keywords, the following formatting rules are
1400 supported:
1401 supported:
1401
1402
1402 :``%%``: literal "%" character
1403 :``%%``: literal "%" character
1403 :``%s``: basename of file being printed
1404 :``%s``: basename of file being printed
1404 :``%d``: dirname of file being printed, or '.' if in repository root
1405 :``%d``: dirname of file being printed, or '.' if in repository root
1405 :``%p``: root-relative path name of file being printed
1406 :``%p``: root-relative path name of file being printed
1406 :``%H``: changeset hash (40 hexadecimal digits)
1407 :``%H``: changeset hash (40 hexadecimal digits)
1407 :``%R``: changeset revision number
1408 :``%R``: changeset revision number
1408 :``%h``: short-form changeset hash (12 hexadecimal digits)
1409 :``%h``: short-form changeset hash (12 hexadecimal digits)
1409 :``%r``: zero-padded changeset revision number
1410 :``%r``: zero-padded changeset revision number
1410 :``%b``: basename of the exporting repository
1411 :``%b``: basename of the exporting repository
1411 :``\\``: literal "\\" character
1412 :``\\``: literal "\\" character
1412
1413
1413 .. container:: verbose
1414 .. container:: verbose
1414
1415
1415 Template:
1416 Template:
1416
1417
1417 The following keywords are supported in addition to the common template
1418 The following keywords are supported in addition to the common template
1418 keywords and functions. See also :hg:`help templates`.
1419 keywords and functions. See also :hg:`help templates`.
1419
1420
1420 :data: String. File content.
1421 :data: String. File content.
1421 :path: String. Repository-absolute path of the file.
1422 :path: String. Repository-absolute path of the file.
1422
1423
1423 Returns 0 on success.
1424 Returns 0 on success.
1424 """
1425 """
1425 opts = pycompat.byteskwargs(opts)
1426 opts = pycompat.byteskwargs(opts)
1426 rev = opts.get('rev')
1427 rev = opts.get('rev')
1427 if rev:
1428 if rev:
1428 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1429 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1429 ctx = scmutil.revsingle(repo, rev)
1430 ctx = scmutil.revsingle(repo, rev)
1430 m = scmutil.match(ctx, (file1,) + pats, opts)
1431 m = scmutil.match(ctx, (file1,) + pats, opts)
1431 fntemplate = opts.pop('output', '')
1432 fntemplate = opts.pop('output', '')
1432 if cmdutil.isstdiofilename(fntemplate):
1433 if cmdutil.isstdiofilename(fntemplate):
1433 fntemplate = ''
1434 fntemplate = ''
1434
1435
1435 if fntemplate:
1436 if fntemplate:
1436 fm = formatter.nullformatter(ui, 'cat', opts)
1437 fm = formatter.nullformatter(ui, 'cat', opts)
1437 else:
1438 else:
1438 ui.pager('cat')
1439 ui.pager('cat')
1439 fm = ui.formatter('cat', opts)
1440 fm = ui.formatter('cat', opts)
1440 with fm:
1441 with fm:
1441 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1442 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1442 **pycompat.strkwargs(opts))
1443 **pycompat.strkwargs(opts))
1443
1444
1444 @command('clone',
1445 @command('clone',
1445 [('U', 'noupdate', None, _('the clone will include an empty working '
1446 [('U', 'noupdate', None, _('the clone will include an empty working '
1446 'directory (only a repository)')),
1447 'directory (only a repository)')),
1447 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1448 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1448 _('REV')),
1449 _('REV')),
1449 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1450 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1450 ' and its ancestors'), _('REV')),
1451 ' and its ancestors'), _('REV')),
1451 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1452 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1452 ' changesets and their ancestors'), _('BRANCH')),
1453 ' changesets and their ancestors'), _('BRANCH')),
1453 ('', 'pull', None, _('use pull protocol to copy metadata')),
1454 ('', 'pull', None, _('use pull protocol to copy metadata')),
1454 ('', 'uncompressed', None,
1455 ('', 'uncompressed', None,
1455 _('an alias to --stream (DEPRECATED)')),
1456 _('an alias to --stream (DEPRECATED)')),
1456 ('', 'stream', None,
1457 ('', 'stream', None,
1457 _('clone with minimal data processing')),
1458 _('clone with minimal data processing')),
1458 ] + remoteopts,
1459 ] + remoteopts,
1459 _('[OPTION]... SOURCE [DEST]'),
1460 _('[OPTION]... SOURCE [DEST]'),
1460 helpcategory=command.CATEGORY_REPO_CREATION,
1461 helpcategory=command.CATEGORY_REPO_CREATION,
1461 helpbasic=True, norepo=True)
1462 helpbasic=True, norepo=True)
1462 def clone(ui, source, dest=None, **opts):
1463 def clone(ui, source, dest=None, **opts):
1463 """make a copy of an existing repository
1464 """make a copy of an existing repository
1464
1465
1465 Create a copy of an existing repository in a new directory.
1466 Create a copy of an existing repository in a new directory.
1466
1467
1467 If no destination directory name is specified, it defaults to the
1468 If no destination directory name is specified, it defaults to the
1468 basename of the source.
1469 basename of the source.
1469
1470
1470 The location of the source is added to the new repository's
1471 The location of the source is added to the new repository's
1471 ``.hg/hgrc`` file, as the default to be used for future pulls.
1472 ``.hg/hgrc`` file, as the default to be used for future pulls.
1472
1473
1473 Only local paths and ``ssh://`` URLs are supported as
1474 Only local paths and ``ssh://`` URLs are supported as
1474 destinations. For ``ssh://`` destinations, no working directory or
1475 destinations. For ``ssh://`` destinations, no working directory or
1475 ``.hg/hgrc`` will be created on the remote side.
1476 ``.hg/hgrc`` will be created on the remote side.
1476
1477
1477 If the source repository has a bookmark called '@' set, that
1478 If the source repository has a bookmark called '@' set, that
1478 revision will be checked out in the new repository by default.
1479 revision will be checked out in the new repository by default.
1479
1480
1480 To check out a particular version, use -u/--update, or
1481 To check out a particular version, use -u/--update, or
1481 -U/--noupdate to create a clone with no working directory.
1482 -U/--noupdate to create a clone with no working directory.
1482
1483
1483 To pull only a subset of changesets, specify one or more revisions
1484 To pull only a subset of changesets, specify one or more revisions
1484 identifiers with -r/--rev or branches with -b/--branch. The
1485 identifiers with -r/--rev or branches with -b/--branch. The
1485 resulting clone will contain only the specified changesets and
1486 resulting clone will contain only the specified changesets and
1486 their ancestors. These options (or 'clone src#rev dest') imply
1487 their ancestors. These options (or 'clone src#rev dest') imply
1487 --pull, even for local source repositories.
1488 --pull, even for local source repositories.
1488
1489
1489 In normal clone mode, the remote normalizes repository data into a common
1490 In normal clone mode, the remote normalizes repository data into a common
1490 exchange format and the receiving end translates this data into its local
1491 exchange format and the receiving end translates this data into its local
1491 storage format. --stream activates a different clone mode that essentially
1492 storage format. --stream activates a different clone mode that essentially
1492 copies repository files from the remote with minimal data processing. This
1493 copies repository files from the remote with minimal data processing. This
1493 significantly reduces the CPU cost of a clone both remotely and locally.
1494 significantly reduces the CPU cost of a clone both remotely and locally.
1494 However, it often increases the transferred data size by 30-40%. This can
1495 However, it often increases the transferred data size by 30-40%. This can
1495 result in substantially faster clones where I/O throughput is plentiful,
1496 result in substantially faster clones where I/O throughput is plentiful,
1496 especially for larger repositories. A side-effect of --stream clones is
1497 especially for larger repositories. A side-effect of --stream clones is
1497 that storage settings and requirements on the remote are applied locally:
1498 that storage settings and requirements on the remote are applied locally:
1498 a modern client may inherit legacy or inefficient storage used by the
1499 a modern client may inherit legacy or inefficient storage used by the
1499 remote or a legacy Mercurial client may not be able to clone from a
1500 remote or a legacy Mercurial client may not be able to clone from a
1500 modern Mercurial remote.
1501 modern Mercurial remote.
1501
1502
1502 .. note::
1503 .. note::
1503
1504
1504 Specifying a tag will include the tagged changeset but not the
1505 Specifying a tag will include the tagged changeset but not the
1505 changeset containing the tag.
1506 changeset containing the tag.
1506
1507
1507 .. container:: verbose
1508 .. container:: verbose
1508
1509
1509 For efficiency, hardlinks are used for cloning whenever the
1510 For efficiency, hardlinks are used for cloning whenever the
1510 source and destination are on the same filesystem (note this
1511 source and destination are on the same filesystem (note this
1511 applies only to the repository data, not to the working
1512 applies only to the repository data, not to the working
1512 directory). Some filesystems, such as AFS, implement hardlinking
1513 directory). Some filesystems, such as AFS, implement hardlinking
1513 incorrectly, but do not report errors. In these cases, use the
1514 incorrectly, but do not report errors. In these cases, use the
1514 --pull option to avoid hardlinking.
1515 --pull option to avoid hardlinking.
1515
1516
1516 Mercurial will update the working directory to the first applicable
1517 Mercurial will update the working directory to the first applicable
1517 revision from this list:
1518 revision from this list:
1518
1519
1519 a) null if -U or the source repository has no changesets
1520 a) null if -U or the source repository has no changesets
1520 b) if -u . and the source repository is local, the first parent of
1521 b) if -u . and the source repository is local, the first parent of
1521 the source repository's working directory
1522 the source repository's working directory
1522 c) the changeset specified with -u (if a branch name, this means the
1523 c) the changeset specified with -u (if a branch name, this means the
1523 latest head of that branch)
1524 latest head of that branch)
1524 d) the changeset specified with -r
1525 d) the changeset specified with -r
1525 e) the tipmost head specified with -b
1526 e) the tipmost head specified with -b
1526 f) the tipmost head specified with the url#branch source syntax
1527 f) the tipmost head specified with the url#branch source syntax
1527 g) the revision marked with the '@' bookmark, if present
1528 g) the revision marked with the '@' bookmark, if present
1528 h) the tipmost head of the default branch
1529 h) the tipmost head of the default branch
1529 i) tip
1530 i) tip
1530
1531
1531 When cloning from servers that support it, Mercurial may fetch
1532 When cloning from servers that support it, Mercurial may fetch
1532 pre-generated data from a server-advertised URL or inline from the
1533 pre-generated data from a server-advertised URL or inline from the
1533 same stream. When this is done, hooks operating on incoming changesets
1534 same stream. When this is done, hooks operating on incoming changesets
1534 and changegroups may fire more than once, once for each pre-generated
1535 and changegroups may fire more than once, once for each pre-generated
1535 bundle and as well as for any additional remaining data. In addition,
1536 bundle and as well as for any additional remaining data. In addition,
1536 if an error occurs, the repository may be rolled back to a partial
1537 if an error occurs, the repository may be rolled back to a partial
1537 clone. This behavior may change in future releases.
1538 clone. This behavior may change in future releases.
1538 See :hg:`help -e clonebundles` for more.
1539 See :hg:`help -e clonebundles` for more.
1539
1540
1540 Examples:
1541 Examples:
1541
1542
1542 - clone a remote repository to a new directory named hg/::
1543 - clone a remote repository to a new directory named hg/::
1543
1544
1544 hg clone https://www.mercurial-scm.org/repo/hg/
1545 hg clone https://www.mercurial-scm.org/repo/hg/
1545
1546
1546 - create a lightweight local clone::
1547 - create a lightweight local clone::
1547
1548
1548 hg clone project/ project-feature/
1549 hg clone project/ project-feature/
1549
1550
1550 - clone from an absolute path on an ssh server (note double-slash)::
1551 - clone from an absolute path on an ssh server (note double-slash)::
1551
1552
1552 hg clone ssh://user@server//home/projects/alpha/
1553 hg clone ssh://user@server//home/projects/alpha/
1553
1554
1554 - do a streaming clone while checking out a specified version::
1555 - do a streaming clone while checking out a specified version::
1555
1556
1556 hg clone --stream http://server/repo -u 1.5
1557 hg clone --stream http://server/repo -u 1.5
1557
1558
1558 - create a repository without changesets after a particular revision::
1559 - create a repository without changesets after a particular revision::
1559
1560
1560 hg clone -r 04e544 experimental/ good/
1561 hg clone -r 04e544 experimental/ good/
1561
1562
1562 - clone (and track) a particular named branch::
1563 - clone (and track) a particular named branch::
1563
1564
1564 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1565 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1565
1566
1566 See :hg:`help urls` for details on specifying URLs.
1567 See :hg:`help urls` for details on specifying URLs.
1567
1568
1568 Returns 0 on success.
1569 Returns 0 on success.
1569 """
1570 """
1570 opts = pycompat.byteskwargs(opts)
1571 opts = pycompat.byteskwargs(opts)
1571 if opts.get('noupdate') and opts.get('updaterev'):
1572 if opts.get('noupdate') and opts.get('updaterev'):
1572 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1573 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1573
1574
1574 # --include/--exclude can come from narrow or sparse.
1575 # --include/--exclude can come from narrow or sparse.
1575 includepats, excludepats = None, None
1576 includepats, excludepats = None, None
1576
1577
1577 # hg.clone() differentiates between None and an empty set. So make sure
1578 # hg.clone() differentiates between None and an empty set. So make sure
1578 # patterns are sets if narrow is requested without patterns.
1579 # patterns are sets if narrow is requested without patterns.
1579 if opts.get('narrow'):
1580 if opts.get('narrow'):
1580 includepats = set()
1581 includepats = set()
1581 excludepats = set()
1582 excludepats = set()
1582
1583
1583 if opts.get('include'):
1584 if opts.get('include'):
1584 includepats = narrowspec.parsepatterns(opts.get('include'))
1585 includepats = narrowspec.parsepatterns(opts.get('include'))
1585 if opts.get('exclude'):
1586 if opts.get('exclude'):
1586 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1587 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1587
1588
1588 r = hg.clone(ui, opts, source, dest,
1589 r = hg.clone(ui, opts, source, dest,
1589 pull=opts.get('pull'),
1590 pull=opts.get('pull'),
1590 stream=opts.get('stream') or opts.get('uncompressed'),
1591 stream=opts.get('stream') or opts.get('uncompressed'),
1591 revs=opts.get('rev'),
1592 revs=opts.get('rev'),
1592 update=opts.get('updaterev') or not opts.get('noupdate'),
1593 update=opts.get('updaterev') or not opts.get('noupdate'),
1593 branch=opts.get('branch'),
1594 branch=opts.get('branch'),
1594 shareopts=opts.get('shareopts'),
1595 shareopts=opts.get('shareopts'),
1595 storeincludepats=includepats,
1596 storeincludepats=includepats,
1596 storeexcludepats=excludepats,
1597 storeexcludepats=excludepats,
1597 depth=opts.get('depth') or None)
1598 depth=opts.get('depth') or None)
1598
1599
1599 return r is None
1600 return r is None
1600
1601
1601 @command('commit|ci',
1602 @command('commit|ci',
1602 [('A', 'addremove', None,
1603 [('A', 'addremove', None,
1603 _('mark new/missing files as added/removed before committing')),
1604 _('mark new/missing files as added/removed before committing')),
1604 ('', 'close-branch', None,
1605 ('', 'close-branch', None,
1605 _('mark a branch head as closed')),
1606 _('mark a branch head as closed')),
1606 ('', 'amend', None, _('amend the parent of the working directory')),
1607 ('', 'amend', None, _('amend the parent of the working directory')),
1607 ('s', 'secret', None, _('use the secret phase for committing')),
1608 ('s', 'secret', None, _('use the secret phase for committing')),
1608 ('e', 'edit', None, _('invoke editor on commit messages')),
1609 ('e', 'edit', None, _('invoke editor on commit messages')),
1609 ('', 'force-close-branch', None,
1610 ('', 'force-close-branch', None,
1610 _('forcibly close branch from a non-head changeset (ADVANCED)')),
1611 _('forcibly close branch from a non-head changeset (ADVANCED)')),
1611 ('i', 'interactive', None, _('use interactive mode')),
1612 ('i', 'interactive', None, _('use interactive mode')),
1612 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1613 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1613 _('[OPTION]... [FILE]...'),
1614 _('[OPTION]... [FILE]...'),
1614 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1615 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1615 inferrepo=True)
1616 inferrepo=True)
1616 def commit(ui, repo, *pats, **opts):
1617 def commit(ui, repo, *pats, **opts):
1617 """commit the specified files or all outstanding changes
1618 """commit the specified files or all outstanding changes
1618
1619
1619 Commit changes to the given files into the repository. Unlike a
1620 Commit changes to the given files into the repository. Unlike a
1620 centralized SCM, this operation is a local operation. See
1621 centralized SCM, this operation is a local operation. See
1621 :hg:`push` for a way to actively distribute your changes.
1622 :hg:`push` for a way to actively distribute your changes.
1622
1623
1623 If a list of files is omitted, all changes reported by :hg:`status`
1624 If a list of files is omitted, all changes reported by :hg:`status`
1624 will be committed.
1625 will be committed.
1625
1626
1626 If you are committing the result of a merge, do not provide any
1627 If you are committing the result of a merge, do not provide any
1627 filenames or -I/-X filters.
1628 filenames or -I/-X filters.
1628
1629
1629 If no commit message is specified, Mercurial starts your
1630 If no commit message is specified, Mercurial starts your
1630 configured editor where you can enter a message. In case your
1631 configured editor where you can enter a message. In case your
1631 commit fails, you will find a backup of your message in
1632 commit fails, you will find a backup of your message in
1632 ``.hg/last-message.txt``.
1633 ``.hg/last-message.txt``.
1633
1634
1634 The --close-branch flag can be used to mark the current branch
1635 The --close-branch flag can be used to mark the current branch
1635 head closed. When all heads of a branch are closed, the branch
1636 head closed. When all heads of a branch are closed, the branch
1636 will be considered closed and no longer listed.
1637 will be considered closed and no longer listed.
1637
1638
1638 The --amend flag can be used to amend the parent of the
1639 The --amend flag can be used to amend the parent of the
1639 working directory with a new commit that contains the changes
1640 working directory with a new commit that contains the changes
1640 in the parent in addition to those currently reported by :hg:`status`,
1641 in the parent in addition to those currently reported by :hg:`status`,
1641 if there are any. The old commit is stored in a backup bundle in
1642 if there are any. The old commit is stored in a backup bundle in
1642 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1643 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1643 on how to restore it).
1644 on how to restore it).
1644
1645
1645 Message, user and date are taken from the amended commit unless
1646 Message, user and date are taken from the amended commit unless
1646 specified. When a message isn't specified on the command line,
1647 specified. When a message isn't specified on the command line,
1647 the editor will open with the message of the amended commit.
1648 the editor will open with the message of the amended commit.
1648
1649
1649 It is not possible to amend public changesets (see :hg:`help phases`)
1650 It is not possible to amend public changesets (see :hg:`help phases`)
1650 or changesets that have children.
1651 or changesets that have children.
1651
1652
1652 See :hg:`help dates` for a list of formats valid for -d/--date.
1653 See :hg:`help dates` for a list of formats valid for -d/--date.
1653
1654
1654 Returns 0 on success, 1 if nothing changed.
1655 Returns 0 on success, 1 if nothing changed.
1655
1656
1656 .. container:: verbose
1657 .. container:: verbose
1657
1658
1658 Examples:
1659 Examples:
1659
1660
1660 - commit all files ending in .py::
1661 - commit all files ending in .py::
1661
1662
1662 hg commit --include "set:**.py"
1663 hg commit --include "set:**.py"
1663
1664
1664 - commit all non-binary files::
1665 - commit all non-binary files::
1665
1666
1666 hg commit --exclude "set:binary()"
1667 hg commit --exclude "set:binary()"
1667
1668
1668 - amend the current commit and set the date to now::
1669 - amend the current commit and set the date to now::
1669
1670
1670 hg commit --amend --date now
1671 hg commit --amend --date now
1671 """
1672 """
1672 with repo.wlock(), repo.lock():
1673 with repo.wlock(), repo.lock():
1673 return _docommit(ui, repo, *pats, **opts)
1674 return _docommit(ui, repo, *pats, **opts)
1674
1675
1675 def _docommit(ui, repo, *pats, **opts):
1676 def _docommit(ui, repo, *pats, **opts):
1676 if opts.get(r'interactive'):
1677 if opts.get(r'interactive'):
1677 opts.pop(r'interactive')
1678 opts.pop(r'interactive')
1678 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1679 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1679 cmdutil.recordfilter, *pats,
1680 cmdutil.recordfilter, *pats,
1680 **opts)
1681 **opts)
1681 # ret can be 0 (no changes to record) or the value returned by
1682 # ret can be 0 (no changes to record) or the value returned by
1682 # commit(), 1 if nothing changed or None on success.
1683 # commit(), 1 if nothing changed or None on success.
1683 return 1 if ret == 0 else ret
1684 return 1 if ret == 0 else ret
1684
1685
1685 opts = pycompat.byteskwargs(opts)
1686 opts = pycompat.byteskwargs(opts)
1686 if opts.get('subrepos'):
1687 if opts.get('subrepos'):
1687 if opts.get('amend'):
1688 if opts.get('amend'):
1688 raise error.Abort(_('cannot amend with --subrepos'))
1689 raise error.Abort(_('cannot amend with --subrepos'))
1689 # Let --subrepos on the command line override config setting.
1690 # Let --subrepos on the command line override config setting.
1690 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1691 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1691
1692
1692 cmdutil.checkunfinished(repo, commit=True)
1693 cmdutil.checkunfinished(repo, commit=True)
1693
1694
1694 branch = repo[None].branch()
1695 branch = repo[None].branch()
1695 bheads = repo.branchheads(branch)
1696 bheads = repo.branchheads(branch)
1696
1697
1697 extra = {}
1698 extra = {}
1698 if opts.get('close_branch') or opts.get('force_close_branch'):
1699 if opts.get('close_branch') or opts.get('force_close_branch'):
1699 extra['close'] = '1'
1700 extra['close'] = '1'
1700
1701
1701 if repo['.'].closesbranch():
1702 if repo['.'].closesbranch():
1702 raise error.Abort(_('current revision is already a branch closing'
1703 raise error.Abort(_('current revision is already a branch closing'
1703 ' head'))
1704 ' head'))
1704 elif not bheads:
1705 elif not bheads:
1705 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1706 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1706 elif (branch == repo['.'].branch() and repo['.'].node() not in bheads
1707 elif (branch == repo['.'].branch() and repo['.'].node() not in bheads
1707 and not opts.get('force_close_branch')):
1708 and not opts.get('force_close_branch')):
1708 hint = _('use --force-close-branch to close branch from a non-head'
1709 hint = _('use --force-close-branch to close branch from a non-head'
1709 ' changeset')
1710 ' changeset')
1710 raise error.Abort(_('can only close branch heads'), hint=hint)
1711 raise error.Abort(_('can only close branch heads'), hint=hint)
1711 elif opts.get('amend'):
1712 elif opts.get('amend'):
1712 if (repo['.'].p1().branch() != branch and
1713 if (repo['.'].p1().branch() != branch and
1713 repo['.'].p2().branch() != branch):
1714 repo['.'].p2().branch() != branch):
1714 raise error.Abort(_('can only close branch heads'))
1715 raise error.Abort(_('can only close branch heads'))
1715
1716
1716 if opts.get('amend'):
1717 if opts.get('amend'):
1717 if ui.configbool('ui', 'commitsubrepos'):
1718 if ui.configbool('ui', 'commitsubrepos'):
1718 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1719 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1719
1720
1720 old = repo['.']
1721 old = repo['.']
1721 rewriteutil.precheck(repo, [old.rev()], 'amend')
1722 rewriteutil.precheck(repo, [old.rev()], 'amend')
1722
1723
1723 # Currently histedit gets confused if an amend happens while histedit
1724 # Currently histedit gets confused if an amend happens while histedit
1724 # is in progress. Since we have a checkunfinished command, we are
1725 # is in progress. Since we have a checkunfinished command, we are
1725 # temporarily honoring it.
1726 # temporarily honoring it.
1726 #
1727 #
1727 # Note: eventually this guard will be removed. Please do not expect
1728 # Note: eventually this guard will be removed. Please do not expect
1728 # this behavior to remain.
1729 # this behavior to remain.
1729 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1730 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1730 cmdutil.checkunfinished(repo)
1731 cmdutil.checkunfinished(repo)
1731
1732
1732 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1733 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1733 if node == old.node():
1734 if node == old.node():
1734 ui.status(_("nothing changed\n"))
1735 ui.status(_("nothing changed\n"))
1735 return 1
1736 return 1
1736 else:
1737 else:
1737 def commitfunc(ui, repo, message, match, opts):
1738 def commitfunc(ui, repo, message, match, opts):
1738 overrides = {}
1739 overrides = {}
1739 if opts.get('secret'):
1740 if opts.get('secret'):
1740 overrides[('phases', 'new-commit')] = 'secret'
1741 overrides[('phases', 'new-commit')] = 'secret'
1741
1742
1742 baseui = repo.baseui
1743 baseui = repo.baseui
1743 with baseui.configoverride(overrides, 'commit'):
1744 with baseui.configoverride(overrides, 'commit'):
1744 with ui.configoverride(overrides, 'commit'):
1745 with ui.configoverride(overrides, 'commit'):
1745 editform = cmdutil.mergeeditform(repo[None],
1746 editform = cmdutil.mergeeditform(repo[None],
1746 'commit.normal')
1747 'commit.normal')
1747 editor = cmdutil.getcommiteditor(
1748 editor = cmdutil.getcommiteditor(
1748 editform=editform, **pycompat.strkwargs(opts))
1749 editform=editform, **pycompat.strkwargs(opts))
1749 return repo.commit(message,
1750 return repo.commit(message,
1750 opts.get('user'),
1751 opts.get('user'),
1751 opts.get('date'),
1752 opts.get('date'),
1752 match,
1753 match,
1753 editor=editor,
1754 editor=editor,
1754 extra=extra)
1755 extra=extra)
1755
1756
1756 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1757 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1757
1758
1758 if not node:
1759 if not node:
1759 stat = cmdutil.postcommitstatus(repo, pats, opts)
1760 stat = cmdutil.postcommitstatus(repo, pats, opts)
1760 if stat[3]:
1761 if stat[3]:
1761 ui.status(_("nothing changed (%d missing files, see "
1762 ui.status(_("nothing changed (%d missing files, see "
1762 "'hg status')\n") % len(stat[3]))
1763 "'hg status')\n") % len(stat[3]))
1763 else:
1764 else:
1764 ui.status(_("nothing changed\n"))
1765 ui.status(_("nothing changed\n"))
1765 return 1
1766 return 1
1766
1767
1767 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1768 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1768
1769
1769 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1770 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1770 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1771 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1771 unknown=True, subrepos=opts.get('subrepos'))
1772 unknown=True, subrepos=opts.get('subrepos'))
1772
1773
1773 @command('config|showconfig|debugconfig',
1774 @command('config|showconfig|debugconfig',
1774 [('u', 'untrusted', None, _('show untrusted configuration options')),
1775 [('u', 'untrusted', None, _('show untrusted configuration options')),
1775 ('e', 'edit', None, _('edit user config')),
1776 ('e', 'edit', None, _('edit user config')),
1776 ('l', 'local', None, _('edit repository config')),
1777 ('l', 'local', None, _('edit repository config')),
1777 ('g', 'global', None, _('edit global config'))] + formatteropts,
1778 ('g', 'global', None, _('edit global config'))] + formatteropts,
1778 _('[-u] [NAME]...'),
1779 _('[-u] [NAME]...'),
1779 helpcategory=command.CATEGORY_HELP,
1780 helpcategory=command.CATEGORY_HELP,
1780 optionalrepo=True,
1781 optionalrepo=True,
1781 intents={INTENT_READONLY})
1782 intents={INTENT_READONLY})
1782 def config(ui, repo, *values, **opts):
1783 def config(ui, repo, *values, **opts):
1783 """show combined config settings from all hgrc files
1784 """show combined config settings from all hgrc files
1784
1785
1785 With no arguments, print names and values of all config items.
1786 With no arguments, print names and values of all config items.
1786
1787
1787 With one argument of the form section.name, print just the value
1788 With one argument of the form section.name, print just the value
1788 of that config item.
1789 of that config item.
1789
1790
1790 With multiple arguments, print names and values of all config
1791 With multiple arguments, print names and values of all config
1791 items with matching section names or section.names.
1792 items with matching section names or section.names.
1792
1793
1793 With --edit, start an editor on the user-level config file. With
1794 With --edit, start an editor on the user-level config file. With
1794 --global, edit the system-wide config file. With --local, edit the
1795 --global, edit the system-wide config file. With --local, edit the
1795 repository-level config file.
1796 repository-level config file.
1796
1797
1797 With --debug, the source (filename and line number) is printed
1798 With --debug, the source (filename and line number) is printed
1798 for each config item.
1799 for each config item.
1799
1800
1800 See :hg:`help config` for more information about config files.
1801 See :hg:`help config` for more information about config files.
1801
1802
1802 .. container:: verbose
1803 .. container:: verbose
1803
1804
1804 Template:
1805 Template:
1805
1806
1806 The following keywords are supported. See also :hg:`help templates`.
1807 The following keywords are supported. See also :hg:`help templates`.
1807
1808
1808 :name: String. Config name.
1809 :name: String. Config name.
1809 :source: String. Filename and line number where the item is defined.
1810 :source: String. Filename and line number where the item is defined.
1810 :value: String. Config value.
1811 :value: String. Config value.
1811
1812
1812 Returns 0 on success, 1 if NAME does not exist.
1813 Returns 0 on success, 1 if NAME does not exist.
1813
1814
1814 """
1815 """
1815
1816
1816 opts = pycompat.byteskwargs(opts)
1817 opts = pycompat.byteskwargs(opts)
1817 if opts.get('edit') or opts.get('local') or opts.get('global'):
1818 if opts.get('edit') or opts.get('local') or opts.get('global'):
1818 if opts.get('local') and opts.get('global'):
1819 if opts.get('local') and opts.get('global'):
1819 raise error.Abort(_("can't use --local and --global together"))
1820 raise error.Abort(_("can't use --local and --global together"))
1820
1821
1821 if opts.get('local'):
1822 if opts.get('local'):
1822 if not repo:
1823 if not repo:
1823 raise error.Abort(_("can't use --local outside a repository"))
1824 raise error.Abort(_("can't use --local outside a repository"))
1824 paths = [repo.vfs.join('hgrc')]
1825 paths = [repo.vfs.join('hgrc')]
1825 elif opts.get('global'):
1826 elif opts.get('global'):
1826 paths = rcutil.systemrcpath()
1827 paths = rcutil.systemrcpath()
1827 else:
1828 else:
1828 paths = rcutil.userrcpath()
1829 paths = rcutil.userrcpath()
1829
1830
1830 for f in paths:
1831 for f in paths:
1831 if os.path.exists(f):
1832 if os.path.exists(f):
1832 break
1833 break
1833 else:
1834 else:
1834 if opts.get('global'):
1835 if opts.get('global'):
1835 samplehgrc = uimod.samplehgrcs['global']
1836 samplehgrc = uimod.samplehgrcs['global']
1836 elif opts.get('local'):
1837 elif opts.get('local'):
1837 samplehgrc = uimod.samplehgrcs['local']
1838 samplehgrc = uimod.samplehgrcs['local']
1838 else:
1839 else:
1839 samplehgrc = uimod.samplehgrcs['user']
1840 samplehgrc = uimod.samplehgrcs['user']
1840
1841
1841 f = paths[0]
1842 f = paths[0]
1842 fp = open(f, "wb")
1843 fp = open(f, "wb")
1843 fp.write(util.tonativeeol(samplehgrc))
1844 fp.write(util.tonativeeol(samplehgrc))
1844 fp.close()
1845 fp.close()
1845
1846
1846 editor = ui.geteditor()
1847 editor = ui.geteditor()
1847 ui.system("%s \"%s\"" % (editor, f),
1848 ui.system("%s \"%s\"" % (editor, f),
1848 onerr=error.Abort, errprefix=_("edit failed"),
1849 onerr=error.Abort, errprefix=_("edit failed"),
1849 blockedtag='config_edit')
1850 blockedtag='config_edit')
1850 return
1851 return
1851 ui.pager('config')
1852 ui.pager('config')
1852 fm = ui.formatter('config', opts)
1853 fm = ui.formatter('config', opts)
1853 for t, f in rcutil.rccomponents():
1854 for t, f in rcutil.rccomponents():
1854 if t == 'path':
1855 if t == 'path':
1855 ui.debug('read config from: %s\n' % f)
1856 ui.debug('read config from: %s\n' % f)
1856 elif t == 'items':
1857 elif t == 'items':
1857 for section, name, value, source in f:
1858 for section, name, value, source in f:
1858 ui.debug('set config by: %s\n' % source)
1859 ui.debug('set config by: %s\n' % source)
1859 else:
1860 else:
1860 raise error.ProgrammingError('unknown rctype: %s' % t)
1861 raise error.ProgrammingError('unknown rctype: %s' % t)
1861 untrusted = bool(opts.get('untrusted'))
1862 untrusted = bool(opts.get('untrusted'))
1862
1863
1863 selsections = selentries = []
1864 selsections = selentries = []
1864 if values:
1865 if values:
1865 selsections = [v for v in values if '.' not in v]
1866 selsections = [v for v in values if '.' not in v]
1866 selentries = [v for v in values if '.' in v]
1867 selentries = [v for v in values if '.' in v]
1867 uniquesel = (len(selentries) == 1 and not selsections)
1868 uniquesel = (len(selentries) == 1 and not selsections)
1868 selsections = set(selsections)
1869 selsections = set(selsections)
1869 selentries = set(selentries)
1870 selentries = set(selentries)
1870
1871
1871 matched = False
1872 matched = False
1872 for section, name, value in ui.walkconfig(untrusted=untrusted):
1873 for section, name, value in ui.walkconfig(untrusted=untrusted):
1873 source = ui.configsource(section, name, untrusted)
1874 source = ui.configsource(section, name, untrusted)
1874 value = pycompat.bytestr(value)
1875 value = pycompat.bytestr(value)
1875 defaultvalue = ui.configdefault(section, name)
1876 defaultvalue = ui.configdefault(section, name)
1876 if fm.isplain():
1877 if fm.isplain():
1877 source = source or 'none'
1878 source = source or 'none'
1878 value = value.replace('\n', '\\n')
1879 value = value.replace('\n', '\\n')
1879 entryname = section + '.' + name
1880 entryname = section + '.' + name
1880 if values and not (section in selsections or entryname in selentries):
1881 if values and not (section in selsections or entryname in selentries):
1881 continue
1882 continue
1882 fm.startitem()
1883 fm.startitem()
1883 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1884 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1884 if uniquesel:
1885 if uniquesel:
1885 fm.data(name=entryname)
1886 fm.data(name=entryname)
1886 fm.write('value', '%s\n', value)
1887 fm.write('value', '%s\n', value)
1887 else:
1888 else:
1888 fm.write('name value', '%s=%s\n', entryname, value)
1889 fm.write('name value', '%s=%s\n', entryname, value)
1889 fm.data(defaultvalue=defaultvalue)
1890 fm.data(defaultvalue=defaultvalue)
1890 matched = True
1891 matched = True
1891 fm.end()
1892 fm.end()
1892 if matched:
1893 if matched:
1893 return 0
1894 return 0
1894 return 1
1895 return 1
1895
1896
1896 @command('continue',
1897 @command('continue',
1897 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1898 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1898 helpbasic=True)
1899 helpbasic=True)
1899 def continuecmd(ui, repo, **opts):
1900 def continuecmd(ui, repo, **opts):
1900 """resumes an interrupted operation (EXPERIMENTAL)
1901 """resumes an interrupted operation (EXPERIMENTAL)
1901
1902
1902 Finishes a multistep operation like graft, histedit, rebase, merge,
1903 Finishes a multistep operation like graft, histedit, rebase, merge,
1903 and unshelve if they are in an interrupted state.
1904 and unshelve if they are in an interrupted state.
1904
1905
1905 use --dry-run/-n to dry run the command.
1906 use --dry-run/-n to dry run the command.
1906 """
1907 """
1907 dryrun = opts.get(r'dry_run')
1908 dryrun = opts.get(r'dry_run')
1908 contstate = cmdutil.getunfinishedstate(repo)
1909 contstate = cmdutil.getunfinishedstate(repo)
1909 if not contstate:
1910 if not contstate:
1910 raise error.Abort(_('no operation in progress'))
1911 raise error.Abort(_('no operation in progress'))
1911 if not contstate.continuefunc:
1912 if not contstate.continuefunc:
1912 raise error.Abort((_("%s in progress but does not support "
1913 raise error.Abort((_("%s in progress but does not support "
1913 "'hg continue'") % (contstate._opname)),
1914 "'hg continue'") % (contstate._opname)),
1914 hint=contstate.continuemsg())
1915 hint=contstate.continuemsg())
1915 if dryrun:
1916 if dryrun:
1916 ui.status(_('%s in progress, will be resumed\n') % (contstate._opname))
1917 ui.status(_('%s in progress, will be resumed\n') % (contstate._opname))
1917 return
1918 return
1918 return contstate.continuefunc(ui, repo)
1919 return contstate.continuefunc(ui, repo)
1919
1920
1920 @command('copy|cp',
1921 @command('copy|cp',
1921 [('A', 'after', None, _('record a copy that has already occurred')),
1922 [('A', 'after', None, _('record a copy that has already occurred')),
1922 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1923 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1923 ] + walkopts + dryrunopts,
1924 ] + walkopts + dryrunopts,
1924 _('[OPTION]... SOURCE... DEST'),
1925 _('[OPTION]... SOURCE... DEST'),
1925 helpcategory=command.CATEGORY_FILE_CONTENTS)
1926 helpcategory=command.CATEGORY_FILE_CONTENTS)
1926 def copy(ui, repo, *pats, **opts):
1927 def copy(ui, repo, *pats, **opts):
1927 """mark files as copied for the next commit
1928 """mark files as copied for the next commit
1928
1929
1929 Mark dest as having copies of source files. If dest is a
1930 Mark dest as having copies of source files. If dest is a
1930 directory, copies are put in that directory. If dest is a file,
1931 directory, copies are put in that directory. If dest is a file,
1931 the source must be a single file.
1932 the source must be a single file.
1932
1933
1933 By default, this command copies the contents of files as they
1934 By default, this command copies the contents of files as they
1934 exist in the working directory. If invoked with -A/--after, the
1935 exist in the working directory. If invoked with -A/--after, the
1935 operation is recorded, but no copying is performed.
1936 operation is recorded, but no copying is performed.
1936
1937
1937 This command takes effect with the next commit. To undo a copy
1938 This command takes effect with the next commit. To undo a copy
1938 before that, see :hg:`revert`.
1939 before that, see :hg:`revert`.
1939
1940
1940 Returns 0 on success, 1 if errors are encountered.
1941 Returns 0 on success, 1 if errors are encountered.
1941 """
1942 """
1942 opts = pycompat.byteskwargs(opts)
1943 opts = pycompat.byteskwargs(opts)
1943 with repo.wlock(False):
1944 with repo.wlock(False):
1944 return cmdutil.copy(ui, repo, pats, opts)
1945 return cmdutil.copy(ui, repo, pats, opts)
1945
1946
1946 @command(
1947 @command(
1947 'debugcommands', [], _('[COMMAND]'),
1948 'debugcommands', [], _('[COMMAND]'),
1948 helpcategory=command.CATEGORY_HELP,
1949 helpcategory=command.CATEGORY_HELP,
1949 norepo=True)
1950 norepo=True)
1950 def debugcommands(ui, cmd='', *args):
1951 def debugcommands(ui, cmd='', *args):
1951 """list all available commands and options"""
1952 """list all available commands and options"""
1952 for cmd, vals in sorted(table.iteritems()):
1953 for cmd, vals in sorted(table.iteritems()):
1953 cmd = cmd.split('|')[0]
1954 cmd = cmd.split('|')[0]
1954 opts = ', '.join([i[1] for i in vals[1]])
1955 opts = ', '.join([i[1] for i in vals[1]])
1955 ui.write('%s: %s\n' % (cmd, opts))
1956 ui.write('%s: %s\n' % (cmd, opts))
1956
1957
1957 @command('debugcomplete',
1958 @command('debugcomplete',
1958 [('o', 'options', None, _('show the command options'))],
1959 [('o', 'options', None, _('show the command options'))],
1959 _('[-o] CMD'),
1960 _('[-o] CMD'),
1960 helpcategory=command.CATEGORY_HELP,
1961 helpcategory=command.CATEGORY_HELP,
1961 norepo=True)
1962 norepo=True)
1962 def debugcomplete(ui, cmd='', **opts):
1963 def debugcomplete(ui, cmd='', **opts):
1963 """returns the completion list associated with the given command"""
1964 """returns the completion list associated with the given command"""
1964
1965
1965 if opts.get(r'options'):
1966 if opts.get(r'options'):
1966 options = []
1967 options = []
1967 otables = [globalopts]
1968 otables = [globalopts]
1968 if cmd:
1969 if cmd:
1969 aliases, entry = cmdutil.findcmd(cmd, table, False)
1970 aliases, entry = cmdutil.findcmd(cmd, table, False)
1970 otables.append(entry[1])
1971 otables.append(entry[1])
1971 for t in otables:
1972 for t in otables:
1972 for o in t:
1973 for o in t:
1973 if "(DEPRECATED)" in o[3]:
1974 if "(DEPRECATED)" in o[3]:
1974 continue
1975 continue
1975 if o[0]:
1976 if o[0]:
1976 options.append('-%s' % o[0])
1977 options.append('-%s' % o[0])
1977 options.append('--%s' % o[1])
1978 options.append('--%s' % o[1])
1978 ui.write("%s\n" % "\n".join(options))
1979 ui.write("%s\n" % "\n".join(options))
1979 return
1980 return
1980
1981
1981 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1982 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1982 if ui.verbose:
1983 if ui.verbose:
1983 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1984 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1984 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1985 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1985
1986
1986 @command('diff',
1987 @command('diff',
1987 [('r', 'rev', [], _('revision'), _('REV')),
1988 [('r', 'rev', [], _('revision'), _('REV')),
1988 ('c', 'change', '', _('change made by revision'), _('REV'))
1989 ('c', 'change', '', _('change made by revision'), _('REV'))
1989 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1990 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1990 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1991 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1991 helpcategory=command.CATEGORY_FILE_CONTENTS,
1992 helpcategory=command.CATEGORY_FILE_CONTENTS,
1992 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1993 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1993 def diff(ui, repo, *pats, **opts):
1994 def diff(ui, repo, *pats, **opts):
1994 """diff repository (or selected files)
1995 """diff repository (or selected files)
1995
1996
1996 Show differences between revisions for the specified files.
1997 Show differences between revisions for the specified files.
1997
1998
1998 Differences between files are shown using the unified diff format.
1999 Differences between files are shown using the unified diff format.
1999
2000
2000 .. note::
2001 .. note::
2001
2002
2002 :hg:`diff` may generate unexpected results for merges, as it will
2003 :hg:`diff` may generate unexpected results for merges, as it will
2003 default to comparing against the working directory's first
2004 default to comparing against the working directory's first
2004 parent changeset if no revisions are specified.
2005 parent changeset if no revisions are specified.
2005
2006
2006 When two revision arguments are given, then changes are shown
2007 When two revision arguments are given, then changes are shown
2007 between those revisions. If only one revision is specified then
2008 between those revisions. If only one revision is specified then
2008 that revision is compared to the working directory, and, when no
2009 that revision is compared to the working directory, and, when no
2009 revisions are specified, the working directory files are compared
2010 revisions are specified, the working directory files are compared
2010 to its first parent.
2011 to its first parent.
2011
2012
2012 Alternatively you can specify -c/--change with a revision to see
2013 Alternatively you can specify -c/--change with a revision to see
2013 the changes in that changeset relative to its first parent.
2014 the changes in that changeset relative to its first parent.
2014
2015
2015 Without the -a/--text option, diff will avoid generating diffs of
2016 Without the -a/--text option, diff will avoid generating diffs of
2016 files it detects as binary. With -a, diff will generate a diff
2017 files it detects as binary. With -a, diff will generate a diff
2017 anyway, probably with undesirable results.
2018 anyway, probably with undesirable results.
2018
2019
2019 Use the -g/--git option to generate diffs in the git extended diff
2020 Use the -g/--git option to generate diffs in the git extended diff
2020 format. For more information, read :hg:`help diffs`.
2021 format. For more information, read :hg:`help diffs`.
2021
2022
2022 .. container:: verbose
2023 .. container:: verbose
2023
2024
2024 Examples:
2025 Examples:
2025
2026
2026 - compare a file in the current working directory to its parent::
2027 - compare a file in the current working directory to its parent::
2027
2028
2028 hg diff foo.c
2029 hg diff foo.c
2029
2030
2030 - compare two historical versions of a directory, with rename info::
2031 - compare two historical versions of a directory, with rename info::
2031
2032
2032 hg diff --git -r 1.0:1.2 lib/
2033 hg diff --git -r 1.0:1.2 lib/
2033
2034
2034 - get change stats relative to the last change on some date::
2035 - get change stats relative to the last change on some date::
2035
2036
2036 hg diff --stat -r "date('may 2')"
2037 hg diff --stat -r "date('may 2')"
2037
2038
2038 - diff all newly-added files that contain a keyword::
2039 - diff all newly-added files that contain a keyword::
2039
2040
2040 hg diff "set:added() and grep(GNU)"
2041 hg diff "set:added() and grep(GNU)"
2041
2042
2042 - compare a revision and its parents::
2043 - compare a revision and its parents::
2043
2044
2044 hg diff -c 9353 # compare against first parent
2045 hg diff -c 9353 # compare against first parent
2045 hg diff -r 9353^:9353 # same using revset syntax
2046 hg diff -r 9353^:9353 # same using revset syntax
2046 hg diff -r 9353^2:9353 # compare against the second parent
2047 hg diff -r 9353^2:9353 # compare against the second parent
2047
2048
2048 Returns 0 on success.
2049 Returns 0 on success.
2049 """
2050 """
2050
2051
2051 opts = pycompat.byteskwargs(opts)
2052 opts = pycompat.byteskwargs(opts)
2052 revs = opts.get('rev')
2053 revs = opts.get('rev')
2053 change = opts.get('change')
2054 change = opts.get('change')
2054 stat = opts.get('stat')
2055 stat = opts.get('stat')
2055 reverse = opts.get('reverse')
2056 reverse = opts.get('reverse')
2056
2057
2057 if revs and change:
2058 if revs and change:
2058 msg = _('cannot specify --rev and --change at the same time')
2059 msg = _('cannot specify --rev and --change at the same time')
2059 raise error.Abort(msg)
2060 raise error.Abort(msg)
2060 elif change:
2061 elif change:
2061 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2062 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2062 ctx2 = scmutil.revsingle(repo, change, None)
2063 ctx2 = scmutil.revsingle(repo, change, None)
2063 ctx1 = ctx2.p1()
2064 ctx1 = ctx2.p1()
2064 else:
2065 else:
2065 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2066 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2066 ctx1, ctx2 = scmutil.revpair(repo, revs)
2067 ctx1, ctx2 = scmutil.revpair(repo, revs)
2067 node1, node2 = ctx1.node(), ctx2.node()
2068 node1, node2 = ctx1.node(), ctx2.node()
2068
2069
2069 if reverse:
2070 if reverse:
2070 node1, node2 = node2, node1
2071 node1, node2 = node2, node1
2071
2072
2072 diffopts = patch.diffallopts(ui, opts)
2073 diffopts = patch.diffallopts(ui, opts)
2073 m = scmutil.match(ctx2, pats, opts)
2074 m = scmutil.match(ctx2, pats, opts)
2074 m = repo.narrowmatch(m)
2075 m = repo.narrowmatch(m)
2075 ui.pager('diff')
2076 ui.pager('diff')
2076 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2077 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2077 listsubrepos=opts.get('subrepos'),
2078 listsubrepos=opts.get('subrepos'),
2078 root=opts.get('root'))
2079 root=opts.get('root'))
2079
2080
2080 @command('export',
2081 @command('export',
2081 [('B', 'bookmark', '',
2082 [('B', 'bookmark', '',
2082 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2083 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2083 ('o', 'output', '',
2084 ('o', 'output', '',
2084 _('print output to file with formatted name'), _('FORMAT')),
2085 _('print output to file with formatted name'), _('FORMAT')),
2085 ('', 'switch-parent', None, _('diff against the second parent')),
2086 ('', 'switch-parent', None, _('diff against the second parent')),
2086 ('r', 'rev', [], _('revisions to export'), _('REV')),
2087 ('r', 'rev', [], _('revisions to export'), _('REV')),
2087 ] + diffopts + formatteropts,
2088 ] + diffopts + formatteropts,
2088 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2089 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2089 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2090 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2090 helpbasic=True, intents={INTENT_READONLY})
2091 helpbasic=True, intents={INTENT_READONLY})
2091 def export(ui, repo, *changesets, **opts):
2092 def export(ui, repo, *changesets, **opts):
2092 """dump the header and diffs for one or more changesets
2093 """dump the header and diffs for one or more changesets
2093
2094
2094 Print the changeset header and diffs for one or more revisions.
2095 Print the changeset header and diffs for one or more revisions.
2095 If no revision is given, the parent of the working directory is used.
2096 If no revision is given, the parent of the working directory is used.
2096
2097
2097 The information shown in the changeset header is: author, date,
2098 The information shown in the changeset header is: author, date,
2098 branch name (if non-default), changeset hash, parent(s) and commit
2099 branch name (if non-default), changeset hash, parent(s) and commit
2099 comment.
2100 comment.
2100
2101
2101 .. note::
2102 .. note::
2102
2103
2103 :hg:`export` may generate unexpected diff output for merge
2104 :hg:`export` may generate unexpected diff output for merge
2104 changesets, as it will compare the merge changeset against its
2105 changesets, as it will compare the merge changeset against its
2105 first parent only.
2106 first parent only.
2106
2107
2107 Output may be to a file, in which case the name of the file is
2108 Output may be to a file, in which case the name of the file is
2108 given using a template string. See :hg:`help templates`. In addition
2109 given using a template string. See :hg:`help templates`. In addition
2109 to the common template keywords, the following formatting rules are
2110 to the common template keywords, the following formatting rules are
2110 supported:
2111 supported:
2111
2112
2112 :``%%``: literal "%" character
2113 :``%%``: literal "%" character
2113 :``%H``: changeset hash (40 hexadecimal digits)
2114 :``%H``: changeset hash (40 hexadecimal digits)
2114 :``%N``: number of patches being generated
2115 :``%N``: number of patches being generated
2115 :``%R``: changeset revision number
2116 :``%R``: changeset revision number
2116 :``%b``: basename of the exporting repository
2117 :``%b``: basename of the exporting repository
2117 :``%h``: short-form changeset hash (12 hexadecimal digits)
2118 :``%h``: short-form changeset hash (12 hexadecimal digits)
2118 :``%m``: first line of the commit message (only alphanumeric characters)
2119 :``%m``: first line of the commit message (only alphanumeric characters)
2119 :``%n``: zero-padded sequence number, starting at 1
2120 :``%n``: zero-padded sequence number, starting at 1
2120 :``%r``: zero-padded changeset revision number
2121 :``%r``: zero-padded changeset revision number
2121 :``\\``: literal "\\" character
2122 :``\\``: literal "\\" character
2122
2123
2123 Without the -a/--text option, export will avoid generating diffs
2124 Without the -a/--text option, export will avoid generating diffs
2124 of files it detects as binary. With -a, export will generate a
2125 of files it detects as binary. With -a, export will generate a
2125 diff anyway, probably with undesirable results.
2126 diff anyway, probably with undesirable results.
2126
2127
2127 With -B/--bookmark changesets reachable by the given bookmark are
2128 With -B/--bookmark changesets reachable by the given bookmark are
2128 selected.
2129 selected.
2129
2130
2130 Use the -g/--git option to generate diffs in the git extended diff
2131 Use the -g/--git option to generate diffs in the git extended diff
2131 format. See :hg:`help diffs` for more information.
2132 format. See :hg:`help diffs` for more information.
2132
2133
2133 With the --switch-parent option, the diff will be against the
2134 With the --switch-parent option, the diff will be against the
2134 second parent. It can be useful to review a merge.
2135 second parent. It can be useful to review a merge.
2135
2136
2136 .. container:: verbose
2137 .. container:: verbose
2137
2138
2138 Template:
2139 Template:
2139
2140
2140 The following keywords are supported in addition to the common template
2141 The following keywords are supported in addition to the common template
2141 keywords and functions. See also :hg:`help templates`.
2142 keywords and functions. See also :hg:`help templates`.
2142
2143
2143 :diff: String. Diff content.
2144 :diff: String. Diff content.
2144 :parents: List of strings. Parent nodes of the changeset.
2145 :parents: List of strings. Parent nodes of the changeset.
2145
2146
2146 Examples:
2147 Examples:
2147
2148
2148 - use export and import to transplant a bugfix to the current
2149 - use export and import to transplant a bugfix to the current
2149 branch::
2150 branch::
2150
2151
2151 hg export -r 9353 | hg import -
2152 hg export -r 9353 | hg import -
2152
2153
2153 - export all the changesets between two revisions to a file with
2154 - export all the changesets between two revisions to a file with
2154 rename information::
2155 rename information::
2155
2156
2156 hg export --git -r 123:150 > changes.txt
2157 hg export --git -r 123:150 > changes.txt
2157
2158
2158 - split outgoing changes into a series of patches with
2159 - split outgoing changes into a series of patches with
2159 descriptive names::
2160 descriptive names::
2160
2161
2161 hg export -r "outgoing()" -o "%n-%m.patch"
2162 hg export -r "outgoing()" -o "%n-%m.patch"
2162
2163
2163 Returns 0 on success.
2164 Returns 0 on success.
2164 """
2165 """
2165 opts = pycompat.byteskwargs(opts)
2166 opts = pycompat.byteskwargs(opts)
2166 bookmark = opts.get('bookmark')
2167 bookmark = opts.get('bookmark')
2167 changesets += tuple(opts.get('rev', []))
2168 changesets += tuple(opts.get('rev', []))
2168
2169
2169 if bookmark and changesets:
2170 if bookmark and changesets:
2170 raise error.Abort(_("-r and -B are mutually exclusive"))
2171 raise error.Abort(_("-r and -B are mutually exclusive"))
2171
2172
2172 if bookmark:
2173 if bookmark:
2173 if bookmark not in repo._bookmarks:
2174 if bookmark not in repo._bookmarks:
2174 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2175 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2175
2176
2176 revs = scmutil.bookmarkrevs(repo, bookmark)
2177 revs = scmutil.bookmarkrevs(repo, bookmark)
2177 else:
2178 else:
2178 if not changesets:
2179 if not changesets:
2179 changesets = ['.']
2180 changesets = ['.']
2180
2181
2181 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2182 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2182 revs = scmutil.revrange(repo, changesets)
2183 revs = scmutil.revrange(repo, changesets)
2183
2184
2184 if not revs:
2185 if not revs:
2185 raise error.Abort(_("export requires at least one changeset"))
2186 raise error.Abort(_("export requires at least one changeset"))
2186 if len(revs) > 1:
2187 if len(revs) > 1:
2187 ui.note(_('exporting patches:\n'))
2188 ui.note(_('exporting patches:\n'))
2188 else:
2189 else:
2189 ui.note(_('exporting patch:\n'))
2190 ui.note(_('exporting patch:\n'))
2190
2191
2191 fntemplate = opts.get('output')
2192 fntemplate = opts.get('output')
2192 if cmdutil.isstdiofilename(fntemplate):
2193 if cmdutil.isstdiofilename(fntemplate):
2193 fntemplate = ''
2194 fntemplate = ''
2194
2195
2195 if fntemplate:
2196 if fntemplate:
2196 fm = formatter.nullformatter(ui, 'export', opts)
2197 fm = formatter.nullformatter(ui, 'export', opts)
2197 else:
2198 else:
2198 ui.pager('export')
2199 ui.pager('export')
2199 fm = ui.formatter('export', opts)
2200 fm = ui.formatter('export', opts)
2200 with fm:
2201 with fm:
2201 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2202 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2202 switch_parent=opts.get('switch_parent'),
2203 switch_parent=opts.get('switch_parent'),
2203 opts=patch.diffallopts(ui, opts))
2204 opts=patch.diffallopts(ui, opts))
2204
2205
2205 @command('files',
2206 @command('files',
2206 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2207 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2207 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2208 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2208 ] + walkopts + formatteropts + subrepoopts,
2209 ] + walkopts + formatteropts + subrepoopts,
2209 _('[OPTION]... [FILE]...'),
2210 _('[OPTION]... [FILE]...'),
2210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2211 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2211 intents={INTENT_READONLY})
2212 intents={INTENT_READONLY})
2212 def files(ui, repo, *pats, **opts):
2213 def files(ui, repo, *pats, **opts):
2213 """list tracked files
2214 """list tracked files
2214
2215
2215 Print files under Mercurial control in the working directory or
2216 Print files under Mercurial control in the working directory or
2216 specified revision for given files (excluding removed files).
2217 specified revision for given files (excluding removed files).
2217 Files can be specified as filenames or filesets.
2218 Files can be specified as filenames or filesets.
2218
2219
2219 If no files are given to match, this command prints the names
2220 If no files are given to match, this command prints the names
2220 of all files under Mercurial control.
2221 of all files under Mercurial control.
2221
2222
2222 .. container:: verbose
2223 .. container:: verbose
2223
2224
2224 Template:
2225 Template:
2225
2226
2226 The following keywords are supported in addition to the common template
2227 The following keywords are supported in addition to the common template
2227 keywords and functions. See also :hg:`help templates`.
2228 keywords and functions. See also :hg:`help templates`.
2228
2229
2229 :flags: String. Character denoting file's symlink and executable bits.
2230 :flags: String. Character denoting file's symlink and executable bits.
2230 :path: String. Repository-absolute path of the file.
2231 :path: String. Repository-absolute path of the file.
2231 :size: Integer. Size of the file in bytes.
2232 :size: Integer. Size of the file in bytes.
2232
2233
2233 Examples:
2234 Examples:
2234
2235
2235 - list all files under the current directory::
2236 - list all files under the current directory::
2236
2237
2237 hg files .
2238 hg files .
2238
2239
2239 - shows sizes and flags for current revision::
2240 - shows sizes and flags for current revision::
2240
2241
2241 hg files -vr .
2242 hg files -vr .
2242
2243
2243 - list all files named README::
2244 - list all files named README::
2244
2245
2245 hg files -I "**/README"
2246 hg files -I "**/README"
2246
2247
2247 - list all binary files::
2248 - list all binary files::
2248
2249
2249 hg files "set:binary()"
2250 hg files "set:binary()"
2250
2251
2251 - find files containing a regular expression::
2252 - find files containing a regular expression::
2252
2253
2253 hg files "set:grep('bob')"
2254 hg files "set:grep('bob')"
2254
2255
2255 - search tracked file contents with xargs and grep::
2256 - search tracked file contents with xargs and grep::
2256
2257
2257 hg files -0 | xargs -0 grep foo
2258 hg files -0 | xargs -0 grep foo
2258
2259
2259 See :hg:`help patterns` and :hg:`help filesets` for more information
2260 See :hg:`help patterns` and :hg:`help filesets` for more information
2260 on specifying file patterns.
2261 on specifying file patterns.
2261
2262
2262 Returns 0 if a match is found, 1 otherwise.
2263 Returns 0 if a match is found, 1 otherwise.
2263
2264
2264 """
2265 """
2265
2266
2266 opts = pycompat.byteskwargs(opts)
2267 opts = pycompat.byteskwargs(opts)
2267 rev = opts.get('rev')
2268 rev = opts.get('rev')
2268 if rev:
2269 if rev:
2269 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2270 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2270 ctx = scmutil.revsingle(repo, rev, None)
2271 ctx = scmutil.revsingle(repo, rev, None)
2271
2272
2272 end = '\n'
2273 end = '\n'
2273 if opts.get('print0'):
2274 if opts.get('print0'):
2274 end = '\0'
2275 end = '\0'
2275 fmt = '%s' + end
2276 fmt = '%s' + end
2276
2277
2277 m = scmutil.match(ctx, pats, opts)
2278 m = scmutil.match(ctx, pats, opts)
2278 ui.pager('files')
2279 ui.pager('files')
2279 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2280 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2280 with ui.formatter('files', opts) as fm:
2281 with ui.formatter('files', opts) as fm:
2281 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2282 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2282 opts.get('subrepos'))
2283 opts.get('subrepos'))
2283
2284
2284 @command(
2285 @command(
2285 'forget',
2286 'forget',
2286 [('i', 'interactive', None, _('use interactive mode')),
2287 [('i', 'interactive', None, _('use interactive mode')),
2287 ] + walkopts + dryrunopts,
2288 ] + walkopts + dryrunopts,
2288 _('[OPTION]... FILE...'),
2289 _('[OPTION]... FILE...'),
2289 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2290 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2290 helpbasic=True, inferrepo=True)
2291 helpbasic=True, inferrepo=True)
2291 def forget(ui, repo, *pats, **opts):
2292 def forget(ui, repo, *pats, **opts):
2292 """forget the specified files on the next commit
2293 """forget the specified files on the next commit
2293
2294
2294 Mark the specified files so they will no longer be tracked
2295 Mark the specified files so they will no longer be tracked
2295 after the next commit.
2296 after the next commit.
2296
2297
2297 This only removes files from the current branch, not from the
2298 This only removes files from the current branch, not from the
2298 entire project history, and it does not delete them from the
2299 entire project history, and it does not delete them from the
2299 working directory.
2300 working directory.
2300
2301
2301 To delete the file from the working directory, see :hg:`remove`.
2302 To delete the file from the working directory, see :hg:`remove`.
2302
2303
2303 To undo a forget before the next commit, see :hg:`add`.
2304 To undo a forget before the next commit, see :hg:`add`.
2304
2305
2305 .. container:: verbose
2306 .. container:: verbose
2306
2307
2307 Examples:
2308 Examples:
2308
2309
2309 - forget newly-added binary files::
2310 - forget newly-added binary files::
2310
2311
2311 hg forget "set:added() and binary()"
2312 hg forget "set:added() and binary()"
2312
2313
2313 - forget files that would be excluded by .hgignore::
2314 - forget files that would be excluded by .hgignore::
2314
2315
2315 hg forget "set:hgignore()"
2316 hg forget "set:hgignore()"
2316
2317
2317 Returns 0 on success.
2318 Returns 0 on success.
2318 """
2319 """
2319
2320
2320 opts = pycompat.byteskwargs(opts)
2321 opts = pycompat.byteskwargs(opts)
2321 if not pats:
2322 if not pats:
2322 raise error.Abort(_('no files specified'))
2323 raise error.Abort(_('no files specified'))
2323
2324
2324 m = scmutil.match(repo[None], pats, opts)
2325 m = scmutil.match(repo[None], pats, opts)
2325 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2326 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2326 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2327 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2327 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2328 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2328 explicitonly=False, dryrun=dryrun,
2329 explicitonly=False, dryrun=dryrun,
2329 interactive=interactive)[0]
2330 interactive=interactive)[0]
2330 return rejected and 1 or 0
2331 return rejected and 1 or 0
2331
2332
2332 @command(
2333 @command(
2333 'graft',
2334 'graft',
2334 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2335 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2335 ('', 'base', '',
2336 ('', 'base', '',
2336 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2337 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2337 ('c', 'continue', False, _('resume interrupted graft')),
2338 ('c', 'continue', False, _('resume interrupted graft')),
2338 ('', 'stop', False, _('stop interrupted graft')),
2339 ('', 'stop', False, _('stop interrupted graft')),
2339 ('', 'abort', False, _('abort interrupted graft')),
2340 ('', 'abort', False, _('abort interrupted graft')),
2340 ('e', 'edit', False, _('invoke editor on commit messages')),
2341 ('e', 'edit', False, _('invoke editor on commit messages')),
2341 ('', 'log', None, _('append graft info to log message')),
2342 ('', 'log', None, _('append graft info to log message')),
2342 ('', 'no-commit', None,
2343 ('', 'no-commit', None,
2343 _("don't commit, just apply the changes in working directory")),
2344 _("don't commit, just apply the changes in working directory")),
2344 ('f', 'force', False, _('force graft')),
2345 ('f', 'force', False, _('force graft')),
2345 ('D', 'currentdate', False,
2346 ('D', 'currentdate', False,
2346 _('record the current date as commit date')),
2347 _('record the current date as commit date')),
2347 ('U', 'currentuser', False,
2348 ('U', 'currentuser', False,
2348 _('record the current user as committer'))]
2349 _('record the current user as committer'))]
2349 + commitopts2 + mergetoolopts + dryrunopts,
2350 + commitopts2 + mergetoolopts + dryrunopts,
2350 _('[OPTION]... [-r REV]... REV...'),
2351 _('[OPTION]... [-r REV]... REV...'),
2351 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2352 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2352 def graft(ui, repo, *revs, **opts):
2353 def graft(ui, repo, *revs, **opts):
2353 '''copy changes from other branches onto the current branch
2354 '''copy changes from other branches onto the current branch
2354
2355
2355 This command uses Mercurial's merge logic to copy individual
2356 This command uses Mercurial's merge logic to copy individual
2356 changes from other branches without merging branches in the
2357 changes from other branches without merging branches in the
2357 history graph. This is sometimes known as 'backporting' or
2358 history graph. This is sometimes known as 'backporting' or
2358 'cherry-picking'. By default, graft will copy user, date, and
2359 'cherry-picking'. By default, graft will copy user, date, and
2359 description from the source changesets.
2360 description from the source changesets.
2360
2361
2361 Changesets that are ancestors of the current revision, that have
2362 Changesets that are ancestors of the current revision, that have
2362 already been grafted, or that are merges will be skipped.
2363 already been grafted, or that are merges will be skipped.
2363
2364
2364 If --log is specified, log messages will have a comment appended
2365 If --log is specified, log messages will have a comment appended
2365 of the form::
2366 of the form::
2366
2367
2367 (grafted from CHANGESETHASH)
2368 (grafted from CHANGESETHASH)
2368
2369
2369 If --force is specified, revisions will be grafted even if they
2370 If --force is specified, revisions will be grafted even if they
2370 are already ancestors of, or have been grafted to, the destination.
2371 are already ancestors of, or have been grafted to, the destination.
2371 This is useful when the revisions have since been backed out.
2372 This is useful when the revisions have since been backed out.
2372
2373
2373 If a graft merge results in conflicts, the graft process is
2374 If a graft merge results in conflicts, the graft process is
2374 interrupted so that the current merge can be manually resolved.
2375 interrupted so that the current merge can be manually resolved.
2375 Once all conflicts are addressed, the graft process can be
2376 Once all conflicts are addressed, the graft process can be
2376 continued with the -c/--continue option.
2377 continued with the -c/--continue option.
2377
2378
2378 The -c/--continue option reapplies all the earlier options.
2379 The -c/--continue option reapplies all the earlier options.
2379
2380
2380 .. container:: verbose
2381 .. container:: verbose
2381
2382
2382 The --base option exposes more of how graft internally uses merge with a
2383 The --base option exposes more of how graft internally uses merge with a
2383 custom base revision. --base can be used to specify another ancestor than
2384 custom base revision. --base can be used to specify another ancestor than
2384 the first and only parent.
2385 the first and only parent.
2385
2386
2386 The command::
2387 The command::
2387
2388
2388 hg graft -r 345 --base 234
2389 hg graft -r 345 --base 234
2389
2390
2390 is thus pretty much the same as::
2391 is thus pretty much the same as::
2391
2392
2392 hg diff -r 234 -r 345 | hg import
2393 hg diff -r 234 -r 345 | hg import
2393
2394
2394 but using merge to resolve conflicts and track moved files.
2395 but using merge to resolve conflicts and track moved files.
2395
2396
2396 The result of a merge can thus be backported as a single commit by
2397 The result of a merge can thus be backported as a single commit by
2397 specifying one of the merge parents as base, and thus effectively
2398 specifying one of the merge parents as base, and thus effectively
2398 grafting the changes from the other side.
2399 grafting the changes from the other side.
2399
2400
2400 It is also possible to collapse multiple changesets and clean up history
2401 It is also possible to collapse multiple changesets and clean up history
2401 by specifying another ancestor as base, much like rebase --collapse
2402 by specifying another ancestor as base, much like rebase --collapse
2402 --keep.
2403 --keep.
2403
2404
2404 The commit message can be tweaked after the fact using commit --amend .
2405 The commit message can be tweaked after the fact using commit --amend .
2405
2406
2406 For using non-ancestors as the base to backout changes, see the backout
2407 For using non-ancestors as the base to backout changes, see the backout
2407 command and the hidden --parent option.
2408 command and the hidden --parent option.
2408
2409
2409 .. container:: verbose
2410 .. container:: verbose
2410
2411
2411 Examples:
2412 Examples:
2412
2413
2413 - copy a single change to the stable branch and edit its description::
2414 - copy a single change to the stable branch and edit its description::
2414
2415
2415 hg update stable
2416 hg update stable
2416 hg graft --edit 9393
2417 hg graft --edit 9393
2417
2418
2418 - graft a range of changesets with one exception, updating dates::
2419 - graft a range of changesets with one exception, updating dates::
2419
2420
2420 hg graft -D "2085::2093 and not 2091"
2421 hg graft -D "2085::2093 and not 2091"
2421
2422
2422 - continue a graft after resolving conflicts::
2423 - continue a graft after resolving conflicts::
2423
2424
2424 hg graft -c
2425 hg graft -c
2425
2426
2426 - show the source of a grafted changeset::
2427 - show the source of a grafted changeset::
2427
2428
2428 hg log --debug -r .
2429 hg log --debug -r .
2429
2430
2430 - show revisions sorted by date::
2431 - show revisions sorted by date::
2431
2432
2432 hg log -r "sort(all(), date)"
2433 hg log -r "sort(all(), date)"
2433
2434
2434 - backport the result of a merge as a single commit::
2435 - backport the result of a merge as a single commit::
2435
2436
2436 hg graft -r 123 --base 123^
2437 hg graft -r 123 --base 123^
2437
2438
2438 - land a feature branch as one changeset::
2439 - land a feature branch as one changeset::
2439
2440
2440 hg up -cr default
2441 hg up -cr default
2441 hg graft -r featureX --base "ancestor('featureX', 'default')"
2442 hg graft -r featureX --base "ancestor('featureX', 'default')"
2442
2443
2443 See :hg:`help revisions` for more about specifying revisions.
2444 See :hg:`help revisions` for more about specifying revisions.
2444
2445
2445 Returns 0 on successful completion.
2446 Returns 0 on successful completion.
2446 '''
2447 '''
2447 with repo.wlock():
2448 with repo.wlock():
2448 return _dograft(ui, repo, *revs, **opts)
2449 return _dograft(ui, repo, *revs, **opts)
2449
2450
2450 def _dograft(ui, repo, *revs, **opts):
2451 def _dograft(ui, repo, *revs, **opts):
2451 opts = pycompat.byteskwargs(opts)
2452 opts = pycompat.byteskwargs(opts)
2452 if revs and opts.get('rev'):
2453 if revs and opts.get('rev'):
2453 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2454 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2454 'revision ordering!\n'))
2455 'revision ordering!\n'))
2455
2456
2456 revs = list(revs)
2457 revs = list(revs)
2457 revs.extend(opts.get('rev'))
2458 revs.extend(opts.get('rev'))
2458 basectx = None
2459 basectx = None
2459 if opts.get('base'):
2460 if opts.get('base'):
2460 basectx = scmutil.revsingle(repo, opts['base'], None)
2461 basectx = scmutil.revsingle(repo, opts['base'], None)
2461 # a dict of data to be stored in state file
2462 # a dict of data to be stored in state file
2462 statedata = {}
2463 statedata = {}
2463 # list of new nodes created by ongoing graft
2464 # list of new nodes created by ongoing graft
2464 statedata['newnodes'] = []
2465 statedata['newnodes'] = []
2465
2466
2466 if opts.get('user') and opts.get('currentuser'):
2467 if opts.get('user') and opts.get('currentuser'):
2467 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2468 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2468 if opts.get('date') and opts.get('currentdate'):
2469 if opts.get('date') and opts.get('currentdate'):
2469 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2470 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2470 if not opts.get('user') and opts.get('currentuser'):
2471 if not opts.get('user') and opts.get('currentuser'):
2471 opts['user'] = ui.username()
2472 opts['user'] = ui.username()
2472 if not opts.get('date') and opts.get('currentdate'):
2473 if not opts.get('date') and opts.get('currentdate'):
2473 opts['date'] = "%d %d" % dateutil.makedate()
2474 opts['date'] = "%d %d" % dateutil.makedate()
2474
2475
2475 editor = cmdutil.getcommiteditor(editform='graft',
2476 editor = cmdutil.getcommiteditor(editform='graft',
2476 **pycompat.strkwargs(opts))
2477 **pycompat.strkwargs(opts))
2477
2478
2478 cont = False
2479 cont = False
2479 if opts.get('no_commit'):
2480 if opts.get('no_commit'):
2480 if opts.get('edit'):
2481 if opts.get('edit'):
2481 raise error.Abort(_("cannot specify --no-commit and "
2482 raise error.Abort(_("cannot specify --no-commit and "
2482 "--edit together"))
2483 "--edit together"))
2483 if opts.get('currentuser'):
2484 if opts.get('currentuser'):
2484 raise error.Abort(_("cannot specify --no-commit and "
2485 raise error.Abort(_("cannot specify --no-commit and "
2485 "--currentuser together"))
2486 "--currentuser together"))
2486 if opts.get('currentdate'):
2487 if opts.get('currentdate'):
2487 raise error.Abort(_("cannot specify --no-commit and "
2488 raise error.Abort(_("cannot specify --no-commit and "
2488 "--currentdate together"))
2489 "--currentdate together"))
2489 if opts.get('log'):
2490 if opts.get('log'):
2490 raise error.Abort(_("cannot specify --no-commit and "
2491 raise error.Abort(_("cannot specify --no-commit and "
2491 "--log together"))
2492 "--log together"))
2492
2493
2493 graftstate = statemod.cmdstate(repo, 'graftstate')
2494 graftstate = statemod.cmdstate(repo, 'graftstate')
2494
2495
2495 if opts.get('stop'):
2496 if opts.get('stop'):
2496 if opts.get('continue'):
2497 if opts.get('continue'):
2497 raise error.Abort(_("cannot use '--continue' and "
2498 raise error.Abort(_("cannot use '--continue' and "
2498 "'--stop' together"))
2499 "'--stop' together"))
2499 if opts.get('abort'):
2500 if opts.get('abort'):
2500 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2501 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2501
2502
2502 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2503 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2503 opts.get('date'), opts.get('currentdate'),
2504 opts.get('date'), opts.get('currentdate'),
2504 opts.get('currentuser'), opts.get('rev'))):
2505 opts.get('currentuser'), opts.get('rev'))):
2505 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2506 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2506 return _stopgraft(ui, repo, graftstate)
2507 return _stopgraft(ui, repo, graftstate)
2507 elif opts.get('abort'):
2508 elif opts.get('abort'):
2508 if opts.get('continue'):
2509 if opts.get('continue'):
2509 raise error.Abort(_("cannot use '--continue' and "
2510 raise error.Abort(_("cannot use '--continue' and "
2510 "'--abort' together"))
2511 "'--abort' together"))
2511 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2512 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2512 opts.get('date'), opts.get('currentdate'),
2513 opts.get('date'), opts.get('currentdate'),
2513 opts.get('currentuser'), opts.get('rev'))):
2514 opts.get('currentuser'), opts.get('rev'))):
2514 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2515 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2515
2516
2516 return cmdutil.abortgraft(ui, repo, graftstate)
2517 return cmdutil.abortgraft(ui, repo, graftstate)
2517 elif opts.get('continue'):
2518 elif opts.get('continue'):
2518 cont = True
2519 cont = True
2519 if revs:
2520 if revs:
2520 raise error.Abort(_("can't specify --continue and revisions"))
2521 raise error.Abort(_("can't specify --continue and revisions"))
2521 # read in unfinished revisions
2522 # read in unfinished revisions
2522 if graftstate.exists():
2523 if graftstate.exists():
2523 statedata = cmdutil.readgraftstate(repo, graftstate)
2524 statedata = cmdutil.readgraftstate(repo, graftstate)
2524 if statedata.get('date'):
2525 if statedata.get('date'):
2525 opts['date'] = statedata['date']
2526 opts['date'] = statedata['date']
2526 if statedata.get('user'):
2527 if statedata.get('user'):
2527 opts['user'] = statedata['user']
2528 opts['user'] = statedata['user']
2528 if statedata.get('log'):
2529 if statedata.get('log'):
2529 opts['log'] = True
2530 opts['log'] = True
2530 if statedata.get('no_commit'):
2531 if statedata.get('no_commit'):
2531 opts['no_commit'] = statedata.get('no_commit')
2532 opts['no_commit'] = statedata.get('no_commit')
2532 nodes = statedata['nodes']
2533 nodes = statedata['nodes']
2533 revs = [repo[node].rev() for node in nodes]
2534 revs = [repo[node].rev() for node in nodes]
2534 else:
2535 else:
2535 cmdutil.wrongtooltocontinue(repo, _('graft'))
2536 cmdutil.wrongtooltocontinue(repo, _('graft'))
2536 else:
2537 else:
2537 if not revs:
2538 if not revs:
2538 raise error.Abort(_('no revisions specified'))
2539 raise error.Abort(_('no revisions specified'))
2539 cmdutil.checkunfinished(repo)
2540 cmdutil.checkunfinished(repo)
2540 cmdutil.bailifchanged(repo)
2541 cmdutil.bailifchanged(repo)
2541 revs = scmutil.revrange(repo, revs)
2542 revs = scmutil.revrange(repo, revs)
2542
2543
2543 skipped = set()
2544 skipped = set()
2544 if basectx is None:
2545 if basectx is None:
2545 # check for merges
2546 # check for merges
2546 for rev in repo.revs('%ld and merge()', revs):
2547 for rev in repo.revs('%ld and merge()', revs):
2547 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2548 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2548 skipped.add(rev)
2549 skipped.add(rev)
2549 revs = [r for r in revs if r not in skipped]
2550 revs = [r for r in revs if r not in skipped]
2550 if not revs:
2551 if not revs:
2551 return -1
2552 return -1
2552 if basectx is not None and len(revs) != 1:
2553 if basectx is not None and len(revs) != 1:
2553 raise error.Abort(_('only one revision allowed with --base '))
2554 raise error.Abort(_('only one revision allowed with --base '))
2554
2555
2555 # Don't check in the --continue case, in effect retaining --force across
2556 # Don't check in the --continue case, in effect retaining --force across
2556 # --continues. That's because without --force, any revisions we decided to
2557 # --continues. That's because without --force, any revisions we decided to
2557 # skip would have been filtered out here, so they wouldn't have made their
2558 # skip would have been filtered out here, so they wouldn't have made their
2558 # way to the graftstate. With --force, any revisions we would have otherwise
2559 # way to the graftstate. With --force, any revisions we would have otherwise
2559 # skipped would not have been filtered out, and if they hadn't been applied
2560 # skipped would not have been filtered out, and if they hadn't been applied
2560 # already, they'd have been in the graftstate.
2561 # already, they'd have been in the graftstate.
2561 if not (cont or opts.get('force')) and basectx is None:
2562 if not (cont or opts.get('force')) and basectx is None:
2562 # check for ancestors of dest branch
2563 # check for ancestors of dest branch
2563 crev = repo['.'].rev()
2564 crev = repo['.'].rev()
2564 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2565 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2565 # XXX make this lazy in the future
2566 # XXX make this lazy in the future
2566 # don't mutate while iterating, create a copy
2567 # don't mutate while iterating, create a copy
2567 for rev in list(revs):
2568 for rev in list(revs):
2568 if rev in ancestors:
2569 if rev in ancestors:
2569 ui.warn(_('skipping ancestor revision %d:%s\n') %
2570 ui.warn(_('skipping ancestor revision %d:%s\n') %
2570 (rev, repo[rev]))
2571 (rev, repo[rev]))
2571 # XXX remove on list is slow
2572 # XXX remove on list is slow
2572 revs.remove(rev)
2573 revs.remove(rev)
2573 if not revs:
2574 if not revs:
2574 return -1
2575 return -1
2575
2576
2576 # analyze revs for earlier grafts
2577 # analyze revs for earlier grafts
2577 ids = {}
2578 ids = {}
2578 for ctx in repo.set("%ld", revs):
2579 for ctx in repo.set("%ld", revs):
2579 ids[ctx.hex()] = ctx.rev()
2580 ids[ctx.hex()] = ctx.rev()
2580 n = ctx.extra().get('source')
2581 n = ctx.extra().get('source')
2581 if n:
2582 if n:
2582 ids[n] = ctx.rev()
2583 ids[n] = ctx.rev()
2583
2584
2584 # check ancestors for earlier grafts
2585 # check ancestors for earlier grafts
2585 ui.debug('scanning for duplicate grafts\n')
2586 ui.debug('scanning for duplicate grafts\n')
2586
2587
2587 # The only changesets we can be sure doesn't contain grafts of any
2588 # The only changesets we can be sure doesn't contain grafts of any
2588 # revs, are the ones that are common ancestors of *all* revs:
2589 # revs, are the ones that are common ancestors of *all* revs:
2589 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2590 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2590 ctx = repo[rev]
2591 ctx = repo[rev]
2591 n = ctx.extra().get('source')
2592 n = ctx.extra().get('source')
2592 if n in ids:
2593 if n in ids:
2593 try:
2594 try:
2594 r = repo[n].rev()
2595 r = repo[n].rev()
2595 except error.RepoLookupError:
2596 except error.RepoLookupError:
2596 r = None
2597 r = None
2597 if r in revs:
2598 if r in revs:
2598 ui.warn(_('skipping revision %d:%s '
2599 ui.warn(_('skipping revision %d:%s '
2599 '(already grafted to %d:%s)\n')
2600 '(already grafted to %d:%s)\n')
2600 % (r, repo[r], rev, ctx))
2601 % (r, repo[r], rev, ctx))
2601 revs.remove(r)
2602 revs.remove(r)
2602 elif ids[n] in revs:
2603 elif ids[n] in revs:
2603 if r is None:
2604 if r is None:
2604 ui.warn(_('skipping already grafted revision %d:%s '
2605 ui.warn(_('skipping already grafted revision %d:%s '
2605 '(%d:%s also has unknown origin %s)\n')
2606 '(%d:%s also has unknown origin %s)\n')
2606 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2607 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2607 else:
2608 else:
2608 ui.warn(_('skipping already grafted revision %d:%s '
2609 ui.warn(_('skipping already grafted revision %d:%s '
2609 '(%d:%s also has origin %d:%s)\n')
2610 '(%d:%s also has origin %d:%s)\n')
2610 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2611 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2611 revs.remove(ids[n])
2612 revs.remove(ids[n])
2612 elif ctx.hex() in ids:
2613 elif ctx.hex() in ids:
2613 r = ids[ctx.hex()]
2614 r = ids[ctx.hex()]
2614 if r in revs:
2615 if r in revs:
2615 ui.warn(_('skipping already grafted revision %d:%s '
2616 ui.warn(_('skipping already grafted revision %d:%s '
2616 '(was grafted from %d:%s)\n') %
2617 '(was grafted from %d:%s)\n') %
2617 (r, repo[r], rev, ctx))
2618 (r, repo[r], rev, ctx))
2618 revs.remove(r)
2619 revs.remove(r)
2619 if not revs:
2620 if not revs:
2620 return -1
2621 return -1
2621
2622
2622 if opts.get('no_commit'):
2623 if opts.get('no_commit'):
2623 statedata['no_commit'] = True
2624 statedata['no_commit'] = True
2624 for pos, ctx in enumerate(repo.set("%ld", revs)):
2625 for pos, ctx in enumerate(repo.set("%ld", revs)):
2625 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2626 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2626 ctx.description().split('\n', 1)[0])
2627 ctx.description().split('\n', 1)[0])
2627 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2628 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2628 if names:
2629 if names:
2629 desc += ' (%s)' % ' '.join(names)
2630 desc += ' (%s)' % ' '.join(names)
2630 ui.status(_('grafting %s\n') % desc)
2631 ui.status(_('grafting %s\n') % desc)
2631 if opts.get('dry_run'):
2632 if opts.get('dry_run'):
2632 continue
2633 continue
2633
2634
2634 source = ctx.extra().get('source')
2635 source = ctx.extra().get('source')
2635 extra = {}
2636 extra = {}
2636 if source:
2637 if source:
2637 extra['source'] = source
2638 extra['source'] = source
2638 extra['intermediate-source'] = ctx.hex()
2639 extra['intermediate-source'] = ctx.hex()
2639 else:
2640 else:
2640 extra['source'] = ctx.hex()
2641 extra['source'] = ctx.hex()
2641 user = ctx.user()
2642 user = ctx.user()
2642 if opts.get('user'):
2643 if opts.get('user'):
2643 user = opts['user']
2644 user = opts['user']
2644 statedata['user'] = user
2645 statedata['user'] = user
2645 date = ctx.date()
2646 date = ctx.date()
2646 if opts.get('date'):
2647 if opts.get('date'):
2647 date = opts['date']
2648 date = opts['date']
2648 statedata['date'] = date
2649 statedata['date'] = date
2649 message = ctx.description()
2650 message = ctx.description()
2650 if opts.get('log'):
2651 if opts.get('log'):
2651 message += '\n(grafted from %s)' % ctx.hex()
2652 message += '\n(grafted from %s)' % ctx.hex()
2652 statedata['log'] = True
2653 statedata['log'] = True
2653
2654
2654 # we don't merge the first commit when continuing
2655 # we don't merge the first commit when continuing
2655 if not cont:
2656 if not cont:
2656 # perform the graft merge with p1(rev) as 'ancestor'
2657 # perform the graft merge with p1(rev) as 'ancestor'
2657 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2658 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2658 base = ctx.p1() if basectx is None else basectx
2659 base = ctx.p1() if basectx is None else basectx
2659 with ui.configoverride(overrides, 'graft'):
2660 with ui.configoverride(overrides, 'graft'):
2660 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2661 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2661 # report any conflicts
2662 # report any conflicts
2662 if stats.unresolvedcount > 0:
2663 if stats.unresolvedcount > 0:
2663 # write out state for --continue
2664 # write out state for --continue
2664 nodes = [repo[rev].hex() for rev in revs[pos:]]
2665 nodes = [repo[rev].hex() for rev in revs[pos:]]
2665 statedata['nodes'] = nodes
2666 statedata['nodes'] = nodes
2666 stateversion = 1
2667 stateversion = 1
2667 graftstate.save(stateversion, statedata)
2668 graftstate.save(stateversion, statedata)
2668 hint = _("use 'hg resolve' and 'hg graft --continue'")
2669 hint = _("use 'hg resolve' and 'hg graft --continue'")
2669 raise error.Abort(
2670 raise error.Abort(
2670 _("unresolved conflicts, can't continue"),
2671 _("unresolved conflicts, can't continue"),
2671 hint=hint)
2672 hint=hint)
2672 else:
2673 else:
2673 cont = False
2674 cont = False
2674
2675
2675 # commit if --no-commit is false
2676 # commit if --no-commit is false
2676 if not opts.get('no_commit'):
2677 if not opts.get('no_commit'):
2677 node = repo.commit(text=message, user=user, date=date, extra=extra,
2678 node = repo.commit(text=message, user=user, date=date, extra=extra,
2678 editor=editor)
2679 editor=editor)
2679 if node is None:
2680 if node is None:
2680 ui.warn(
2681 ui.warn(
2681 _('note: graft of %d:%s created no changes to commit\n') %
2682 _('note: graft of %d:%s created no changes to commit\n') %
2682 (ctx.rev(), ctx))
2683 (ctx.rev(), ctx))
2683 # checking that newnodes exist because old state files won't have it
2684 # checking that newnodes exist because old state files won't have it
2684 elif statedata.get('newnodes') is not None:
2685 elif statedata.get('newnodes') is not None:
2685 statedata['newnodes'].append(node)
2686 statedata['newnodes'].append(node)
2686
2687
2687 # remove state when we complete successfully
2688 # remove state when we complete successfully
2688 if not opts.get('dry_run'):
2689 if not opts.get('dry_run'):
2689 graftstate.delete()
2690 graftstate.delete()
2690
2691
2691 return 0
2692 return 0
2692
2693
2693 def _stopgraft(ui, repo, graftstate):
2694 def _stopgraft(ui, repo, graftstate):
2694 """stop the interrupted graft"""
2695 """stop the interrupted graft"""
2695 if not graftstate.exists():
2696 if not graftstate.exists():
2696 raise error.Abort(_("no interrupted graft found"))
2697 raise error.Abort(_("no interrupted graft found"))
2697 pctx = repo['.']
2698 pctx = repo['.']
2698 hg.updaterepo(repo, pctx.node(), overwrite=True)
2699 hg.updaterepo(repo, pctx.node(), overwrite=True)
2699 graftstate.delete()
2700 graftstate.delete()
2700 ui.status(_("stopped the interrupted graft\n"))
2701 ui.status(_("stopped the interrupted graft\n"))
2701 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2702 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2702 return 0
2703 return 0
2703
2704
2704 statemod.addunfinished(
2705 statemod.addunfinished(
2705 'graft', fname='graftstate', clearable=True, stopflag=True,
2706 'graft', fname='graftstate', clearable=True, stopflag=True,
2706 continueflag=True, abortfunc=cmdutil.hgabortgraft,
2707 continueflag=True, abortfunc=cmdutil.hgabortgraft,
2707 cmdhint=_("use 'hg graft --continue' or 'hg graft --stop' to stop")
2708 cmdhint=_("use 'hg graft --continue' or 'hg graft --stop' to stop")
2708 )
2709 )
2709
2710
2710 @command('grep',
2711 @command('grep',
2711 [('0', 'print0', None, _('end fields with NUL')),
2712 [('0', 'print0', None, _('end fields with NUL')),
2712 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2713 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2713 ('', 'diff', None, _('print all revisions when the term was introduced '
2714 ('', 'diff', None, _('print all revisions when the term was introduced '
2714 'or removed')),
2715 'or removed')),
2715 ('a', 'text', None, _('treat all files as text')),
2716 ('a', 'text', None, _('treat all files as text')),
2716 ('f', 'follow', None,
2717 ('f', 'follow', None,
2717 _('follow changeset history,'
2718 _('follow changeset history,'
2718 ' or file history across copies and renames')),
2719 ' or file history across copies and renames')),
2719 ('i', 'ignore-case', None, _('ignore case when matching')),
2720 ('i', 'ignore-case', None, _('ignore case when matching')),
2720 ('l', 'files-with-matches', None,
2721 ('l', 'files-with-matches', None,
2721 _('print only filenames and revisions that match')),
2722 _('print only filenames and revisions that match')),
2722 ('n', 'line-number', None, _('print matching line numbers')),
2723 ('n', 'line-number', None, _('print matching line numbers')),
2723 ('r', 'rev', [],
2724 ('r', 'rev', [],
2724 _('only search files changed within revision range'), _('REV')),
2725 _('only search files changed within revision range'), _('REV')),
2725 ('', 'all-files', None,
2726 ('', 'all-files', None,
2726 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2727 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2727 ('u', 'user', None, _('list the author (long with -v)')),
2728 ('u', 'user', None, _('list the author (long with -v)')),
2728 ('d', 'date', None, _('list the date (short with -q)')),
2729 ('d', 'date', None, _('list the date (short with -q)')),
2729 ] + formatteropts + walkopts,
2730 ] + formatteropts + walkopts,
2730 _('[OPTION]... PATTERN [FILE]...'),
2731 _('[OPTION]... PATTERN [FILE]...'),
2731 helpcategory=command.CATEGORY_FILE_CONTENTS,
2732 helpcategory=command.CATEGORY_FILE_CONTENTS,
2732 inferrepo=True,
2733 inferrepo=True,
2733 intents={INTENT_READONLY})
2734 intents={INTENT_READONLY})
2734 def grep(ui, repo, pattern, *pats, **opts):
2735 def grep(ui, repo, pattern, *pats, **opts):
2735 """search revision history for a pattern in specified files
2736 """search revision history for a pattern in specified files
2736
2737
2737 Search revision history for a regular expression in the specified
2738 Search revision history for a regular expression in the specified
2738 files or the entire project.
2739 files or the entire project.
2739
2740
2740 By default, grep prints the most recent revision number for each
2741 By default, grep prints the most recent revision number for each
2741 file in which it finds a match. To get it to print every revision
2742 file in which it finds a match. To get it to print every revision
2742 that contains a change in match status ("-" for a match that becomes
2743 that contains a change in match status ("-" for a match that becomes
2743 a non-match, or "+" for a non-match that becomes a match), use the
2744 a non-match, or "+" for a non-match that becomes a match), use the
2744 --diff flag.
2745 --diff flag.
2745
2746
2746 PATTERN can be any Python (roughly Perl-compatible) regular
2747 PATTERN can be any Python (roughly Perl-compatible) regular
2747 expression.
2748 expression.
2748
2749
2749 If no FILEs are specified (and -f/--follow isn't set), all files in
2750 If no FILEs are specified (and -f/--follow isn't set), all files in
2750 the repository are searched, including those that don't exist in the
2751 the repository are searched, including those that don't exist in the
2751 current branch or have been deleted in a prior changeset.
2752 current branch or have been deleted in a prior changeset.
2752
2753
2753 .. container:: verbose
2754 .. container:: verbose
2754
2755
2755 Template:
2756 Template:
2756
2757
2757 The following keywords are supported in addition to the common template
2758 The following keywords are supported in addition to the common template
2758 keywords and functions. See also :hg:`help templates`.
2759 keywords and functions. See also :hg:`help templates`.
2759
2760
2760 :change: String. Character denoting insertion ``+`` or removal ``-``.
2761 :change: String. Character denoting insertion ``+`` or removal ``-``.
2761 Available if ``--diff`` is specified.
2762 Available if ``--diff`` is specified.
2762 :lineno: Integer. Line number of the match.
2763 :lineno: Integer. Line number of the match.
2763 :path: String. Repository-absolute path of the file.
2764 :path: String. Repository-absolute path of the file.
2764 :texts: List of text chunks.
2765 :texts: List of text chunks.
2765
2766
2766 And each entry of ``{texts}`` provides the following sub-keywords.
2767 And each entry of ``{texts}`` provides the following sub-keywords.
2767
2768
2768 :matched: Boolean. True if the chunk matches the specified pattern.
2769 :matched: Boolean. True if the chunk matches the specified pattern.
2769 :text: String. Chunk content.
2770 :text: String. Chunk content.
2770
2771
2771 See :hg:`help templates.operators` for the list expansion syntax.
2772 See :hg:`help templates.operators` for the list expansion syntax.
2772
2773
2773 Returns 0 if a match is found, 1 otherwise.
2774 Returns 0 if a match is found, 1 otherwise.
2774 """
2775 """
2775 opts = pycompat.byteskwargs(opts)
2776 opts = pycompat.byteskwargs(opts)
2776 diff = opts.get('all') or opts.get('diff')
2777 diff = opts.get('all') or opts.get('diff')
2777 all_files = opts.get('all_files')
2778 all_files = opts.get('all_files')
2778 if diff and opts.get('all_files'):
2779 if diff and opts.get('all_files'):
2779 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2780 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2780 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2781 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2781 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2782 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2782 # experimental config: commands.grep.all-files
2783 # experimental config: commands.grep.all-files
2783 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2784 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2784 plaingrep = opts.get('all_files') and not opts.get('rev')
2785 plaingrep = opts.get('all_files') and not opts.get('rev')
2785 if plaingrep:
2786 if plaingrep:
2786 opts['rev'] = ['wdir()']
2787 opts['rev'] = ['wdir()']
2787
2788
2788 reflags = re.M
2789 reflags = re.M
2789 if opts.get('ignore_case'):
2790 if opts.get('ignore_case'):
2790 reflags |= re.I
2791 reflags |= re.I
2791 try:
2792 try:
2792 regexp = util.re.compile(pattern, reflags)
2793 regexp = util.re.compile(pattern, reflags)
2793 except re.error as inst:
2794 except re.error as inst:
2794 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2795 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2795 return 1
2796 return 1
2796 sep, eol = ':', '\n'
2797 sep, eol = ':', '\n'
2797 if opts.get('print0'):
2798 if opts.get('print0'):
2798 sep = eol = '\0'
2799 sep = eol = '\0'
2799
2800
2800 getfile = util.lrucachefunc(repo.file)
2801 getfile = util.lrucachefunc(repo.file)
2801
2802
2802 def matchlines(body):
2803 def matchlines(body):
2803 begin = 0
2804 begin = 0
2804 linenum = 0
2805 linenum = 0
2805 while begin < len(body):
2806 while begin < len(body):
2806 match = regexp.search(body, begin)
2807 match = regexp.search(body, begin)
2807 if not match:
2808 if not match:
2808 break
2809 break
2809 mstart, mend = match.span()
2810 mstart, mend = match.span()
2810 linenum += body.count('\n', begin, mstart) + 1
2811 linenum += body.count('\n', begin, mstart) + 1
2811 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2812 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2812 begin = body.find('\n', mend) + 1 or len(body) + 1
2813 begin = body.find('\n', mend) + 1 or len(body) + 1
2813 lend = begin - 1
2814 lend = begin - 1
2814 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2815 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2815
2816
2816 class linestate(object):
2817 class linestate(object):
2817 def __init__(self, line, linenum, colstart, colend):
2818 def __init__(self, line, linenum, colstart, colend):
2818 self.line = line
2819 self.line = line
2819 self.linenum = linenum
2820 self.linenum = linenum
2820 self.colstart = colstart
2821 self.colstart = colstart
2821 self.colend = colend
2822 self.colend = colend
2822
2823
2823 def __hash__(self):
2824 def __hash__(self):
2824 return hash((self.linenum, self.line))
2825 return hash((self.linenum, self.line))
2825
2826
2826 def __eq__(self, other):
2827 def __eq__(self, other):
2827 return self.line == other.line
2828 return self.line == other.line
2828
2829
2829 def findpos(self):
2830 def findpos(self):
2830 """Iterate all (start, end) indices of matches"""
2831 """Iterate all (start, end) indices of matches"""
2831 yield self.colstart, self.colend
2832 yield self.colstart, self.colend
2832 p = self.colend
2833 p = self.colend
2833 while p < len(self.line):
2834 while p < len(self.line):
2834 m = regexp.search(self.line, p)
2835 m = regexp.search(self.line, p)
2835 if not m:
2836 if not m:
2836 break
2837 break
2837 yield m.span()
2838 yield m.span()
2838 p = m.end()
2839 p = m.end()
2839
2840
2840 matches = {}
2841 matches = {}
2841 copies = {}
2842 copies = {}
2842 def grepbody(fn, rev, body):
2843 def grepbody(fn, rev, body):
2843 matches[rev].setdefault(fn, [])
2844 matches[rev].setdefault(fn, [])
2844 m = matches[rev][fn]
2845 m = matches[rev][fn]
2845 for lnum, cstart, cend, line in matchlines(body):
2846 for lnum, cstart, cend, line in matchlines(body):
2846 s = linestate(line, lnum, cstart, cend)
2847 s = linestate(line, lnum, cstart, cend)
2847 m.append(s)
2848 m.append(s)
2848
2849
2849 def difflinestates(a, b):
2850 def difflinestates(a, b):
2850 sm = difflib.SequenceMatcher(None, a, b)
2851 sm = difflib.SequenceMatcher(None, a, b)
2851 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2852 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2852 if tag == r'insert':
2853 if tag == r'insert':
2853 for i in pycompat.xrange(blo, bhi):
2854 for i in pycompat.xrange(blo, bhi):
2854 yield ('+', b[i])
2855 yield ('+', b[i])
2855 elif tag == r'delete':
2856 elif tag == r'delete':
2856 for i in pycompat.xrange(alo, ahi):
2857 for i in pycompat.xrange(alo, ahi):
2857 yield ('-', a[i])
2858 yield ('-', a[i])
2858 elif tag == r'replace':
2859 elif tag == r'replace':
2859 for i in pycompat.xrange(alo, ahi):
2860 for i in pycompat.xrange(alo, ahi):
2860 yield ('-', a[i])
2861 yield ('-', a[i])
2861 for i in pycompat.xrange(blo, bhi):
2862 for i in pycompat.xrange(blo, bhi):
2862 yield ('+', b[i])
2863 yield ('+', b[i])
2863
2864
2864 uipathfn = scmutil.getuipathfn(repo)
2865 uipathfn = scmutil.getuipathfn(repo)
2865 def display(fm, fn, ctx, pstates, states):
2866 def display(fm, fn, ctx, pstates, states):
2866 rev = scmutil.intrev(ctx)
2867 rev = scmutil.intrev(ctx)
2867 if fm.isplain():
2868 if fm.isplain():
2868 formatuser = ui.shortuser
2869 formatuser = ui.shortuser
2869 else:
2870 else:
2870 formatuser = pycompat.bytestr
2871 formatuser = pycompat.bytestr
2871 if ui.quiet:
2872 if ui.quiet:
2872 datefmt = '%Y-%m-%d'
2873 datefmt = '%Y-%m-%d'
2873 else:
2874 else:
2874 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2875 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2875 found = False
2876 found = False
2876 @util.cachefunc
2877 @util.cachefunc
2877 def binary():
2878 def binary():
2878 flog = getfile(fn)
2879 flog = getfile(fn)
2879 try:
2880 try:
2880 return stringutil.binary(flog.read(ctx.filenode(fn)))
2881 return stringutil.binary(flog.read(ctx.filenode(fn)))
2881 except error.WdirUnsupported:
2882 except error.WdirUnsupported:
2882 return ctx[fn].isbinary()
2883 return ctx[fn].isbinary()
2883
2884
2884 fieldnamemap = {'linenumber': 'lineno'}
2885 fieldnamemap = {'linenumber': 'lineno'}
2885 if diff:
2886 if diff:
2886 iter = difflinestates(pstates, states)
2887 iter = difflinestates(pstates, states)
2887 else:
2888 else:
2888 iter = [('', l) for l in states]
2889 iter = [('', l) for l in states]
2889 for change, l in iter:
2890 for change, l in iter:
2890 fm.startitem()
2891 fm.startitem()
2891 fm.context(ctx=ctx)
2892 fm.context(ctx=ctx)
2892 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2893 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2893 fm.plain(uipathfn(fn), label='grep.filename')
2894 fm.plain(uipathfn(fn), label='grep.filename')
2894
2895
2895 cols = [
2896 cols = [
2896 ('rev', '%d', rev, not plaingrep, ''),
2897 ('rev', '%d', rev, not plaingrep, ''),
2897 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2898 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2898 ]
2899 ]
2899 if diff:
2900 if diff:
2900 cols.append(
2901 cols.append(
2901 ('change', '%s', change, True,
2902 ('change', '%s', change, True,
2902 'grep.inserted ' if change == '+' else 'grep.deleted ')
2903 'grep.inserted ' if change == '+' else 'grep.deleted ')
2903 )
2904 )
2904 cols.extend([
2905 cols.extend([
2905 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2906 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2906 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2907 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2907 opts.get('date'), ''),
2908 opts.get('date'), ''),
2908 ])
2909 ])
2909 for name, fmt, data, cond, extra_label in cols:
2910 for name, fmt, data, cond, extra_label in cols:
2910 if cond:
2911 if cond:
2911 fm.plain(sep, label='grep.sep')
2912 fm.plain(sep, label='grep.sep')
2912 field = fieldnamemap.get(name, name)
2913 field = fieldnamemap.get(name, name)
2913 label = extra_label + ('grep.%s' % name)
2914 label = extra_label + ('grep.%s' % name)
2914 fm.condwrite(cond, field, fmt, data, label=label)
2915 fm.condwrite(cond, field, fmt, data, label=label)
2915 if not opts.get('files_with_matches'):
2916 if not opts.get('files_with_matches'):
2916 fm.plain(sep, label='grep.sep')
2917 fm.plain(sep, label='grep.sep')
2917 if not opts.get('text') and binary():
2918 if not opts.get('text') and binary():
2918 fm.plain(_(" Binary file matches"))
2919 fm.plain(_(" Binary file matches"))
2919 else:
2920 else:
2920 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2921 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2921 fm.plain(eol)
2922 fm.plain(eol)
2922 found = True
2923 found = True
2923 if opts.get('files_with_matches'):
2924 if opts.get('files_with_matches'):
2924 break
2925 break
2925 return found
2926 return found
2926
2927
2927 def displaymatches(fm, l):
2928 def displaymatches(fm, l):
2928 p = 0
2929 p = 0
2929 for s, e in l.findpos():
2930 for s, e in l.findpos():
2930 if p < s:
2931 if p < s:
2931 fm.startitem()
2932 fm.startitem()
2932 fm.write('text', '%s', l.line[p:s])
2933 fm.write('text', '%s', l.line[p:s])
2933 fm.data(matched=False)
2934 fm.data(matched=False)
2934 fm.startitem()
2935 fm.startitem()
2935 fm.write('text', '%s', l.line[s:e], label='grep.match')
2936 fm.write('text', '%s', l.line[s:e], label='grep.match')
2936 fm.data(matched=True)
2937 fm.data(matched=True)
2937 p = e
2938 p = e
2938 if p < len(l.line):
2939 if p < len(l.line):
2939 fm.startitem()
2940 fm.startitem()
2940 fm.write('text', '%s', l.line[p:])
2941 fm.write('text', '%s', l.line[p:])
2941 fm.data(matched=False)
2942 fm.data(matched=False)
2942 fm.end()
2943 fm.end()
2943
2944
2944 skip = set()
2945 skip = set()
2945 revfiles = {}
2946 revfiles = {}
2946 match = scmutil.match(repo[None], pats, opts)
2947 match = scmutil.match(repo[None], pats, opts)
2947 found = False
2948 found = False
2948 follow = opts.get('follow')
2949 follow = opts.get('follow')
2949
2950
2950 getrenamed = scmutil.getrenamedfn(repo)
2951 getrenamed = scmutil.getrenamedfn(repo)
2951 def prep(ctx, fns):
2952 def prep(ctx, fns):
2952 rev = ctx.rev()
2953 rev = ctx.rev()
2953 pctx = ctx.p1()
2954 pctx = ctx.p1()
2954 parent = pctx.rev()
2955 parent = pctx.rev()
2955 matches.setdefault(rev, {})
2956 matches.setdefault(rev, {})
2956 matches.setdefault(parent, {})
2957 matches.setdefault(parent, {})
2957 files = revfiles.setdefault(rev, [])
2958 files = revfiles.setdefault(rev, [])
2958 for fn in fns:
2959 for fn in fns:
2959 flog = getfile(fn)
2960 flog = getfile(fn)
2960 try:
2961 try:
2961 fnode = ctx.filenode(fn)
2962 fnode = ctx.filenode(fn)
2962 except error.LookupError:
2963 except error.LookupError:
2963 continue
2964 continue
2964
2965
2965 copy = None
2966 copy = None
2966 if follow:
2967 if follow:
2967 copy = getrenamed(fn, rev)
2968 copy = getrenamed(fn, rev)
2968 if copy:
2969 if copy:
2969 copies.setdefault(rev, {})[fn] = copy
2970 copies.setdefault(rev, {})[fn] = copy
2970 if fn in skip:
2971 if fn in skip:
2971 skip.add(copy)
2972 skip.add(copy)
2972 if fn in skip:
2973 if fn in skip:
2973 continue
2974 continue
2974 files.append(fn)
2975 files.append(fn)
2975
2976
2976 if fn not in matches[rev]:
2977 if fn not in matches[rev]:
2977 try:
2978 try:
2978 content = flog.read(fnode)
2979 content = flog.read(fnode)
2979 except error.WdirUnsupported:
2980 except error.WdirUnsupported:
2980 content = ctx[fn].data()
2981 content = ctx[fn].data()
2981 grepbody(fn, rev, content)
2982 grepbody(fn, rev, content)
2982
2983
2983 pfn = copy or fn
2984 pfn = copy or fn
2984 if pfn not in matches[parent]:
2985 if pfn not in matches[parent]:
2985 try:
2986 try:
2986 fnode = pctx.filenode(pfn)
2987 fnode = pctx.filenode(pfn)
2987 grepbody(pfn, parent, flog.read(fnode))
2988 grepbody(pfn, parent, flog.read(fnode))
2988 except error.LookupError:
2989 except error.LookupError:
2989 pass
2990 pass
2990
2991
2991 ui.pager('grep')
2992 ui.pager('grep')
2992 fm = ui.formatter('grep', opts)
2993 fm = ui.formatter('grep', opts)
2993 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2994 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2994 rev = ctx.rev()
2995 rev = ctx.rev()
2995 parent = ctx.p1().rev()
2996 parent = ctx.p1().rev()
2996 for fn in sorted(revfiles.get(rev, [])):
2997 for fn in sorted(revfiles.get(rev, [])):
2997 states = matches[rev][fn]
2998 states = matches[rev][fn]
2998 copy = copies.get(rev, {}).get(fn)
2999 copy = copies.get(rev, {}).get(fn)
2999 if fn in skip:
3000 if fn in skip:
3000 if copy:
3001 if copy:
3001 skip.add(copy)
3002 skip.add(copy)
3002 continue
3003 continue
3003 pstates = matches.get(parent, {}).get(copy or fn, [])
3004 pstates = matches.get(parent, {}).get(copy or fn, [])
3004 if pstates or states:
3005 if pstates or states:
3005 r = display(fm, fn, ctx, pstates, states)
3006 r = display(fm, fn, ctx, pstates, states)
3006 found = found or r
3007 found = found or r
3007 if r and not diff and not all_files:
3008 if r and not diff and not all_files:
3008 skip.add(fn)
3009 skip.add(fn)
3009 if copy:
3010 if copy:
3010 skip.add(copy)
3011 skip.add(copy)
3011 del revfiles[rev]
3012 del revfiles[rev]
3012 # We will keep the matches dict for the duration of the window
3013 # We will keep the matches dict for the duration of the window
3013 # clear the matches dict once the window is over
3014 # clear the matches dict once the window is over
3014 if not revfiles:
3015 if not revfiles:
3015 matches.clear()
3016 matches.clear()
3016 fm.end()
3017 fm.end()
3017
3018
3018 return not found
3019 return not found
3019
3020
3020 @command('heads',
3021 @command('heads',
3021 [('r', 'rev', '',
3022 [('r', 'rev', '',
3022 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3023 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3023 ('t', 'topo', False, _('show topological heads only')),
3024 ('t', 'topo', False, _('show topological heads only')),
3024 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3025 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3025 ('c', 'closed', False, _('show normal and closed branch heads')),
3026 ('c', 'closed', False, _('show normal and closed branch heads')),
3026 ] + templateopts,
3027 ] + templateopts,
3027 _('[-ct] [-r STARTREV] [REV]...'),
3028 _('[-ct] [-r STARTREV] [REV]...'),
3028 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3029 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3029 intents={INTENT_READONLY})
3030 intents={INTENT_READONLY})
3030 def heads(ui, repo, *branchrevs, **opts):
3031 def heads(ui, repo, *branchrevs, **opts):
3031 """show branch heads
3032 """show branch heads
3032
3033
3033 With no arguments, show all open branch heads in the repository.
3034 With no arguments, show all open branch heads in the repository.
3034 Branch heads are changesets that have no descendants on the
3035 Branch heads are changesets that have no descendants on the
3035 same branch. They are where development generally takes place and
3036 same branch. They are where development generally takes place and
3036 are the usual targets for update and merge operations.
3037 are the usual targets for update and merge operations.
3037
3038
3038 If one or more REVs are given, only open branch heads on the
3039 If one or more REVs are given, only open branch heads on the
3039 branches associated with the specified changesets are shown. This
3040 branches associated with the specified changesets are shown. This
3040 means that you can use :hg:`heads .` to see the heads on the
3041 means that you can use :hg:`heads .` to see the heads on the
3041 currently checked-out branch.
3042 currently checked-out branch.
3042
3043
3043 If -c/--closed is specified, also show branch heads marked closed
3044 If -c/--closed is specified, also show branch heads marked closed
3044 (see :hg:`commit --close-branch`).
3045 (see :hg:`commit --close-branch`).
3045
3046
3046 If STARTREV is specified, only those heads that are descendants of
3047 If STARTREV is specified, only those heads that are descendants of
3047 STARTREV will be displayed.
3048 STARTREV will be displayed.
3048
3049
3049 If -t/--topo is specified, named branch mechanics will be ignored and only
3050 If -t/--topo is specified, named branch mechanics will be ignored and only
3050 topological heads (changesets with no children) will be shown.
3051 topological heads (changesets with no children) will be shown.
3051
3052
3052 Returns 0 if matching heads are found, 1 if not.
3053 Returns 0 if matching heads are found, 1 if not.
3053 """
3054 """
3054
3055
3055 opts = pycompat.byteskwargs(opts)
3056 opts = pycompat.byteskwargs(opts)
3056 start = None
3057 start = None
3057 rev = opts.get('rev')
3058 rev = opts.get('rev')
3058 if rev:
3059 if rev:
3059 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3060 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3060 start = scmutil.revsingle(repo, rev, None).node()
3061 start = scmutil.revsingle(repo, rev, None).node()
3061
3062
3062 if opts.get('topo'):
3063 if opts.get('topo'):
3063 heads = [repo[h] for h in repo.heads(start)]
3064 heads = [repo[h] for h in repo.heads(start)]
3064 else:
3065 else:
3065 heads = []
3066 heads = []
3066 for branch in repo.branchmap():
3067 for branch in repo.branchmap():
3067 heads += repo.branchheads(branch, start, opts.get('closed'))
3068 heads += repo.branchheads(branch, start, opts.get('closed'))
3068 heads = [repo[h] for h in heads]
3069 heads = [repo[h] for h in heads]
3069
3070
3070 if branchrevs:
3071 if branchrevs:
3071 branches = set(repo[r].branch()
3072 branches = set(repo[r].branch()
3072 for r in scmutil.revrange(repo, branchrevs))
3073 for r in scmutil.revrange(repo, branchrevs))
3073 heads = [h for h in heads if h.branch() in branches]
3074 heads = [h for h in heads if h.branch() in branches]
3074
3075
3075 if opts.get('active') and branchrevs:
3076 if opts.get('active') and branchrevs:
3076 dagheads = repo.heads(start)
3077 dagheads = repo.heads(start)
3077 heads = [h for h in heads if h.node() in dagheads]
3078 heads = [h for h in heads if h.node() in dagheads]
3078
3079
3079 if branchrevs:
3080 if branchrevs:
3080 haveheads = set(h.branch() for h in heads)
3081 haveheads = set(h.branch() for h in heads)
3081 if branches - haveheads:
3082 if branches - haveheads:
3082 headless = ', '.join(b for b in branches - haveheads)
3083 headless = ', '.join(b for b in branches - haveheads)
3083 msg = _('no open branch heads found on branches %s')
3084 msg = _('no open branch heads found on branches %s')
3084 if opts.get('rev'):
3085 if opts.get('rev'):
3085 msg += _(' (started at %s)') % opts['rev']
3086 msg += _(' (started at %s)') % opts['rev']
3086 ui.warn((msg + '\n') % headless)
3087 ui.warn((msg + '\n') % headless)
3087
3088
3088 if not heads:
3089 if not heads:
3089 return 1
3090 return 1
3090
3091
3091 ui.pager('heads')
3092 ui.pager('heads')
3092 heads = sorted(heads, key=lambda x: -x.rev())
3093 heads = sorted(heads, key=lambda x: -x.rev())
3093 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3094 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3094 for ctx in heads:
3095 for ctx in heads:
3095 displayer.show(ctx)
3096 displayer.show(ctx)
3096 displayer.close()
3097 displayer.close()
3097
3098
3098 @command('help',
3099 @command('help',
3099 [('e', 'extension', None, _('show only help for extensions')),
3100 [('e', 'extension', None, _('show only help for extensions')),
3100 ('c', 'command', None, _('show only help for commands')),
3101 ('c', 'command', None, _('show only help for commands')),
3101 ('k', 'keyword', None, _('show topics matching keyword')),
3102 ('k', 'keyword', None, _('show topics matching keyword')),
3102 ('s', 'system', [],
3103 ('s', 'system', [],
3103 _('show help for specific platform(s)'), _('PLATFORM')),
3104 _('show help for specific platform(s)'), _('PLATFORM')),
3104 ],
3105 ],
3105 _('[-eck] [-s PLATFORM] [TOPIC]'),
3106 _('[-eck] [-s PLATFORM] [TOPIC]'),
3106 helpcategory=command.CATEGORY_HELP,
3107 helpcategory=command.CATEGORY_HELP,
3107 norepo=True,
3108 norepo=True,
3108 intents={INTENT_READONLY})
3109 intents={INTENT_READONLY})
3109 def help_(ui, name=None, **opts):
3110 def help_(ui, name=None, **opts):
3110 """show help for a given topic or a help overview
3111 """show help for a given topic or a help overview
3111
3112
3112 With no arguments, print a list of commands with short help messages.
3113 With no arguments, print a list of commands with short help messages.
3113
3114
3114 Given a topic, extension, or command name, print help for that
3115 Given a topic, extension, or command name, print help for that
3115 topic.
3116 topic.
3116
3117
3117 Returns 0 if successful.
3118 Returns 0 if successful.
3118 """
3119 """
3119
3120
3120 keep = opts.get(r'system') or []
3121 keep = opts.get(r'system') or []
3121 if len(keep) == 0:
3122 if len(keep) == 0:
3122 if pycompat.sysplatform.startswith('win'):
3123 if pycompat.sysplatform.startswith('win'):
3123 keep.append('windows')
3124 keep.append('windows')
3124 elif pycompat.sysplatform == 'OpenVMS':
3125 elif pycompat.sysplatform == 'OpenVMS':
3125 keep.append('vms')
3126 keep.append('vms')
3126 elif pycompat.sysplatform == 'plan9':
3127 elif pycompat.sysplatform == 'plan9':
3127 keep.append('plan9')
3128 keep.append('plan9')
3128 else:
3129 else:
3129 keep.append('unix')
3130 keep.append('unix')
3130 keep.append(pycompat.sysplatform.lower())
3131 keep.append(pycompat.sysplatform.lower())
3131 if ui.verbose:
3132 if ui.verbose:
3132 keep.append('verbose')
3133 keep.append('verbose')
3133
3134
3134 commands = sys.modules[__name__]
3135 commands = sys.modules[__name__]
3135 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3136 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3136 ui.pager('help')
3137 ui.pager('help')
3137 ui.write(formatted)
3138 ui.write(formatted)
3138
3139
3139
3140
3140 @command('identify|id',
3141 @command('identify|id',
3141 [('r', 'rev', '',
3142 [('r', 'rev', '',
3142 _('identify the specified revision'), _('REV')),
3143 _('identify the specified revision'), _('REV')),
3143 ('n', 'num', None, _('show local revision number')),
3144 ('n', 'num', None, _('show local revision number')),
3144 ('i', 'id', None, _('show global revision id')),
3145 ('i', 'id', None, _('show global revision id')),
3145 ('b', 'branch', None, _('show branch')),
3146 ('b', 'branch', None, _('show branch')),
3146 ('t', 'tags', None, _('show tags')),
3147 ('t', 'tags', None, _('show tags')),
3147 ('B', 'bookmarks', None, _('show bookmarks')),
3148 ('B', 'bookmarks', None, _('show bookmarks')),
3148 ] + remoteopts + formatteropts,
3149 ] + remoteopts + formatteropts,
3149 _('[-nibtB] [-r REV] [SOURCE]'),
3150 _('[-nibtB] [-r REV] [SOURCE]'),
3150 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3151 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3151 optionalrepo=True,
3152 optionalrepo=True,
3152 intents={INTENT_READONLY})
3153 intents={INTENT_READONLY})
3153 def identify(ui, repo, source=None, rev=None,
3154 def identify(ui, repo, source=None, rev=None,
3154 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3155 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3155 """identify the working directory or specified revision
3156 """identify the working directory or specified revision
3156
3157
3157 Print a summary identifying the repository state at REV using one or
3158 Print a summary identifying the repository state at REV using one or
3158 two parent hash identifiers, followed by a "+" if the working
3159 two parent hash identifiers, followed by a "+" if the working
3159 directory has uncommitted changes, the branch name (if not default),
3160 directory has uncommitted changes, the branch name (if not default),
3160 a list of tags, and a list of bookmarks.
3161 a list of tags, and a list of bookmarks.
3161
3162
3162 When REV is not given, print a summary of the current state of the
3163 When REV is not given, print a summary of the current state of the
3163 repository including the working directory. Specify -r. to get information
3164 repository including the working directory. Specify -r. to get information
3164 of the working directory parent without scanning uncommitted changes.
3165 of the working directory parent without scanning uncommitted changes.
3165
3166
3166 Specifying a path to a repository root or Mercurial bundle will
3167 Specifying a path to a repository root or Mercurial bundle will
3167 cause lookup to operate on that repository/bundle.
3168 cause lookup to operate on that repository/bundle.
3168
3169
3169 .. container:: verbose
3170 .. container:: verbose
3170
3171
3171 Template:
3172 Template:
3172
3173
3173 The following keywords are supported in addition to the common template
3174 The following keywords are supported in addition to the common template
3174 keywords and functions. See also :hg:`help templates`.
3175 keywords and functions. See also :hg:`help templates`.
3175
3176
3176 :dirty: String. Character ``+`` denoting if the working directory has
3177 :dirty: String. Character ``+`` denoting if the working directory has
3177 uncommitted changes.
3178 uncommitted changes.
3178 :id: String. One or two nodes, optionally followed by ``+``.
3179 :id: String. One or two nodes, optionally followed by ``+``.
3179 :parents: List of strings. Parent nodes of the changeset.
3180 :parents: List of strings. Parent nodes of the changeset.
3180
3181
3181 Examples:
3182 Examples:
3182
3183
3183 - generate a build identifier for the working directory::
3184 - generate a build identifier for the working directory::
3184
3185
3185 hg id --id > build-id.dat
3186 hg id --id > build-id.dat
3186
3187
3187 - find the revision corresponding to a tag::
3188 - find the revision corresponding to a tag::
3188
3189
3189 hg id -n -r 1.3
3190 hg id -n -r 1.3
3190
3191
3191 - check the most recent revision of a remote repository::
3192 - check the most recent revision of a remote repository::
3192
3193
3193 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3194 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3194
3195
3195 See :hg:`log` for generating more information about specific revisions,
3196 See :hg:`log` for generating more information about specific revisions,
3196 including full hash identifiers.
3197 including full hash identifiers.
3197
3198
3198 Returns 0 if successful.
3199 Returns 0 if successful.
3199 """
3200 """
3200
3201
3201 opts = pycompat.byteskwargs(opts)
3202 opts = pycompat.byteskwargs(opts)
3202 if not repo and not source:
3203 if not repo and not source:
3203 raise error.Abort(_("there is no Mercurial repository here "
3204 raise error.Abort(_("there is no Mercurial repository here "
3204 "(.hg not found)"))
3205 "(.hg not found)"))
3205
3206
3206 default = not (num or id or branch or tags or bookmarks)
3207 default = not (num or id or branch or tags or bookmarks)
3207 output = []
3208 output = []
3208 revs = []
3209 revs = []
3209
3210
3210 if source:
3211 if source:
3211 source, branches = hg.parseurl(ui.expandpath(source))
3212 source, branches = hg.parseurl(ui.expandpath(source))
3212 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3213 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3213 repo = peer.local()
3214 repo = peer.local()
3214 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3215 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3215
3216
3216 fm = ui.formatter('identify', opts)
3217 fm = ui.formatter('identify', opts)
3217 fm.startitem()
3218 fm.startitem()
3218
3219
3219 if not repo:
3220 if not repo:
3220 if num or branch or tags:
3221 if num or branch or tags:
3221 raise error.Abort(
3222 raise error.Abort(
3222 _("can't query remote revision number, branch, or tags"))
3223 _("can't query remote revision number, branch, or tags"))
3223 if not rev and revs:
3224 if not rev and revs:
3224 rev = revs[0]
3225 rev = revs[0]
3225 if not rev:
3226 if not rev:
3226 rev = "tip"
3227 rev = "tip"
3227
3228
3228 remoterev = peer.lookup(rev)
3229 remoterev = peer.lookup(rev)
3229 hexrev = fm.hexfunc(remoterev)
3230 hexrev = fm.hexfunc(remoterev)
3230 if default or id:
3231 if default or id:
3231 output = [hexrev]
3232 output = [hexrev]
3232 fm.data(id=hexrev)
3233 fm.data(id=hexrev)
3233
3234
3234 @util.cachefunc
3235 @util.cachefunc
3235 def getbms():
3236 def getbms():
3236 bms = []
3237 bms = []
3237
3238
3238 if 'bookmarks' in peer.listkeys('namespaces'):
3239 if 'bookmarks' in peer.listkeys('namespaces'):
3239 hexremoterev = hex(remoterev)
3240 hexremoterev = hex(remoterev)
3240 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3241 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3241 if bmr == hexremoterev]
3242 if bmr == hexremoterev]
3242
3243
3243 return sorted(bms)
3244 return sorted(bms)
3244
3245
3245 if fm.isplain():
3246 if fm.isplain():
3246 if bookmarks:
3247 if bookmarks:
3247 output.extend(getbms())
3248 output.extend(getbms())
3248 elif default and not ui.quiet:
3249 elif default and not ui.quiet:
3249 # multiple bookmarks for a single parent separated by '/'
3250 # multiple bookmarks for a single parent separated by '/'
3250 bm = '/'.join(getbms())
3251 bm = '/'.join(getbms())
3251 if bm:
3252 if bm:
3252 output.append(bm)
3253 output.append(bm)
3253 else:
3254 else:
3254 fm.data(node=hex(remoterev))
3255 fm.data(node=hex(remoterev))
3255 if bookmarks or 'bookmarks' in fm.datahint():
3256 if bookmarks or 'bookmarks' in fm.datahint():
3256 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3257 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3257 else:
3258 else:
3258 if rev:
3259 if rev:
3259 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3260 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3260 ctx = scmutil.revsingle(repo, rev, None)
3261 ctx = scmutil.revsingle(repo, rev, None)
3261
3262
3262 if ctx.rev() is None:
3263 if ctx.rev() is None:
3263 ctx = repo[None]
3264 ctx = repo[None]
3264 parents = ctx.parents()
3265 parents = ctx.parents()
3265 taglist = []
3266 taglist = []
3266 for p in parents:
3267 for p in parents:
3267 taglist.extend(p.tags())
3268 taglist.extend(p.tags())
3268
3269
3269 dirty = ""
3270 dirty = ""
3270 if ctx.dirty(missing=True, merge=False, branch=False):
3271 if ctx.dirty(missing=True, merge=False, branch=False):
3271 dirty = '+'
3272 dirty = '+'
3272 fm.data(dirty=dirty)
3273 fm.data(dirty=dirty)
3273
3274
3274 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3275 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3275 if default or id:
3276 if default or id:
3276 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3277 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3277 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3278 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3278
3279
3279 if num:
3280 if num:
3280 numoutput = ["%d" % p.rev() for p in parents]
3281 numoutput = ["%d" % p.rev() for p in parents]
3281 output.append("%s%s" % ('+'.join(numoutput), dirty))
3282 output.append("%s%s" % ('+'.join(numoutput), dirty))
3282
3283
3283 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3284 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3284 for p in parents], name='node'))
3285 for p in parents], name='node'))
3285 else:
3286 else:
3286 hexoutput = fm.hexfunc(ctx.node())
3287 hexoutput = fm.hexfunc(ctx.node())
3287 if default or id:
3288 if default or id:
3288 output = [hexoutput]
3289 output = [hexoutput]
3289 fm.data(id=hexoutput)
3290 fm.data(id=hexoutput)
3290
3291
3291 if num:
3292 if num:
3292 output.append(pycompat.bytestr(ctx.rev()))
3293 output.append(pycompat.bytestr(ctx.rev()))
3293 taglist = ctx.tags()
3294 taglist = ctx.tags()
3294
3295
3295 if default and not ui.quiet:
3296 if default and not ui.quiet:
3296 b = ctx.branch()
3297 b = ctx.branch()
3297 if b != 'default':
3298 if b != 'default':
3298 output.append("(%s)" % b)
3299 output.append("(%s)" % b)
3299
3300
3300 # multiple tags for a single parent separated by '/'
3301 # multiple tags for a single parent separated by '/'
3301 t = '/'.join(taglist)
3302 t = '/'.join(taglist)
3302 if t:
3303 if t:
3303 output.append(t)
3304 output.append(t)
3304
3305
3305 # multiple bookmarks for a single parent separated by '/'
3306 # multiple bookmarks for a single parent separated by '/'
3306 bm = '/'.join(ctx.bookmarks())
3307 bm = '/'.join(ctx.bookmarks())
3307 if bm:
3308 if bm:
3308 output.append(bm)
3309 output.append(bm)
3309 else:
3310 else:
3310 if branch:
3311 if branch:
3311 output.append(ctx.branch())
3312 output.append(ctx.branch())
3312
3313
3313 if tags:
3314 if tags:
3314 output.extend(taglist)
3315 output.extend(taglist)
3315
3316
3316 if bookmarks:
3317 if bookmarks:
3317 output.extend(ctx.bookmarks())
3318 output.extend(ctx.bookmarks())
3318
3319
3319 fm.data(node=ctx.hex())
3320 fm.data(node=ctx.hex())
3320 fm.data(branch=ctx.branch())
3321 fm.data(branch=ctx.branch())
3321 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3322 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3322 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3323 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3323 fm.context(ctx=ctx)
3324 fm.context(ctx=ctx)
3324
3325
3325 fm.plain("%s\n" % ' '.join(output))
3326 fm.plain("%s\n" % ' '.join(output))
3326 fm.end()
3327 fm.end()
3327
3328
3328 @command('import|patch',
3329 @command('import|patch',
3329 [('p', 'strip', 1,
3330 [('p', 'strip', 1,
3330 _('directory strip option for patch. This has the same '
3331 _('directory strip option for patch. This has the same '
3331 'meaning as the corresponding patch option'), _('NUM')),
3332 'meaning as the corresponding patch option'), _('NUM')),
3332 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3333 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3333 ('e', 'edit', False, _('invoke editor on commit messages')),
3334 ('e', 'edit', False, _('invoke editor on commit messages')),
3334 ('f', 'force', None,
3335 ('f', 'force', None,
3335 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3336 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3336 ('', 'no-commit', None,
3337 ('', 'no-commit', None,
3337 _("don't commit, just update the working directory")),
3338 _("don't commit, just update the working directory")),
3338 ('', 'bypass', None,
3339 ('', 'bypass', None,
3339 _("apply patch without touching the working directory")),
3340 _("apply patch without touching the working directory")),
3340 ('', 'partial', None,
3341 ('', 'partial', None,
3341 _('commit even if some hunks fail')),
3342 _('commit even if some hunks fail')),
3342 ('', 'exact', None,
3343 ('', 'exact', None,
3343 _('abort if patch would apply lossily')),
3344 _('abort if patch would apply lossily')),
3344 ('', 'prefix', '',
3345 ('', 'prefix', '',
3345 _('apply patch to subdirectory'), _('DIR')),
3346 _('apply patch to subdirectory'), _('DIR')),
3346 ('', 'import-branch', None,
3347 ('', 'import-branch', None,
3347 _('use any branch information in patch (implied by --exact)'))] +
3348 _('use any branch information in patch (implied by --exact)'))] +
3348 commitopts + commitopts2 + similarityopts,
3349 commitopts + commitopts2 + similarityopts,
3349 _('[OPTION]... PATCH...'),
3350 _('[OPTION]... PATCH...'),
3350 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3351 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3351 def import_(ui, repo, patch1=None, *patches, **opts):
3352 def import_(ui, repo, patch1=None, *patches, **opts):
3352 """import an ordered set of patches
3353 """import an ordered set of patches
3353
3354
3354 Import a list of patches and commit them individually (unless
3355 Import a list of patches and commit them individually (unless
3355 --no-commit is specified).
3356 --no-commit is specified).
3356
3357
3357 To read a patch from standard input (stdin), use "-" as the patch
3358 To read a patch from standard input (stdin), use "-" as the patch
3358 name. If a URL is specified, the patch will be downloaded from
3359 name. If a URL is specified, the patch will be downloaded from
3359 there.
3360 there.
3360
3361
3361 Import first applies changes to the working directory (unless
3362 Import first applies changes to the working directory (unless
3362 --bypass is specified), import will abort if there are outstanding
3363 --bypass is specified), import will abort if there are outstanding
3363 changes.
3364 changes.
3364
3365
3365 Use --bypass to apply and commit patches directly to the
3366 Use --bypass to apply and commit patches directly to the
3366 repository, without affecting the working directory. Without
3367 repository, without affecting the working directory. Without
3367 --exact, patches will be applied on top of the working directory
3368 --exact, patches will be applied on top of the working directory
3368 parent revision.
3369 parent revision.
3369
3370
3370 You can import a patch straight from a mail message. Even patches
3371 You can import a patch straight from a mail message. Even patches
3371 as attachments work (to use the body part, it must have type
3372 as attachments work (to use the body part, it must have type
3372 text/plain or text/x-patch). From and Subject headers of email
3373 text/plain or text/x-patch). From and Subject headers of email
3373 message are used as default committer and commit message. All
3374 message are used as default committer and commit message. All
3374 text/plain body parts before first diff are added to the commit
3375 text/plain body parts before first diff are added to the commit
3375 message.
3376 message.
3376
3377
3377 If the imported patch was generated by :hg:`export`, user and
3378 If the imported patch was generated by :hg:`export`, user and
3378 description from patch override values from message headers and
3379 description from patch override values from message headers and
3379 body. Values given on command line with -m/--message and -u/--user
3380 body. Values given on command line with -m/--message and -u/--user
3380 override these.
3381 override these.
3381
3382
3382 If --exact is specified, import will set the working directory to
3383 If --exact is specified, import will set the working directory to
3383 the parent of each patch before applying it, and will abort if the
3384 the parent of each patch before applying it, and will abort if the
3384 resulting changeset has a different ID than the one recorded in
3385 resulting changeset has a different ID than the one recorded in
3385 the patch. This will guard against various ways that portable
3386 the patch. This will guard against various ways that portable
3386 patch formats and mail systems might fail to transfer Mercurial
3387 patch formats and mail systems might fail to transfer Mercurial
3387 data or metadata. See :hg:`bundle` for lossless transmission.
3388 data or metadata. See :hg:`bundle` for lossless transmission.
3388
3389
3389 Use --partial to ensure a changeset will be created from the patch
3390 Use --partial to ensure a changeset will be created from the patch
3390 even if some hunks fail to apply. Hunks that fail to apply will be
3391 even if some hunks fail to apply. Hunks that fail to apply will be
3391 written to a <target-file>.rej file. Conflicts can then be resolved
3392 written to a <target-file>.rej file. Conflicts can then be resolved
3392 by hand before :hg:`commit --amend` is run to update the created
3393 by hand before :hg:`commit --amend` is run to update the created
3393 changeset. This flag exists to let people import patches that
3394 changeset. This flag exists to let people import patches that
3394 partially apply without losing the associated metadata (author,
3395 partially apply without losing the associated metadata (author,
3395 date, description, ...).
3396 date, description, ...).
3396
3397
3397 .. note::
3398 .. note::
3398
3399
3399 When no hunks apply cleanly, :hg:`import --partial` will create
3400 When no hunks apply cleanly, :hg:`import --partial` will create
3400 an empty changeset, importing only the patch metadata.
3401 an empty changeset, importing only the patch metadata.
3401
3402
3402 With -s/--similarity, hg will attempt to discover renames and
3403 With -s/--similarity, hg will attempt to discover renames and
3403 copies in the patch in the same way as :hg:`addremove`.
3404 copies in the patch in the same way as :hg:`addremove`.
3404
3405
3405 It is possible to use external patch programs to perform the patch
3406 It is possible to use external patch programs to perform the patch
3406 by setting the ``ui.patch`` configuration option. For the default
3407 by setting the ``ui.patch`` configuration option. For the default
3407 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3408 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3408 See :hg:`help config` for more information about configuration
3409 See :hg:`help config` for more information about configuration
3409 files and how to use these options.
3410 files and how to use these options.
3410
3411
3411 See :hg:`help dates` for a list of formats valid for -d/--date.
3412 See :hg:`help dates` for a list of formats valid for -d/--date.
3412
3413
3413 .. container:: verbose
3414 .. container:: verbose
3414
3415
3415 Examples:
3416 Examples:
3416
3417
3417 - import a traditional patch from a website and detect renames::
3418 - import a traditional patch from a website and detect renames::
3418
3419
3419 hg import -s 80 http://example.com/bugfix.patch
3420 hg import -s 80 http://example.com/bugfix.patch
3420
3421
3421 - import a changeset from an hgweb server::
3422 - import a changeset from an hgweb server::
3422
3423
3423 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3424 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3424
3425
3425 - import all the patches in an Unix-style mbox::
3426 - import all the patches in an Unix-style mbox::
3426
3427
3427 hg import incoming-patches.mbox
3428 hg import incoming-patches.mbox
3428
3429
3429 - import patches from stdin::
3430 - import patches from stdin::
3430
3431
3431 hg import -
3432 hg import -
3432
3433
3433 - attempt to exactly restore an exported changeset (not always
3434 - attempt to exactly restore an exported changeset (not always
3434 possible)::
3435 possible)::
3435
3436
3436 hg import --exact proposed-fix.patch
3437 hg import --exact proposed-fix.patch
3437
3438
3438 - use an external tool to apply a patch which is too fuzzy for
3439 - use an external tool to apply a patch which is too fuzzy for
3439 the default internal tool.
3440 the default internal tool.
3440
3441
3441 hg import --config ui.patch="patch --merge" fuzzy.patch
3442 hg import --config ui.patch="patch --merge" fuzzy.patch
3442
3443
3443 - change the default fuzzing from 2 to a less strict 7
3444 - change the default fuzzing from 2 to a less strict 7
3444
3445
3445 hg import --config ui.fuzz=7 fuzz.patch
3446 hg import --config ui.fuzz=7 fuzz.patch
3446
3447
3447 Returns 0 on success, 1 on partial success (see --partial).
3448 Returns 0 on success, 1 on partial success (see --partial).
3448 """
3449 """
3449
3450
3450 opts = pycompat.byteskwargs(opts)
3451 opts = pycompat.byteskwargs(opts)
3451 if not patch1:
3452 if not patch1:
3452 raise error.Abort(_('need at least one patch to import'))
3453 raise error.Abort(_('need at least one patch to import'))
3453
3454
3454 patches = (patch1,) + patches
3455 patches = (patch1,) + patches
3455
3456
3456 date = opts.get('date')
3457 date = opts.get('date')
3457 if date:
3458 if date:
3458 opts['date'] = dateutil.parsedate(date)
3459 opts['date'] = dateutil.parsedate(date)
3459
3460
3460 exact = opts.get('exact')
3461 exact = opts.get('exact')
3461 update = not opts.get('bypass')
3462 update = not opts.get('bypass')
3462 if not update and opts.get('no_commit'):
3463 if not update and opts.get('no_commit'):
3463 raise error.Abort(_('cannot use --no-commit with --bypass'))
3464 raise error.Abort(_('cannot use --no-commit with --bypass'))
3464 try:
3465 try:
3465 sim = float(opts.get('similarity') or 0)
3466 sim = float(opts.get('similarity') or 0)
3466 except ValueError:
3467 except ValueError:
3467 raise error.Abort(_('similarity must be a number'))
3468 raise error.Abort(_('similarity must be a number'))
3468 if sim < 0 or sim > 100:
3469 if sim < 0 or sim > 100:
3469 raise error.Abort(_('similarity must be between 0 and 100'))
3470 raise error.Abort(_('similarity must be between 0 and 100'))
3470 if sim and not update:
3471 if sim and not update:
3471 raise error.Abort(_('cannot use --similarity with --bypass'))
3472 raise error.Abort(_('cannot use --similarity with --bypass'))
3472 if exact:
3473 if exact:
3473 if opts.get('edit'):
3474 if opts.get('edit'):
3474 raise error.Abort(_('cannot use --exact with --edit'))
3475 raise error.Abort(_('cannot use --exact with --edit'))
3475 if opts.get('prefix'):
3476 if opts.get('prefix'):
3476 raise error.Abort(_('cannot use --exact with --prefix'))
3477 raise error.Abort(_('cannot use --exact with --prefix'))
3477
3478
3478 base = opts["base"]
3479 base = opts["base"]
3479 msgs = []
3480 msgs = []
3480 ret = 0
3481 ret = 0
3481
3482
3482 with repo.wlock():
3483 with repo.wlock():
3483 if update:
3484 if update:
3484 cmdutil.checkunfinished(repo)
3485 cmdutil.checkunfinished(repo)
3485 if (exact or not opts.get('force')):
3486 if (exact or not opts.get('force')):
3486 cmdutil.bailifchanged(repo)
3487 cmdutil.bailifchanged(repo)
3487
3488
3488 if not opts.get('no_commit'):
3489 if not opts.get('no_commit'):
3489 lock = repo.lock
3490 lock = repo.lock
3490 tr = lambda: repo.transaction('import')
3491 tr = lambda: repo.transaction('import')
3491 dsguard = util.nullcontextmanager
3492 dsguard = util.nullcontextmanager
3492 else:
3493 else:
3493 lock = util.nullcontextmanager
3494 lock = util.nullcontextmanager
3494 tr = util.nullcontextmanager
3495 tr = util.nullcontextmanager
3495 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3496 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3496 with lock(), tr(), dsguard():
3497 with lock(), tr(), dsguard():
3497 parents = repo[None].parents()
3498 parents = repo[None].parents()
3498 for patchurl in patches:
3499 for patchurl in patches:
3499 if patchurl == '-':
3500 if patchurl == '-':
3500 ui.status(_('applying patch from stdin\n'))
3501 ui.status(_('applying patch from stdin\n'))
3501 patchfile = ui.fin
3502 patchfile = ui.fin
3502 patchurl = 'stdin' # for error message
3503 patchurl = 'stdin' # for error message
3503 else:
3504 else:
3504 patchurl = os.path.join(base, patchurl)
3505 patchurl = os.path.join(base, patchurl)
3505 ui.status(_('applying %s\n') % patchurl)
3506 ui.status(_('applying %s\n') % patchurl)
3506 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3507 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3507
3508
3508 haspatch = False
3509 haspatch = False
3509 for hunk in patch.split(patchfile):
3510 for hunk in patch.split(patchfile):
3510 with patch.extract(ui, hunk) as patchdata:
3511 with patch.extract(ui, hunk) as patchdata:
3511 msg, node, rej = cmdutil.tryimportone(ui, repo,
3512 msg, node, rej = cmdutil.tryimportone(ui, repo,
3512 patchdata,
3513 patchdata,
3513 parents, opts,
3514 parents, opts,
3514 msgs, hg.clean)
3515 msgs, hg.clean)
3515 if msg:
3516 if msg:
3516 haspatch = True
3517 haspatch = True
3517 ui.note(msg + '\n')
3518 ui.note(msg + '\n')
3518 if update or exact:
3519 if update or exact:
3519 parents = repo[None].parents()
3520 parents = repo[None].parents()
3520 else:
3521 else:
3521 parents = [repo[node]]
3522 parents = [repo[node]]
3522 if rej:
3523 if rej:
3523 ui.write_err(_("patch applied partially\n"))
3524 ui.write_err(_("patch applied partially\n"))
3524 ui.write_err(_("(fix the .rej files and run "
3525 ui.write_err(_("(fix the .rej files and run "
3525 "`hg commit --amend`)\n"))
3526 "`hg commit --amend`)\n"))
3526 ret = 1
3527 ret = 1
3527 break
3528 break
3528
3529
3529 if not haspatch:
3530 if not haspatch:
3530 raise error.Abort(_('%s: no diffs found') % patchurl)
3531 raise error.Abort(_('%s: no diffs found') % patchurl)
3531
3532
3532 if msgs:
3533 if msgs:
3533 repo.savecommitmessage('\n* * *\n'.join(msgs))
3534 repo.savecommitmessage('\n* * *\n'.join(msgs))
3534 return ret
3535 return ret
3535
3536
3536 @command('incoming|in',
3537 @command('incoming|in',
3537 [('f', 'force', None,
3538 [('f', 'force', None,
3538 _('run even if remote repository is unrelated')),
3539 _('run even if remote repository is unrelated')),
3539 ('n', 'newest-first', None, _('show newest record first')),
3540 ('n', 'newest-first', None, _('show newest record first')),
3540 ('', 'bundle', '',
3541 ('', 'bundle', '',
3541 _('file to store the bundles into'), _('FILE')),
3542 _('file to store the bundles into'), _('FILE')),
3542 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3543 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3543 ('B', 'bookmarks', False, _("compare bookmarks")),
3544 ('B', 'bookmarks', False, _("compare bookmarks")),
3544 ('b', 'branch', [],
3545 ('b', 'branch', [],
3545 _('a specific branch you would like to pull'), _('BRANCH')),
3546 _('a specific branch you would like to pull'), _('BRANCH')),
3546 ] + logopts + remoteopts + subrepoopts,
3547 ] + logopts + remoteopts + subrepoopts,
3547 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3548 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3548 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3549 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3549 def incoming(ui, repo, source="default", **opts):
3550 def incoming(ui, repo, source="default", **opts):
3550 """show new changesets found in source
3551 """show new changesets found in source
3551
3552
3552 Show new changesets found in the specified path/URL or the default
3553 Show new changesets found in the specified path/URL or the default
3553 pull location. These are the changesets that would have been pulled
3554 pull location. These are the changesets that would have been pulled
3554 by :hg:`pull` at the time you issued this command.
3555 by :hg:`pull` at the time you issued this command.
3555
3556
3556 See pull for valid source format details.
3557 See pull for valid source format details.
3557
3558
3558 .. container:: verbose
3559 .. container:: verbose
3559
3560
3560 With -B/--bookmarks, the result of bookmark comparison between
3561 With -B/--bookmarks, the result of bookmark comparison between
3561 local and remote repositories is displayed. With -v/--verbose,
3562 local and remote repositories is displayed. With -v/--verbose,
3562 status is also displayed for each bookmark like below::
3563 status is also displayed for each bookmark like below::
3563
3564
3564 BM1 01234567890a added
3565 BM1 01234567890a added
3565 BM2 1234567890ab advanced
3566 BM2 1234567890ab advanced
3566 BM3 234567890abc diverged
3567 BM3 234567890abc diverged
3567 BM4 34567890abcd changed
3568 BM4 34567890abcd changed
3568
3569
3569 The action taken locally when pulling depends on the
3570 The action taken locally when pulling depends on the
3570 status of each bookmark:
3571 status of each bookmark:
3571
3572
3572 :``added``: pull will create it
3573 :``added``: pull will create it
3573 :``advanced``: pull will update it
3574 :``advanced``: pull will update it
3574 :``diverged``: pull will create a divergent bookmark
3575 :``diverged``: pull will create a divergent bookmark
3575 :``changed``: result depends on remote changesets
3576 :``changed``: result depends on remote changesets
3576
3577
3577 From the point of view of pulling behavior, bookmark
3578 From the point of view of pulling behavior, bookmark
3578 existing only in the remote repository are treated as ``added``,
3579 existing only in the remote repository are treated as ``added``,
3579 even if it is in fact locally deleted.
3580 even if it is in fact locally deleted.
3580
3581
3581 .. container:: verbose
3582 .. container:: verbose
3582
3583
3583 For remote repository, using --bundle avoids downloading the
3584 For remote repository, using --bundle avoids downloading the
3584 changesets twice if the incoming is followed by a pull.
3585 changesets twice if the incoming is followed by a pull.
3585
3586
3586 Examples:
3587 Examples:
3587
3588
3588 - show incoming changes with patches and full description::
3589 - show incoming changes with patches and full description::
3589
3590
3590 hg incoming -vp
3591 hg incoming -vp
3591
3592
3592 - show incoming changes excluding merges, store a bundle::
3593 - show incoming changes excluding merges, store a bundle::
3593
3594
3594 hg in -vpM --bundle incoming.hg
3595 hg in -vpM --bundle incoming.hg
3595 hg pull incoming.hg
3596 hg pull incoming.hg
3596
3597
3597 - briefly list changes inside a bundle::
3598 - briefly list changes inside a bundle::
3598
3599
3599 hg in changes.hg -T "{desc|firstline}\\n"
3600 hg in changes.hg -T "{desc|firstline}\\n"
3600
3601
3601 Returns 0 if there are incoming changes, 1 otherwise.
3602 Returns 0 if there are incoming changes, 1 otherwise.
3602 """
3603 """
3603 opts = pycompat.byteskwargs(opts)
3604 opts = pycompat.byteskwargs(opts)
3604 if opts.get('graph'):
3605 if opts.get('graph'):
3605 logcmdutil.checkunsupportedgraphflags([], opts)
3606 logcmdutil.checkunsupportedgraphflags([], opts)
3606 def display(other, chlist, displayer):
3607 def display(other, chlist, displayer):
3607 revdag = logcmdutil.graphrevs(other, chlist, opts)
3608 revdag = logcmdutil.graphrevs(other, chlist, opts)
3608 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3609 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3609 graphmod.asciiedges)
3610 graphmod.asciiedges)
3610
3611
3611 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3612 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3612 return 0
3613 return 0
3613
3614
3614 if opts.get('bundle') and opts.get('subrepos'):
3615 if opts.get('bundle') and opts.get('subrepos'):
3615 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3616 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3616
3617
3617 if opts.get('bookmarks'):
3618 if opts.get('bookmarks'):
3618 source, branches = hg.parseurl(ui.expandpath(source),
3619 source, branches = hg.parseurl(ui.expandpath(source),
3619 opts.get('branch'))
3620 opts.get('branch'))
3620 other = hg.peer(repo, opts, source)
3621 other = hg.peer(repo, opts, source)
3621 if 'bookmarks' not in other.listkeys('namespaces'):
3622 if 'bookmarks' not in other.listkeys('namespaces'):
3622 ui.warn(_("remote doesn't support bookmarks\n"))
3623 ui.warn(_("remote doesn't support bookmarks\n"))
3623 return 0
3624 return 0
3624 ui.pager('incoming')
3625 ui.pager('incoming')
3625 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3626 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3626 return bookmarks.incoming(ui, repo, other)
3627 return bookmarks.incoming(ui, repo, other)
3627
3628
3628 repo._subtoppath = ui.expandpath(source)
3629 repo._subtoppath = ui.expandpath(source)
3629 try:
3630 try:
3630 return hg.incoming(ui, repo, source, opts)
3631 return hg.incoming(ui, repo, source, opts)
3631 finally:
3632 finally:
3632 del repo._subtoppath
3633 del repo._subtoppath
3633
3634
3634
3635
3635 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3636 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3636 helpcategory=command.CATEGORY_REPO_CREATION,
3637 helpcategory=command.CATEGORY_REPO_CREATION,
3637 helpbasic=True, norepo=True)
3638 helpbasic=True, norepo=True)
3638 def init(ui, dest=".", **opts):
3639 def init(ui, dest=".", **opts):
3639 """create a new repository in the given directory
3640 """create a new repository in the given directory
3640
3641
3641 Initialize a new repository in the given directory. If the given
3642 Initialize a new repository in the given directory. If the given
3642 directory does not exist, it will be created.
3643 directory does not exist, it will be created.
3643
3644
3644 If no directory is given, the current directory is used.
3645 If no directory is given, the current directory is used.
3645
3646
3646 It is possible to specify an ``ssh://`` URL as the destination.
3647 It is possible to specify an ``ssh://`` URL as the destination.
3647 See :hg:`help urls` for more information.
3648 See :hg:`help urls` for more information.
3648
3649
3649 Returns 0 on success.
3650 Returns 0 on success.
3650 """
3651 """
3651 opts = pycompat.byteskwargs(opts)
3652 opts = pycompat.byteskwargs(opts)
3652 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3653 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3653
3654
3654 @command('locate',
3655 @command('locate',
3655 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3656 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3656 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3657 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3657 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3658 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3658 ] + walkopts,
3659 ] + walkopts,
3659 _('[OPTION]... [PATTERN]...'),
3660 _('[OPTION]... [PATTERN]...'),
3660 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3661 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3661 def locate(ui, repo, *pats, **opts):
3662 def locate(ui, repo, *pats, **opts):
3662 """locate files matching specific patterns (DEPRECATED)
3663 """locate files matching specific patterns (DEPRECATED)
3663
3664
3664 Print files under Mercurial control in the working directory whose
3665 Print files under Mercurial control in the working directory whose
3665 names match the given patterns.
3666 names match the given patterns.
3666
3667
3667 By default, this command searches all directories in the working
3668 By default, this command searches all directories in the working
3668 directory. To search just the current directory and its
3669 directory. To search just the current directory and its
3669 subdirectories, use "--include .".
3670 subdirectories, use "--include .".
3670
3671
3671 If no patterns are given to match, this command prints the names
3672 If no patterns are given to match, this command prints the names
3672 of all files under Mercurial control in the working directory.
3673 of all files under Mercurial control in the working directory.
3673
3674
3674 If you want to feed the output of this command into the "xargs"
3675 If you want to feed the output of this command into the "xargs"
3675 command, use the -0 option to both this command and "xargs". This
3676 command, use the -0 option to both this command and "xargs". This
3676 will avoid the problem of "xargs" treating single filenames that
3677 will avoid the problem of "xargs" treating single filenames that
3677 contain whitespace as multiple filenames.
3678 contain whitespace as multiple filenames.
3678
3679
3679 See :hg:`help files` for a more versatile command.
3680 See :hg:`help files` for a more versatile command.
3680
3681
3681 Returns 0 if a match is found, 1 otherwise.
3682 Returns 0 if a match is found, 1 otherwise.
3682 """
3683 """
3683 opts = pycompat.byteskwargs(opts)
3684 opts = pycompat.byteskwargs(opts)
3684 if opts.get('print0'):
3685 if opts.get('print0'):
3685 end = '\0'
3686 end = '\0'
3686 else:
3687 else:
3687 end = '\n'
3688 end = '\n'
3688 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3689 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3689
3690
3690 ret = 1
3691 ret = 1
3691 m = scmutil.match(ctx, pats, opts, default='relglob',
3692 m = scmutil.match(ctx, pats, opts, default='relglob',
3692 badfn=lambda x, y: False)
3693 badfn=lambda x, y: False)
3693
3694
3694 ui.pager('locate')
3695 ui.pager('locate')
3695 if ctx.rev() is None:
3696 if ctx.rev() is None:
3696 # When run on the working copy, "locate" includes removed files, so
3697 # When run on the working copy, "locate" includes removed files, so
3697 # we get the list of files from the dirstate.
3698 # we get the list of files from the dirstate.
3698 filesgen = sorted(repo.dirstate.matches(m))
3699 filesgen = sorted(repo.dirstate.matches(m))
3699 else:
3700 else:
3700 filesgen = ctx.matches(m)
3701 filesgen = ctx.matches(m)
3701 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3702 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3702 for abs in filesgen:
3703 for abs in filesgen:
3703 if opts.get('fullpath'):
3704 if opts.get('fullpath'):
3704 ui.write(repo.wjoin(abs), end)
3705 ui.write(repo.wjoin(abs), end)
3705 else:
3706 else:
3706 ui.write(uipathfn(abs), end)
3707 ui.write(uipathfn(abs), end)
3707 ret = 0
3708 ret = 0
3708
3709
3709 return ret
3710 return ret
3710
3711
3711 @command('log|history',
3712 @command('log|history',
3712 [('f', 'follow', None,
3713 [('f', 'follow', None,
3713 _('follow changeset history, or file history across copies and renames')),
3714 _('follow changeset history, or file history across copies and renames')),
3714 ('', 'follow-first', None,
3715 ('', 'follow-first', None,
3715 _('only follow the first parent of merge changesets (DEPRECATED)')),
3716 _('only follow the first parent of merge changesets (DEPRECATED)')),
3716 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3717 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3717 ('C', 'copies', None, _('show copied files')),
3718 ('C', 'copies', None, _('show copied files')),
3718 ('k', 'keyword', [],
3719 ('k', 'keyword', [],
3719 _('do case-insensitive search for a given text'), _('TEXT')),
3720 _('do case-insensitive search for a given text'), _('TEXT')),
3720 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3721 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3721 ('L', 'line-range', [],
3722 ('L', 'line-range', [],
3722 _('follow line range of specified file (EXPERIMENTAL)'),
3723 _('follow line range of specified file (EXPERIMENTAL)'),
3723 _('FILE,RANGE')),
3724 _('FILE,RANGE')),
3724 ('', 'removed', None, _('include revisions where files were removed')),
3725 ('', 'removed', None, _('include revisions where files were removed')),
3725 ('m', 'only-merges', None,
3726 ('m', 'only-merges', None,
3726 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3727 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3727 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3728 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3728 ('', 'only-branch', [],
3729 ('', 'only-branch', [],
3729 _('show only changesets within the given named branch (DEPRECATED)'),
3730 _('show only changesets within the given named branch (DEPRECATED)'),
3730 _('BRANCH')),
3731 _('BRANCH')),
3731 ('b', 'branch', [],
3732 ('b', 'branch', [],
3732 _('show changesets within the given named branch'), _('BRANCH')),
3733 _('show changesets within the given named branch'), _('BRANCH')),
3733 ('P', 'prune', [],
3734 ('P', 'prune', [],
3734 _('do not display revision or any of its ancestors'), _('REV')),
3735 _('do not display revision or any of its ancestors'), _('REV')),
3735 ] + logopts + walkopts,
3736 ] + logopts + walkopts,
3736 _('[OPTION]... [FILE]'),
3737 _('[OPTION]... [FILE]'),
3737 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3738 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3738 helpbasic=True, inferrepo=True,
3739 helpbasic=True, inferrepo=True,
3739 intents={INTENT_READONLY})
3740 intents={INTENT_READONLY})
3740 def log(ui, repo, *pats, **opts):
3741 def log(ui, repo, *pats, **opts):
3741 """show revision history of entire repository or files
3742 """show revision history of entire repository or files
3742
3743
3743 Print the revision history of the specified files or the entire
3744 Print the revision history of the specified files or the entire
3744 project.
3745 project.
3745
3746
3746 If no revision range is specified, the default is ``tip:0`` unless
3747 If no revision range is specified, the default is ``tip:0`` unless
3747 --follow is set, in which case the working directory parent is
3748 --follow is set, in which case the working directory parent is
3748 used as the starting revision.
3749 used as the starting revision.
3749
3750
3750 File history is shown without following rename or copy history of
3751 File history is shown without following rename or copy history of
3751 files. Use -f/--follow with a filename to follow history across
3752 files. Use -f/--follow with a filename to follow history across
3752 renames and copies. --follow without a filename will only show
3753 renames and copies. --follow without a filename will only show
3753 ancestors of the starting revision.
3754 ancestors of the starting revision.
3754
3755
3755 By default this command prints revision number and changeset id,
3756 By default this command prints revision number and changeset id,
3756 tags, non-trivial parents, user, date and time, and a summary for
3757 tags, non-trivial parents, user, date and time, and a summary for
3757 each commit. When the -v/--verbose switch is used, the list of
3758 each commit. When the -v/--verbose switch is used, the list of
3758 changed files and full commit message are shown.
3759 changed files and full commit message are shown.
3759
3760
3760 With --graph the revisions are shown as an ASCII art DAG with the most
3761 With --graph the revisions are shown as an ASCII art DAG with the most
3761 recent changeset at the top.
3762 recent changeset at the top.
3762 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3763 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3763 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3764 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3764 changeset from the lines below is a parent of the 'o' merge on the same
3765 changeset from the lines below is a parent of the 'o' merge on the same
3765 line.
3766 line.
3766 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3767 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3767 of a '|' indicates one or more revisions in a path are omitted.
3768 of a '|' indicates one or more revisions in a path are omitted.
3768
3769
3769 .. container:: verbose
3770 .. container:: verbose
3770
3771
3771 Use -L/--line-range FILE,M:N options to follow the history of lines
3772 Use -L/--line-range FILE,M:N options to follow the history of lines
3772 from M to N in FILE. With -p/--patch only diff hunks affecting
3773 from M to N in FILE. With -p/--patch only diff hunks affecting
3773 specified line range will be shown. This option requires --follow;
3774 specified line range will be shown. This option requires --follow;
3774 it can be specified multiple times. Currently, this option is not
3775 it can be specified multiple times. Currently, this option is not
3775 compatible with --graph. This option is experimental.
3776 compatible with --graph. This option is experimental.
3776
3777
3777 .. note::
3778 .. note::
3778
3779
3779 :hg:`log --patch` may generate unexpected diff output for merge
3780 :hg:`log --patch` may generate unexpected diff output for merge
3780 changesets, as it will only compare the merge changeset against
3781 changesets, as it will only compare the merge changeset against
3781 its first parent. Also, only files different from BOTH parents
3782 its first parent. Also, only files different from BOTH parents
3782 will appear in files:.
3783 will appear in files:.
3783
3784
3784 .. note::
3785 .. note::
3785
3786
3786 For performance reasons, :hg:`log FILE` may omit duplicate changes
3787 For performance reasons, :hg:`log FILE` may omit duplicate changes
3787 made on branches and will not show removals or mode changes. To
3788 made on branches and will not show removals or mode changes. To
3788 see all such changes, use the --removed switch.
3789 see all such changes, use the --removed switch.
3789
3790
3790 .. container:: verbose
3791 .. container:: verbose
3791
3792
3792 .. note::
3793 .. note::
3793
3794
3794 The history resulting from -L/--line-range options depends on diff
3795 The history resulting from -L/--line-range options depends on diff
3795 options; for instance if white-spaces are ignored, respective changes
3796 options; for instance if white-spaces are ignored, respective changes
3796 with only white-spaces in specified line range will not be listed.
3797 with only white-spaces in specified line range will not be listed.
3797
3798
3798 .. container:: verbose
3799 .. container:: verbose
3799
3800
3800 Some examples:
3801 Some examples:
3801
3802
3802 - changesets with full descriptions and file lists::
3803 - changesets with full descriptions and file lists::
3803
3804
3804 hg log -v
3805 hg log -v
3805
3806
3806 - changesets ancestral to the working directory::
3807 - changesets ancestral to the working directory::
3807
3808
3808 hg log -f
3809 hg log -f
3809
3810
3810 - last 10 commits on the current branch::
3811 - last 10 commits on the current branch::
3811
3812
3812 hg log -l 10 -b .
3813 hg log -l 10 -b .
3813
3814
3814 - changesets showing all modifications of a file, including removals::
3815 - changesets showing all modifications of a file, including removals::
3815
3816
3816 hg log --removed file.c
3817 hg log --removed file.c
3817
3818
3818 - all changesets that touch a directory, with diffs, excluding merges::
3819 - all changesets that touch a directory, with diffs, excluding merges::
3819
3820
3820 hg log -Mp lib/
3821 hg log -Mp lib/
3821
3822
3822 - all revision numbers that match a keyword::
3823 - all revision numbers that match a keyword::
3823
3824
3824 hg log -k bug --template "{rev}\\n"
3825 hg log -k bug --template "{rev}\\n"
3825
3826
3826 - the full hash identifier of the working directory parent::
3827 - the full hash identifier of the working directory parent::
3827
3828
3828 hg log -r . --template "{node}\\n"
3829 hg log -r . --template "{node}\\n"
3829
3830
3830 - list available log templates::
3831 - list available log templates::
3831
3832
3832 hg log -T list
3833 hg log -T list
3833
3834
3834 - check if a given changeset is included in a tagged release::
3835 - check if a given changeset is included in a tagged release::
3835
3836
3836 hg log -r "a21ccf and ancestor(1.9)"
3837 hg log -r "a21ccf and ancestor(1.9)"
3837
3838
3838 - find all changesets by some user in a date range::
3839 - find all changesets by some user in a date range::
3839
3840
3840 hg log -k alice -d "may 2008 to jul 2008"
3841 hg log -k alice -d "may 2008 to jul 2008"
3841
3842
3842 - summary of all changesets after the last tag::
3843 - summary of all changesets after the last tag::
3843
3844
3844 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3845 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3845
3846
3846 - changesets touching lines 13 to 23 for file.c::
3847 - changesets touching lines 13 to 23 for file.c::
3847
3848
3848 hg log -L file.c,13:23
3849 hg log -L file.c,13:23
3849
3850
3850 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3851 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3851 main.c with patch::
3852 main.c with patch::
3852
3853
3853 hg log -L file.c,13:23 -L main.c,2:6 -p
3854 hg log -L file.c,13:23 -L main.c,2:6 -p
3854
3855
3855 See :hg:`help dates` for a list of formats valid for -d/--date.
3856 See :hg:`help dates` for a list of formats valid for -d/--date.
3856
3857
3857 See :hg:`help revisions` for more about specifying and ordering
3858 See :hg:`help revisions` for more about specifying and ordering
3858 revisions.
3859 revisions.
3859
3860
3860 See :hg:`help templates` for more about pre-packaged styles and
3861 See :hg:`help templates` for more about pre-packaged styles and
3861 specifying custom templates. The default template used by the log
3862 specifying custom templates. The default template used by the log
3862 command can be customized via the ``ui.logtemplate`` configuration
3863 command can be customized via the ``ui.logtemplate`` configuration
3863 setting.
3864 setting.
3864
3865
3865 Returns 0 on success.
3866 Returns 0 on success.
3866
3867
3867 """
3868 """
3868 opts = pycompat.byteskwargs(opts)
3869 opts = pycompat.byteskwargs(opts)
3869 linerange = opts.get('line_range')
3870 linerange = opts.get('line_range')
3870
3871
3871 if linerange and not opts.get('follow'):
3872 if linerange and not opts.get('follow'):
3872 raise error.Abort(_('--line-range requires --follow'))
3873 raise error.Abort(_('--line-range requires --follow'))
3873
3874
3874 if linerange and pats:
3875 if linerange and pats:
3875 # TODO: take pats as patterns with no line-range filter
3876 # TODO: take pats as patterns with no line-range filter
3876 raise error.Abort(
3877 raise error.Abort(
3877 _('FILE arguments are not compatible with --line-range option')
3878 _('FILE arguments are not compatible with --line-range option')
3878 )
3879 )
3879
3880
3880 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3881 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3881 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3882 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3882 if linerange:
3883 if linerange:
3883 # TODO: should follow file history from logcmdutil._initialrevs(),
3884 # TODO: should follow file history from logcmdutil._initialrevs(),
3884 # then filter the result by logcmdutil._makerevset() and --limit
3885 # then filter the result by logcmdutil._makerevset() and --limit
3885 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3886 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3886
3887
3887 getcopies = None
3888 getcopies = None
3888 if opts.get('copies'):
3889 if opts.get('copies'):
3889 endrev = None
3890 endrev = None
3890 if revs:
3891 if revs:
3891 endrev = revs.max() + 1
3892 endrev = revs.max() + 1
3892 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
3893 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
3893
3894
3894 ui.pager('log')
3895 ui.pager('log')
3895 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3896 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3896 buffered=True)
3897 buffered=True)
3897 if opts.get('graph'):
3898 if opts.get('graph'):
3898 displayfn = logcmdutil.displaygraphrevs
3899 displayfn = logcmdutil.displaygraphrevs
3899 else:
3900 else:
3900 displayfn = logcmdutil.displayrevs
3901 displayfn = logcmdutil.displayrevs
3901 displayfn(ui, repo, revs, displayer, getcopies)
3902 displayfn(ui, repo, revs, displayer, getcopies)
3902
3903
3903 @command('manifest',
3904 @command('manifest',
3904 [('r', 'rev', '', _('revision to display'), _('REV')),
3905 [('r', 'rev', '', _('revision to display'), _('REV')),
3905 ('', 'all', False, _("list files from all revisions"))]
3906 ('', 'all', False, _("list files from all revisions"))]
3906 + formatteropts,
3907 + formatteropts,
3907 _('[-r REV]'),
3908 _('[-r REV]'),
3908 helpcategory=command.CATEGORY_MAINTENANCE,
3909 helpcategory=command.CATEGORY_MAINTENANCE,
3909 intents={INTENT_READONLY})
3910 intents={INTENT_READONLY})
3910 def manifest(ui, repo, node=None, rev=None, **opts):
3911 def manifest(ui, repo, node=None, rev=None, **opts):
3911 """output the current or given revision of the project manifest
3912 """output the current or given revision of the project manifest
3912
3913
3913 Print a list of version controlled files for the given revision.
3914 Print a list of version controlled files for the given revision.
3914 If no revision is given, the first parent of the working directory
3915 If no revision is given, the first parent of the working directory
3915 is used, or the null revision if no revision is checked out.
3916 is used, or the null revision if no revision is checked out.
3916
3917
3917 With -v, print file permissions, symlink and executable bits.
3918 With -v, print file permissions, symlink and executable bits.
3918 With --debug, print file revision hashes.
3919 With --debug, print file revision hashes.
3919
3920
3920 If option --all is specified, the list of all files from all revisions
3921 If option --all is specified, the list of all files from all revisions
3921 is printed. This includes deleted and renamed files.
3922 is printed. This includes deleted and renamed files.
3922
3923
3923 Returns 0 on success.
3924 Returns 0 on success.
3924 """
3925 """
3925 opts = pycompat.byteskwargs(opts)
3926 opts = pycompat.byteskwargs(opts)
3926 fm = ui.formatter('manifest', opts)
3927 fm = ui.formatter('manifest', opts)
3927
3928
3928 if opts.get('all'):
3929 if opts.get('all'):
3929 if rev or node:
3930 if rev or node:
3930 raise error.Abort(_("can't specify a revision with --all"))
3931 raise error.Abort(_("can't specify a revision with --all"))
3931
3932
3932 res = set()
3933 res = set()
3933 for rev in repo:
3934 for rev in repo:
3934 ctx = repo[rev]
3935 ctx = repo[rev]
3935 res |= set(ctx.files())
3936 res |= set(ctx.files())
3936
3937
3937 ui.pager('manifest')
3938 ui.pager('manifest')
3938 for f in sorted(res):
3939 for f in sorted(res):
3939 fm.startitem()
3940 fm.startitem()
3940 fm.write("path", '%s\n', f)
3941 fm.write("path", '%s\n', f)
3941 fm.end()
3942 fm.end()
3942 return
3943 return
3943
3944
3944 if rev and node:
3945 if rev and node:
3945 raise error.Abort(_("please specify just one revision"))
3946 raise error.Abort(_("please specify just one revision"))
3946
3947
3947 if not node:
3948 if not node:
3948 node = rev
3949 node = rev
3949
3950
3950 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3951 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3951 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3952 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3952 if node:
3953 if node:
3953 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3954 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3954 ctx = scmutil.revsingle(repo, node)
3955 ctx = scmutil.revsingle(repo, node)
3955 mf = ctx.manifest()
3956 mf = ctx.manifest()
3956 ui.pager('manifest')
3957 ui.pager('manifest')
3957 for f in ctx:
3958 for f in ctx:
3958 fm.startitem()
3959 fm.startitem()
3959 fm.context(ctx=ctx)
3960 fm.context(ctx=ctx)
3960 fl = ctx[f].flags()
3961 fl = ctx[f].flags()
3961 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3962 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3962 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3963 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3963 fm.write('path', '%s\n', f)
3964 fm.write('path', '%s\n', f)
3964 fm.end()
3965 fm.end()
3965
3966
3966 @command('merge',
3967 @command('merge',
3967 [('f', 'force', None,
3968 [('f', 'force', None,
3968 _('force a merge including outstanding changes (DEPRECATED)')),
3969 _('force a merge including outstanding changes (DEPRECATED)')),
3969 ('r', 'rev', '', _('revision to merge'), _('REV')),
3970 ('r', 'rev', '', _('revision to merge'), _('REV')),
3970 ('P', 'preview', None,
3971 ('P', 'preview', None,
3971 _('review revisions to merge (no merge is performed)')),
3972 _('review revisions to merge (no merge is performed)')),
3972 ('', 'abort', None, _('abort the ongoing merge')),
3973 ('', 'abort', None, _('abort the ongoing merge')),
3973 ] + mergetoolopts,
3974 ] + mergetoolopts,
3974 _('[-P] [[-r] REV]'),
3975 _('[-P] [[-r] REV]'),
3975 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3976 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3976 def merge(ui, repo, node=None, **opts):
3977 def merge(ui, repo, node=None, **opts):
3977 """merge another revision into working directory
3978 """merge another revision into working directory
3978
3979
3979 The current working directory is updated with all changes made in
3980 The current working directory is updated with all changes made in
3980 the requested revision since the last common predecessor revision.
3981 the requested revision since the last common predecessor revision.
3981
3982
3982 Files that changed between either parent are marked as changed for
3983 Files that changed between either parent are marked as changed for
3983 the next commit and a commit must be performed before any further
3984 the next commit and a commit must be performed before any further
3984 updates to the repository are allowed. The next commit will have
3985 updates to the repository are allowed. The next commit will have
3985 two parents.
3986 two parents.
3986
3987
3987 ``--tool`` can be used to specify the merge tool used for file
3988 ``--tool`` can be used to specify the merge tool used for file
3988 merges. It overrides the HGMERGE environment variable and your
3989 merges. It overrides the HGMERGE environment variable and your
3989 configuration files. See :hg:`help merge-tools` for options.
3990 configuration files. See :hg:`help merge-tools` for options.
3990
3991
3991 If no revision is specified, the working directory's parent is a
3992 If no revision is specified, the working directory's parent is a
3992 head revision, and the current branch contains exactly one other
3993 head revision, and the current branch contains exactly one other
3993 head, the other head is merged with by default. Otherwise, an
3994 head, the other head is merged with by default. Otherwise, an
3994 explicit revision with which to merge must be provided.
3995 explicit revision with which to merge must be provided.
3995
3996
3996 See :hg:`help resolve` for information on handling file conflicts.
3997 See :hg:`help resolve` for information on handling file conflicts.
3997
3998
3998 To undo an uncommitted merge, use :hg:`merge --abort` which
3999 To undo an uncommitted merge, use :hg:`merge --abort` which
3999 will check out a clean copy of the original merge parent, losing
4000 will check out a clean copy of the original merge parent, losing
4000 all changes.
4001 all changes.
4001
4002
4002 Returns 0 on success, 1 if there are unresolved files.
4003 Returns 0 on success, 1 if there are unresolved files.
4003 """
4004 """
4004
4005
4005 opts = pycompat.byteskwargs(opts)
4006 opts = pycompat.byteskwargs(opts)
4006 abort = opts.get('abort')
4007 abort = opts.get('abort')
4007 if abort and repo.dirstate.p2() == nullid:
4008 if abort and repo.dirstate.p2() == nullid:
4008 cmdutil.wrongtooltocontinue(repo, _('merge'))
4009 cmdutil.wrongtooltocontinue(repo, _('merge'))
4009 if abort:
4010 if abort:
4010 state = cmdutil.getunfinishedstate(repo)
4011 state = cmdutil.getunfinishedstate(repo)
4011 if state and state._opname != 'merge':
4012 if state and state._opname != 'merge':
4012 raise error.Abort(_('cannot abort merge with %s in progress') %
4013 raise error.Abort(_('cannot abort merge with %s in progress') %
4013 (state._opname), hint=state.hint())
4014 (state._opname), hint=state.hint())
4014 if node:
4015 if node:
4015 raise error.Abort(_("cannot specify a node with --abort"))
4016 raise error.Abort(_("cannot specify a node with --abort"))
4016 if opts.get('rev'):
4017 if opts.get('rev'):
4017 raise error.Abort(_("cannot specify both --rev and --abort"))
4018 raise error.Abort(_("cannot specify both --rev and --abort"))
4018 if opts.get('preview'):
4019 if opts.get('preview'):
4019 raise error.Abort(_("cannot specify --preview with --abort"))
4020 raise error.Abort(_("cannot specify --preview with --abort"))
4020 if opts.get('rev') and node:
4021 if opts.get('rev') and node:
4021 raise error.Abort(_("please specify just one revision"))
4022 raise error.Abort(_("please specify just one revision"))
4022 if not node:
4023 if not node:
4023 node = opts.get('rev')
4024 node = opts.get('rev')
4024
4025
4025 if node:
4026 if node:
4026 node = scmutil.revsingle(repo, node).node()
4027 node = scmutil.revsingle(repo, node).node()
4027
4028
4028 if not node and not abort:
4029 if not node and not abort:
4029 node = repo[destutil.destmerge(repo)].node()
4030 node = repo[destutil.destmerge(repo)].node()
4030
4031
4031 if opts.get('preview'):
4032 if opts.get('preview'):
4032 # find nodes that are ancestors of p2 but not of p1
4033 # find nodes that are ancestors of p2 but not of p1
4033 p1 = repo.lookup('.')
4034 p1 = repo.lookup('.')
4034 p2 = node
4035 p2 = node
4035 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4036 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4036
4037
4037 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4038 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4038 for node in nodes:
4039 for node in nodes:
4039 displayer.show(repo[node])
4040 displayer.show(repo[node])
4040 displayer.close()
4041 displayer.close()
4041 return 0
4042 return 0
4042
4043
4043 # ui.forcemerge is an internal variable, do not document
4044 # ui.forcemerge is an internal variable, do not document
4044 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4045 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4045 with ui.configoverride(overrides, 'merge'):
4046 with ui.configoverride(overrides, 'merge'):
4046 force = opts.get('force')
4047 force = opts.get('force')
4047 labels = ['working copy', 'merge rev']
4048 labels = ['working copy', 'merge rev']
4048 return hg.merge(repo, node, force=force, mergeforce=force,
4049 return hg.merge(repo, node, force=force, mergeforce=force,
4049 labels=labels, abort=abort)
4050 labels=labels, abort=abort)
4050
4051
4051 statemod.addunfinished(
4052 statemod.addunfinished(
4052 'merge', fname=None, clearable=True, allowcommit=True,
4053 'merge', fname=None, clearable=True, allowcommit=True,
4053 cmdmsg=_('outstanding uncommitted merge'), abortfunc=hg.abortmerge,
4054 cmdmsg=_('outstanding uncommitted merge'), abortfunc=hg.abortmerge,
4054 statushint=_('To continue: hg commit\n'
4055 statushint=_('To continue: hg commit\n'
4055 'To abort: hg merge --abort'),
4056 'To abort: hg merge --abort'),
4056 cmdhint=_("use 'hg commit' or 'hg merge --abort'")
4057 cmdhint=_("use 'hg commit' or 'hg merge --abort'")
4057 )
4058 )
4058
4059
4059 @command('outgoing|out',
4060 @command('outgoing|out',
4060 [('f', 'force', None, _('run even when the destination is unrelated')),
4061 [('f', 'force', None, _('run even when the destination is unrelated')),
4061 ('r', 'rev', [],
4062 ('r', 'rev', [],
4062 _('a changeset intended to be included in the destination'), _('REV')),
4063 _('a changeset intended to be included in the destination'), _('REV')),
4063 ('n', 'newest-first', None, _('show newest record first')),
4064 ('n', 'newest-first', None, _('show newest record first')),
4064 ('B', 'bookmarks', False, _('compare bookmarks')),
4065 ('B', 'bookmarks', False, _('compare bookmarks')),
4065 ('b', 'branch', [], _('a specific branch you would like to push'),
4066 ('b', 'branch', [], _('a specific branch you would like to push'),
4066 _('BRANCH')),
4067 _('BRANCH')),
4067 ] + logopts + remoteopts + subrepoopts,
4068 ] + logopts + remoteopts + subrepoopts,
4068 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4069 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4069 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4070 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4070 def outgoing(ui, repo, dest=None, **opts):
4071 def outgoing(ui, repo, dest=None, **opts):
4071 """show changesets not found in the destination
4072 """show changesets not found in the destination
4072
4073
4073 Show changesets not found in the specified destination repository
4074 Show changesets not found in the specified destination repository
4074 or the default push location. These are the changesets that would
4075 or the default push location. These are the changesets that would
4075 be pushed if a push was requested.
4076 be pushed if a push was requested.
4076
4077
4077 See pull for details of valid destination formats.
4078 See pull for details of valid destination formats.
4078
4079
4079 .. container:: verbose
4080 .. container:: verbose
4080
4081
4081 With -B/--bookmarks, the result of bookmark comparison between
4082 With -B/--bookmarks, the result of bookmark comparison between
4082 local and remote repositories is displayed. With -v/--verbose,
4083 local and remote repositories is displayed. With -v/--verbose,
4083 status is also displayed for each bookmark like below::
4084 status is also displayed for each bookmark like below::
4084
4085
4085 BM1 01234567890a added
4086 BM1 01234567890a added
4086 BM2 deleted
4087 BM2 deleted
4087 BM3 234567890abc advanced
4088 BM3 234567890abc advanced
4088 BM4 34567890abcd diverged
4089 BM4 34567890abcd diverged
4089 BM5 4567890abcde changed
4090 BM5 4567890abcde changed
4090
4091
4091 The action taken when pushing depends on the
4092 The action taken when pushing depends on the
4092 status of each bookmark:
4093 status of each bookmark:
4093
4094
4094 :``added``: push with ``-B`` will create it
4095 :``added``: push with ``-B`` will create it
4095 :``deleted``: push with ``-B`` will delete it
4096 :``deleted``: push with ``-B`` will delete it
4096 :``advanced``: push will update it
4097 :``advanced``: push will update it
4097 :``diverged``: push with ``-B`` will update it
4098 :``diverged``: push with ``-B`` will update it
4098 :``changed``: push with ``-B`` will update it
4099 :``changed``: push with ``-B`` will update it
4099
4100
4100 From the point of view of pushing behavior, bookmarks
4101 From the point of view of pushing behavior, bookmarks
4101 existing only in the remote repository are treated as
4102 existing only in the remote repository are treated as
4102 ``deleted``, even if it is in fact added remotely.
4103 ``deleted``, even if it is in fact added remotely.
4103
4104
4104 Returns 0 if there are outgoing changes, 1 otherwise.
4105 Returns 0 if there are outgoing changes, 1 otherwise.
4105 """
4106 """
4106 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4107 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4107 # style URLs, so don't overwrite dest.
4108 # style URLs, so don't overwrite dest.
4108 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4109 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4109 if not path:
4110 if not path:
4110 raise error.Abort(_('default repository not configured!'),
4111 raise error.Abort(_('default repository not configured!'),
4111 hint=_("see 'hg help config.paths'"))
4112 hint=_("see 'hg help config.paths'"))
4112
4113
4113 opts = pycompat.byteskwargs(opts)
4114 opts = pycompat.byteskwargs(opts)
4114 if opts.get('graph'):
4115 if opts.get('graph'):
4115 logcmdutil.checkunsupportedgraphflags([], opts)
4116 logcmdutil.checkunsupportedgraphflags([], opts)
4116 o, other = hg._outgoing(ui, repo, dest, opts)
4117 o, other = hg._outgoing(ui, repo, dest, opts)
4117 if not o:
4118 if not o:
4118 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4119 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4119 return
4120 return
4120
4121
4121 revdag = logcmdutil.graphrevs(repo, o, opts)
4122 revdag = logcmdutil.graphrevs(repo, o, opts)
4122 ui.pager('outgoing')
4123 ui.pager('outgoing')
4123 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4124 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4124 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4125 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4125 graphmod.asciiedges)
4126 graphmod.asciiedges)
4126 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4127 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4127 return 0
4128 return 0
4128
4129
4129 if opts.get('bookmarks'):
4130 if opts.get('bookmarks'):
4130 dest = path.pushloc or path.loc
4131 dest = path.pushloc or path.loc
4131 other = hg.peer(repo, opts, dest)
4132 other = hg.peer(repo, opts, dest)
4132 if 'bookmarks' not in other.listkeys('namespaces'):
4133 if 'bookmarks' not in other.listkeys('namespaces'):
4133 ui.warn(_("remote doesn't support bookmarks\n"))
4134 ui.warn(_("remote doesn't support bookmarks\n"))
4134 return 0
4135 return 0
4135 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4136 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4136 ui.pager('outgoing')
4137 ui.pager('outgoing')
4137 return bookmarks.outgoing(ui, repo, other)
4138 return bookmarks.outgoing(ui, repo, other)
4138
4139
4139 repo._subtoppath = path.pushloc or path.loc
4140 repo._subtoppath = path.pushloc or path.loc
4140 try:
4141 try:
4141 return hg.outgoing(ui, repo, dest, opts)
4142 return hg.outgoing(ui, repo, dest, opts)
4142 finally:
4143 finally:
4143 del repo._subtoppath
4144 del repo._subtoppath
4144
4145
4145 @command('parents',
4146 @command('parents',
4146 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4147 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4147 ] + templateopts,
4148 ] + templateopts,
4148 _('[-r REV] [FILE]'),
4149 _('[-r REV] [FILE]'),
4149 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4150 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4150 inferrepo=True)
4151 inferrepo=True)
4151 def parents(ui, repo, file_=None, **opts):
4152 def parents(ui, repo, file_=None, **opts):
4152 """show the parents of the working directory or revision (DEPRECATED)
4153 """show the parents of the working directory or revision (DEPRECATED)
4153
4154
4154 Print the working directory's parent revisions. If a revision is
4155 Print the working directory's parent revisions. If a revision is
4155 given via -r/--rev, the parent of that revision will be printed.
4156 given via -r/--rev, the parent of that revision will be printed.
4156 If a file argument is given, the revision in which the file was
4157 If a file argument is given, the revision in which the file was
4157 last changed (before the working directory revision or the
4158 last changed (before the working directory revision or the
4158 argument to --rev if given) is printed.
4159 argument to --rev if given) is printed.
4159
4160
4160 This command is equivalent to::
4161 This command is equivalent to::
4161
4162
4162 hg log -r "p1()+p2()" or
4163 hg log -r "p1()+p2()" or
4163 hg log -r "p1(REV)+p2(REV)" or
4164 hg log -r "p1(REV)+p2(REV)" or
4164 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4165 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4165 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4166 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4166
4167
4167 See :hg:`summary` and :hg:`help revsets` for related information.
4168 See :hg:`summary` and :hg:`help revsets` for related information.
4168
4169
4169 Returns 0 on success.
4170 Returns 0 on success.
4170 """
4171 """
4171
4172
4172 opts = pycompat.byteskwargs(opts)
4173 opts = pycompat.byteskwargs(opts)
4173 rev = opts.get('rev')
4174 rev = opts.get('rev')
4174 if rev:
4175 if rev:
4175 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4176 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4176 ctx = scmutil.revsingle(repo, rev, None)
4177 ctx = scmutil.revsingle(repo, rev, None)
4177
4178
4178 if file_:
4179 if file_:
4179 m = scmutil.match(ctx, (file_,), opts)
4180 m = scmutil.match(ctx, (file_,), opts)
4180 if m.anypats() or len(m.files()) != 1:
4181 if m.anypats() or len(m.files()) != 1:
4181 raise error.Abort(_('can only specify an explicit filename'))
4182 raise error.Abort(_('can only specify an explicit filename'))
4182 file_ = m.files()[0]
4183 file_ = m.files()[0]
4183 filenodes = []
4184 filenodes = []
4184 for cp in ctx.parents():
4185 for cp in ctx.parents():
4185 if not cp:
4186 if not cp:
4186 continue
4187 continue
4187 try:
4188 try:
4188 filenodes.append(cp.filenode(file_))
4189 filenodes.append(cp.filenode(file_))
4189 except error.LookupError:
4190 except error.LookupError:
4190 pass
4191 pass
4191 if not filenodes:
4192 if not filenodes:
4192 raise error.Abort(_("'%s' not found in manifest!") % file_)
4193 raise error.Abort(_("'%s' not found in manifest!") % file_)
4193 p = []
4194 p = []
4194 for fn in filenodes:
4195 for fn in filenodes:
4195 fctx = repo.filectx(file_, fileid=fn)
4196 fctx = repo.filectx(file_, fileid=fn)
4196 p.append(fctx.node())
4197 p.append(fctx.node())
4197 else:
4198 else:
4198 p = [cp.node() for cp in ctx.parents()]
4199 p = [cp.node() for cp in ctx.parents()]
4199
4200
4200 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4201 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4201 for n in p:
4202 for n in p:
4202 if n != nullid:
4203 if n != nullid:
4203 displayer.show(repo[n])
4204 displayer.show(repo[n])
4204 displayer.close()
4205 displayer.close()
4205
4206
4206 @command('paths', formatteropts, _('[NAME]'),
4207 @command('paths', formatteropts, _('[NAME]'),
4207 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4208 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4208 optionalrepo=True, intents={INTENT_READONLY})
4209 optionalrepo=True, intents={INTENT_READONLY})
4209 def paths(ui, repo, search=None, **opts):
4210 def paths(ui, repo, search=None, **opts):
4210 """show aliases for remote repositories
4211 """show aliases for remote repositories
4211
4212
4212 Show definition of symbolic path name NAME. If no name is given,
4213 Show definition of symbolic path name NAME. If no name is given,
4213 show definition of all available names.
4214 show definition of all available names.
4214
4215
4215 Option -q/--quiet suppresses all output when searching for NAME
4216 Option -q/--quiet suppresses all output when searching for NAME
4216 and shows only the path names when listing all definitions.
4217 and shows only the path names when listing all definitions.
4217
4218
4218 Path names are defined in the [paths] section of your
4219 Path names are defined in the [paths] section of your
4219 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4220 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4220 repository, ``.hg/hgrc`` is used, too.
4221 repository, ``.hg/hgrc`` is used, too.
4221
4222
4222 The path names ``default`` and ``default-push`` have a special
4223 The path names ``default`` and ``default-push`` have a special
4223 meaning. When performing a push or pull operation, they are used
4224 meaning. When performing a push or pull operation, they are used
4224 as fallbacks if no location is specified on the command-line.
4225 as fallbacks if no location is specified on the command-line.
4225 When ``default-push`` is set, it will be used for push and
4226 When ``default-push`` is set, it will be used for push and
4226 ``default`` will be used for pull; otherwise ``default`` is used
4227 ``default`` will be used for pull; otherwise ``default`` is used
4227 as the fallback for both. When cloning a repository, the clone
4228 as the fallback for both. When cloning a repository, the clone
4228 source is written as ``default`` in ``.hg/hgrc``.
4229 source is written as ``default`` in ``.hg/hgrc``.
4229
4230
4230 .. note::
4231 .. note::
4231
4232
4232 ``default`` and ``default-push`` apply to all inbound (e.g.
4233 ``default`` and ``default-push`` apply to all inbound (e.g.
4233 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4234 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4234 and :hg:`bundle`) operations.
4235 and :hg:`bundle`) operations.
4235
4236
4236 See :hg:`help urls` for more information.
4237 See :hg:`help urls` for more information.
4237
4238
4238 .. container:: verbose
4239 .. container:: verbose
4239
4240
4240 Template:
4241 Template:
4241
4242
4242 The following keywords are supported. See also :hg:`help templates`.
4243 The following keywords are supported. See also :hg:`help templates`.
4243
4244
4244 :name: String. Symbolic name of the path alias.
4245 :name: String. Symbolic name of the path alias.
4245 :pushurl: String. URL for push operations.
4246 :pushurl: String. URL for push operations.
4246 :url: String. URL or directory path for the other operations.
4247 :url: String. URL or directory path for the other operations.
4247
4248
4248 Returns 0 on success.
4249 Returns 0 on success.
4249 """
4250 """
4250
4251
4251 opts = pycompat.byteskwargs(opts)
4252 opts = pycompat.byteskwargs(opts)
4252 ui.pager('paths')
4253 ui.pager('paths')
4253 if search:
4254 if search:
4254 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4255 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4255 if name == search]
4256 if name == search]
4256 else:
4257 else:
4257 pathitems = sorted(ui.paths.iteritems())
4258 pathitems = sorted(ui.paths.iteritems())
4258
4259
4259 fm = ui.formatter('paths', opts)
4260 fm = ui.formatter('paths', opts)
4260 if fm.isplain():
4261 if fm.isplain():
4261 hidepassword = util.hidepassword
4262 hidepassword = util.hidepassword
4262 else:
4263 else:
4263 hidepassword = bytes
4264 hidepassword = bytes
4264 if ui.quiet:
4265 if ui.quiet:
4265 namefmt = '%s\n'
4266 namefmt = '%s\n'
4266 else:
4267 else:
4267 namefmt = '%s = '
4268 namefmt = '%s = '
4268 showsubopts = not search and not ui.quiet
4269 showsubopts = not search and not ui.quiet
4269
4270
4270 for name, path in pathitems:
4271 for name, path in pathitems:
4271 fm.startitem()
4272 fm.startitem()
4272 fm.condwrite(not search, 'name', namefmt, name)
4273 fm.condwrite(not search, 'name', namefmt, name)
4273 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4274 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4274 for subopt, value in sorted(path.suboptions.items()):
4275 for subopt, value in sorted(path.suboptions.items()):
4275 assert subopt not in ('name', 'url')
4276 assert subopt not in ('name', 'url')
4276 if showsubopts:
4277 if showsubopts:
4277 fm.plain('%s:%s = ' % (name, subopt))
4278 fm.plain('%s:%s = ' % (name, subopt))
4278 fm.condwrite(showsubopts, subopt, '%s\n', value)
4279 fm.condwrite(showsubopts, subopt, '%s\n', value)
4279
4280
4280 fm.end()
4281 fm.end()
4281
4282
4282 if search and not pathitems:
4283 if search and not pathitems:
4283 if not ui.quiet:
4284 if not ui.quiet:
4284 ui.warn(_("not found!\n"))
4285 ui.warn(_("not found!\n"))
4285 return 1
4286 return 1
4286 else:
4287 else:
4287 return 0
4288 return 0
4288
4289
4289 @command('phase',
4290 @command('phase',
4290 [('p', 'public', False, _('set changeset phase to public')),
4291 [('p', 'public', False, _('set changeset phase to public')),
4291 ('d', 'draft', False, _('set changeset phase to draft')),
4292 ('d', 'draft', False, _('set changeset phase to draft')),
4292 ('s', 'secret', False, _('set changeset phase to secret')),
4293 ('s', 'secret', False, _('set changeset phase to secret')),
4293 ('f', 'force', False, _('allow to move boundary backward')),
4294 ('f', 'force', False, _('allow to move boundary backward')),
4294 ('r', 'rev', [], _('target revision'), _('REV')),
4295 ('r', 'rev', [], _('target revision'), _('REV')),
4295 ],
4296 ],
4296 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4297 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4297 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4298 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4298 def phase(ui, repo, *revs, **opts):
4299 def phase(ui, repo, *revs, **opts):
4299 """set or show the current phase name
4300 """set or show the current phase name
4300
4301
4301 With no argument, show the phase name of the current revision(s).
4302 With no argument, show the phase name of the current revision(s).
4302
4303
4303 With one of -p/--public, -d/--draft or -s/--secret, change the
4304 With one of -p/--public, -d/--draft or -s/--secret, change the
4304 phase value of the specified revisions.
4305 phase value of the specified revisions.
4305
4306
4306 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4307 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4307 lower phase to a higher phase. Phases are ordered as follows::
4308 lower phase to a higher phase. Phases are ordered as follows::
4308
4309
4309 public < draft < secret
4310 public < draft < secret
4310
4311
4311 Returns 0 on success, 1 if some phases could not be changed.
4312 Returns 0 on success, 1 if some phases could not be changed.
4312
4313
4313 (For more information about the phases concept, see :hg:`help phases`.)
4314 (For more information about the phases concept, see :hg:`help phases`.)
4314 """
4315 """
4315 opts = pycompat.byteskwargs(opts)
4316 opts = pycompat.byteskwargs(opts)
4316 # search for a unique phase argument
4317 # search for a unique phase argument
4317 targetphase = None
4318 targetphase = None
4318 for idx, name in enumerate(phases.cmdphasenames):
4319 for idx, name in enumerate(phases.cmdphasenames):
4319 if opts[name]:
4320 if opts[name]:
4320 if targetphase is not None:
4321 if targetphase is not None:
4321 raise error.Abort(_('only one phase can be specified'))
4322 raise error.Abort(_('only one phase can be specified'))
4322 targetphase = idx
4323 targetphase = idx
4323
4324
4324 # look for specified revision
4325 # look for specified revision
4325 revs = list(revs)
4326 revs = list(revs)
4326 revs.extend(opts['rev'])
4327 revs.extend(opts['rev'])
4327 if not revs:
4328 if not revs:
4328 # display both parents as the second parent phase can influence
4329 # display both parents as the second parent phase can influence
4329 # the phase of a merge commit
4330 # the phase of a merge commit
4330 revs = [c.rev() for c in repo[None].parents()]
4331 revs = [c.rev() for c in repo[None].parents()]
4331
4332
4332 revs = scmutil.revrange(repo, revs)
4333 revs = scmutil.revrange(repo, revs)
4333
4334
4334 ret = 0
4335 ret = 0
4335 if targetphase is None:
4336 if targetphase is None:
4336 # display
4337 # display
4337 for r in revs:
4338 for r in revs:
4338 ctx = repo[r]
4339 ctx = repo[r]
4339 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4340 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4340 else:
4341 else:
4341 with repo.lock(), repo.transaction("phase") as tr:
4342 with repo.lock(), repo.transaction("phase") as tr:
4342 # set phase
4343 # set phase
4343 if not revs:
4344 if not revs:
4344 raise error.Abort(_('empty revision set'))
4345 raise error.Abort(_('empty revision set'))
4345 nodes = [repo[r].node() for r in revs]
4346 nodes = [repo[r].node() for r in revs]
4346 # moving revision from public to draft may hide them
4347 # moving revision from public to draft may hide them
4347 # We have to check result on an unfiltered repository
4348 # We have to check result on an unfiltered repository
4348 unfi = repo.unfiltered()
4349 unfi = repo.unfiltered()
4349 getphase = unfi._phasecache.phase
4350 getphase = unfi._phasecache.phase
4350 olddata = [getphase(unfi, r) for r in unfi]
4351 olddata = [getphase(unfi, r) for r in unfi]
4351 phases.advanceboundary(repo, tr, targetphase, nodes)
4352 phases.advanceboundary(repo, tr, targetphase, nodes)
4352 if opts['force']:
4353 if opts['force']:
4353 phases.retractboundary(repo, tr, targetphase, nodes)
4354 phases.retractboundary(repo, tr, targetphase, nodes)
4354 getphase = unfi._phasecache.phase
4355 getphase = unfi._phasecache.phase
4355 newdata = [getphase(unfi, r) for r in unfi]
4356 newdata = [getphase(unfi, r) for r in unfi]
4356 changes = sum(newdata[r] != olddata[r] for r in unfi)
4357 changes = sum(newdata[r] != olddata[r] for r in unfi)
4357 cl = unfi.changelog
4358 cl = unfi.changelog
4358 rejected = [n for n in nodes
4359 rejected = [n for n in nodes
4359 if newdata[cl.rev(n)] < targetphase]
4360 if newdata[cl.rev(n)] < targetphase]
4360 if rejected:
4361 if rejected:
4361 ui.warn(_('cannot move %i changesets to a higher '
4362 ui.warn(_('cannot move %i changesets to a higher '
4362 'phase, use --force\n') % len(rejected))
4363 'phase, use --force\n') % len(rejected))
4363 ret = 1
4364 ret = 1
4364 if changes:
4365 if changes:
4365 msg = _('phase changed for %i changesets\n') % changes
4366 msg = _('phase changed for %i changesets\n') % changes
4366 if ret:
4367 if ret:
4367 ui.status(msg)
4368 ui.status(msg)
4368 else:
4369 else:
4369 ui.note(msg)
4370 ui.note(msg)
4370 else:
4371 else:
4371 ui.warn(_('no phases changed\n'))
4372 ui.warn(_('no phases changed\n'))
4372 return ret
4373 return ret
4373
4374
4374 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4375 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4375 """Run after a changegroup has been added via pull/unbundle
4376 """Run after a changegroup has been added via pull/unbundle
4376
4377
4377 This takes arguments below:
4378 This takes arguments below:
4378
4379
4379 :modheads: change of heads by pull/unbundle
4380 :modheads: change of heads by pull/unbundle
4380 :optupdate: updating working directory is needed or not
4381 :optupdate: updating working directory is needed or not
4381 :checkout: update destination revision (or None to default destination)
4382 :checkout: update destination revision (or None to default destination)
4382 :brev: a name, which might be a bookmark to be activated after updating
4383 :brev: a name, which might be a bookmark to be activated after updating
4383 """
4384 """
4384 if modheads == 0:
4385 if modheads == 0:
4385 return
4386 return
4386 if optupdate:
4387 if optupdate:
4387 try:
4388 try:
4388 return hg.updatetotally(ui, repo, checkout, brev)
4389 return hg.updatetotally(ui, repo, checkout, brev)
4389 except error.UpdateAbort as inst:
4390 except error.UpdateAbort as inst:
4390 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4391 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4391 hint = inst.hint
4392 hint = inst.hint
4392 raise error.UpdateAbort(msg, hint=hint)
4393 raise error.UpdateAbort(msg, hint=hint)
4393 if modheads is not None and modheads > 1:
4394 if modheads is not None and modheads > 1:
4394 currentbranchheads = len(repo.branchheads())
4395 currentbranchheads = len(repo.branchheads())
4395 if currentbranchheads == modheads:
4396 if currentbranchheads == modheads:
4396 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4397 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4397 elif currentbranchheads > 1:
4398 elif currentbranchheads > 1:
4398 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4399 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4399 "merge)\n"))
4400 "merge)\n"))
4400 else:
4401 else:
4401 ui.status(_("(run 'hg heads' to see heads)\n"))
4402 ui.status(_("(run 'hg heads' to see heads)\n"))
4402 elif not ui.configbool('commands', 'update.requiredest'):
4403 elif not ui.configbool('commands', 'update.requiredest'):
4403 ui.status(_("(run 'hg update' to get a working copy)\n"))
4404 ui.status(_("(run 'hg update' to get a working copy)\n"))
4404
4405
4405 @command('pull',
4406 @command('pull',
4406 [('u', 'update', None,
4407 [('u', 'update', None,
4407 _('update to new branch head if new descendants were pulled')),
4408 _('update to new branch head if new descendants were pulled')),
4408 ('f', 'force', None, _('run even when remote repository is unrelated')),
4409 ('f', 'force', None, _('run even when remote repository is unrelated')),
4409 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4410 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4410 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4411 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4411 ('b', 'branch', [], _('a specific branch you would like to pull'),
4412 ('b', 'branch', [], _('a specific branch you would like to pull'),
4412 _('BRANCH')),
4413 _('BRANCH')),
4413 ] + remoteopts,
4414 ] + remoteopts,
4414 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4415 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4415 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4416 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4416 helpbasic=True)
4417 helpbasic=True)
4417 def pull(ui, repo, source="default", **opts):
4418 def pull(ui, repo, source="default", **opts):
4418 """pull changes from the specified source
4419 """pull changes from the specified source
4419
4420
4420 Pull changes from a remote repository to a local one.
4421 Pull changes from a remote repository to a local one.
4421
4422
4422 This finds all changes from the repository at the specified path
4423 This finds all changes from the repository at the specified path
4423 or URL and adds them to a local repository (the current one unless
4424 or URL and adds them to a local repository (the current one unless
4424 -R is specified). By default, this does not update the copy of the
4425 -R is specified). By default, this does not update the copy of the
4425 project in the working directory.
4426 project in the working directory.
4426
4427
4427 When cloning from servers that support it, Mercurial may fetch
4428 When cloning from servers that support it, Mercurial may fetch
4428 pre-generated data. When this is done, hooks operating on incoming
4429 pre-generated data. When this is done, hooks operating on incoming
4429 changesets and changegroups may fire more than once, once for each
4430 changesets and changegroups may fire more than once, once for each
4430 pre-generated bundle and as well as for any additional remaining
4431 pre-generated bundle and as well as for any additional remaining
4431 data. See :hg:`help -e clonebundles` for more.
4432 data. See :hg:`help -e clonebundles` for more.
4432
4433
4433 Use :hg:`incoming` if you want to see what would have been added
4434 Use :hg:`incoming` if you want to see what would have been added
4434 by a pull at the time you issued this command. If you then decide
4435 by a pull at the time you issued this command. If you then decide
4435 to add those changes to the repository, you should use :hg:`pull
4436 to add those changes to the repository, you should use :hg:`pull
4436 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4437 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4437
4438
4438 If SOURCE is omitted, the 'default' path will be used.
4439 If SOURCE is omitted, the 'default' path will be used.
4439 See :hg:`help urls` for more information.
4440 See :hg:`help urls` for more information.
4440
4441
4441 Specifying bookmark as ``.`` is equivalent to specifying the active
4442 Specifying bookmark as ``.`` is equivalent to specifying the active
4442 bookmark's name.
4443 bookmark's name.
4443
4444
4444 Returns 0 on success, 1 if an update had unresolved files.
4445 Returns 0 on success, 1 if an update had unresolved files.
4445 """
4446 """
4446
4447
4447 opts = pycompat.byteskwargs(opts)
4448 opts = pycompat.byteskwargs(opts)
4448 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4449 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4449 msg = _('update destination required by configuration')
4450 msg = _('update destination required by configuration')
4450 hint = _('use hg pull followed by hg update DEST')
4451 hint = _('use hg pull followed by hg update DEST')
4451 raise error.Abort(msg, hint=hint)
4452 raise error.Abort(msg, hint=hint)
4452
4453
4453 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4454 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4454 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4455 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4455 other = hg.peer(repo, opts, source)
4456 other = hg.peer(repo, opts, source)
4456 try:
4457 try:
4457 revs, checkout = hg.addbranchrevs(repo, other, branches,
4458 revs, checkout = hg.addbranchrevs(repo, other, branches,
4458 opts.get('rev'))
4459 opts.get('rev'))
4459
4460
4460 pullopargs = {}
4461 pullopargs = {}
4461
4462
4462 nodes = None
4463 nodes = None
4463 if opts.get('bookmark') or revs:
4464 if opts.get('bookmark') or revs:
4464 # The list of bookmark used here is the same used to actually update
4465 # The list of bookmark used here is the same used to actually update
4465 # the bookmark names, to avoid the race from issue 4689 and we do
4466 # the bookmark names, to avoid the race from issue 4689 and we do
4466 # all lookup and bookmark queries in one go so they see the same
4467 # all lookup and bookmark queries in one go so they see the same
4467 # version of the server state (issue 4700).
4468 # version of the server state (issue 4700).
4468 nodes = []
4469 nodes = []
4469 fnodes = []
4470 fnodes = []
4470 revs = revs or []
4471 revs = revs or []
4471 if revs and not other.capable('lookup'):
4472 if revs and not other.capable('lookup'):
4472 err = _("other repository doesn't support revision lookup, "
4473 err = _("other repository doesn't support revision lookup, "
4473 "so a rev cannot be specified.")
4474 "so a rev cannot be specified.")
4474 raise error.Abort(err)
4475 raise error.Abort(err)
4475 with other.commandexecutor() as e:
4476 with other.commandexecutor() as e:
4476 fremotebookmarks = e.callcommand('listkeys', {
4477 fremotebookmarks = e.callcommand('listkeys', {
4477 'namespace': 'bookmarks'
4478 'namespace': 'bookmarks'
4478 })
4479 })
4479 for r in revs:
4480 for r in revs:
4480 fnodes.append(e.callcommand('lookup', {'key': r}))
4481 fnodes.append(e.callcommand('lookup', {'key': r}))
4481 remotebookmarks = fremotebookmarks.result()
4482 remotebookmarks = fremotebookmarks.result()
4482 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4483 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4483 pullopargs['remotebookmarks'] = remotebookmarks
4484 pullopargs['remotebookmarks'] = remotebookmarks
4484 for b in opts.get('bookmark', []):
4485 for b in opts.get('bookmark', []):
4485 b = repo._bookmarks.expandname(b)
4486 b = repo._bookmarks.expandname(b)
4486 if b not in remotebookmarks:
4487 if b not in remotebookmarks:
4487 raise error.Abort(_('remote bookmark %s not found!') % b)
4488 raise error.Abort(_('remote bookmark %s not found!') % b)
4488 nodes.append(remotebookmarks[b])
4489 nodes.append(remotebookmarks[b])
4489 for i, rev in enumerate(revs):
4490 for i, rev in enumerate(revs):
4490 node = fnodes[i].result()
4491 node = fnodes[i].result()
4491 nodes.append(node)
4492 nodes.append(node)
4492 if rev == checkout:
4493 if rev == checkout:
4493 checkout = node
4494 checkout = node
4494
4495
4495 wlock = util.nullcontextmanager()
4496 wlock = util.nullcontextmanager()
4496 if opts.get('update'):
4497 if opts.get('update'):
4497 wlock = repo.wlock()
4498 wlock = repo.wlock()
4498 with wlock:
4499 with wlock:
4499 pullopargs.update(opts.get('opargs', {}))
4500 pullopargs.update(opts.get('opargs', {}))
4500 modheads = exchange.pull(repo, other, heads=nodes,
4501 modheads = exchange.pull(repo, other, heads=nodes,
4501 force=opts.get('force'),
4502 force=opts.get('force'),
4502 bookmarks=opts.get('bookmark', ()),
4503 bookmarks=opts.get('bookmark', ()),
4503 opargs=pullopargs).cgresult
4504 opargs=pullopargs).cgresult
4504
4505
4505 # brev is a name, which might be a bookmark to be activated at
4506 # brev is a name, which might be a bookmark to be activated at
4506 # the end of the update. In other words, it is an explicit
4507 # the end of the update. In other words, it is an explicit
4507 # destination of the update
4508 # destination of the update
4508 brev = None
4509 brev = None
4509
4510
4510 if checkout:
4511 if checkout:
4511 checkout = repo.unfiltered().changelog.rev(checkout)
4512 checkout = repo.unfiltered().changelog.rev(checkout)
4512
4513
4513 # order below depends on implementation of
4514 # order below depends on implementation of
4514 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4515 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4515 # because 'checkout' is determined without it.
4516 # because 'checkout' is determined without it.
4516 if opts.get('rev'):
4517 if opts.get('rev'):
4517 brev = opts['rev'][0]
4518 brev = opts['rev'][0]
4518 elif opts.get('branch'):
4519 elif opts.get('branch'):
4519 brev = opts['branch'][0]
4520 brev = opts['branch'][0]
4520 else:
4521 else:
4521 brev = branches[0]
4522 brev = branches[0]
4522 repo._subtoppath = source
4523 repo._subtoppath = source
4523 try:
4524 try:
4524 ret = postincoming(ui, repo, modheads, opts.get('update'),
4525 ret = postincoming(ui, repo, modheads, opts.get('update'),
4525 checkout, brev)
4526 checkout, brev)
4526 except error.FilteredRepoLookupError as exc:
4527 except error.FilteredRepoLookupError as exc:
4527 msg = _('cannot update to target: %s') % exc.args[0]
4528 msg = _('cannot update to target: %s') % exc.args[0]
4528 exc.args = (msg,) + exc.args[1:]
4529 exc.args = (msg,) + exc.args[1:]
4529 raise
4530 raise
4530 finally:
4531 finally:
4531 del repo._subtoppath
4532 del repo._subtoppath
4532
4533
4533 finally:
4534 finally:
4534 other.close()
4535 other.close()
4535 return ret
4536 return ret
4536
4537
4537 @command('push',
4538 @command('push',
4538 [('f', 'force', None, _('force push')),
4539 [('f', 'force', None, _('force push')),
4539 ('r', 'rev', [],
4540 ('r', 'rev', [],
4540 _('a changeset intended to be included in the destination'),
4541 _('a changeset intended to be included in the destination'),
4541 _('REV')),
4542 _('REV')),
4542 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4543 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4543 ('b', 'branch', [],
4544 ('b', 'branch', [],
4544 _('a specific branch you would like to push'), _('BRANCH')),
4545 _('a specific branch you would like to push'), _('BRANCH')),
4545 ('', 'new-branch', False, _('allow pushing a new branch')),
4546 ('', 'new-branch', False, _('allow pushing a new branch')),
4546 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4547 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4547 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4548 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4548 ] + remoteopts,
4549 ] + remoteopts,
4549 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4550 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4550 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4551 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4551 helpbasic=True)
4552 helpbasic=True)
4552 def push(ui, repo, dest=None, **opts):
4553 def push(ui, repo, dest=None, **opts):
4553 """push changes to the specified destination
4554 """push changes to the specified destination
4554
4555
4555 Push changesets from the local repository to the specified
4556 Push changesets from the local repository to the specified
4556 destination.
4557 destination.
4557
4558
4558 This operation is symmetrical to pull: it is identical to a pull
4559 This operation is symmetrical to pull: it is identical to a pull
4559 in the destination repository from the current one.
4560 in the destination repository from the current one.
4560
4561
4561 By default, push will not allow creation of new heads at the
4562 By default, push will not allow creation of new heads at the
4562 destination, since multiple heads would make it unclear which head
4563 destination, since multiple heads would make it unclear which head
4563 to use. In this situation, it is recommended to pull and merge
4564 to use. In this situation, it is recommended to pull and merge
4564 before pushing.
4565 before pushing.
4565
4566
4566 Use --new-branch if you want to allow push to create a new named
4567 Use --new-branch if you want to allow push to create a new named
4567 branch that is not present at the destination. This allows you to
4568 branch that is not present at the destination. This allows you to
4568 only create a new branch without forcing other changes.
4569 only create a new branch without forcing other changes.
4569
4570
4570 .. note::
4571 .. note::
4571
4572
4572 Extra care should be taken with the -f/--force option,
4573 Extra care should be taken with the -f/--force option,
4573 which will push all new heads on all branches, an action which will
4574 which will push all new heads on all branches, an action which will
4574 almost always cause confusion for collaborators.
4575 almost always cause confusion for collaborators.
4575
4576
4576 If -r/--rev is used, the specified revision and all its ancestors
4577 If -r/--rev is used, the specified revision and all its ancestors
4577 will be pushed to the remote repository.
4578 will be pushed to the remote repository.
4578
4579
4579 If -B/--bookmark is used, the specified bookmarked revision, its
4580 If -B/--bookmark is used, the specified bookmarked revision, its
4580 ancestors, and the bookmark will be pushed to the remote
4581 ancestors, and the bookmark will be pushed to the remote
4581 repository. Specifying ``.`` is equivalent to specifying the active
4582 repository. Specifying ``.`` is equivalent to specifying the active
4582 bookmark's name.
4583 bookmark's name.
4583
4584
4584 Please see :hg:`help urls` for important details about ``ssh://``
4585 Please see :hg:`help urls` for important details about ``ssh://``
4585 URLs. If DESTINATION is omitted, a default path will be used.
4586 URLs. If DESTINATION is omitted, a default path will be used.
4586
4587
4587 .. container:: verbose
4588 .. container:: verbose
4588
4589
4589 The --pushvars option sends strings to the server that become
4590 The --pushvars option sends strings to the server that become
4590 environment variables prepended with ``HG_USERVAR_``. For example,
4591 environment variables prepended with ``HG_USERVAR_``. For example,
4591 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4592 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4592 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4593 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4593
4594
4594 pushvars can provide for user-overridable hooks as well as set debug
4595 pushvars can provide for user-overridable hooks as well as set debug
4595 levels. One example is having a hook that blocks commits containing
4596 levels. One example is having a hook that blocks commits containing
4596 conflict markers, but enables the user to override the hook if the file
4597 conflict markers, but enables the user to override the hook if the file
4597 is using conflict markers for testing purposes or the file format has
4598 is using conflict markers for testing purposes or the file format has
4598 strings that look like conflict markers.
4599 strings that look like conflict markers.
4599
4600
4600 By default, servers will ignore `--pushvars`. To enable it add the
4601 By default, servers will ignore `--pushvars`. To enable it add the
4601 following to your configuration file::
4602 following to your configuration file::
4602
4603
4603 [push]
4604 [push]
4604 pushvars.server = true
4605 pushvars.server = true
4605
4606
4606 Returns 0 if push was successful, 1 if nothing to push.
4607 Returns 0 if push was successful, 1 if nothing to push.
4607 """
4608 """
4608
4609
4609 opts = pycompat.byteskwargs(opts)
4610 opts = pycompat.byteskwargs(opts)
4610 if opts.get('bookmark'):
4611 if opts.get('bookmark'):
4611 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4612 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4612 for b in opts['bookmark']:
4613 for b in opts['bookmark']:
4613 # translate -B options to -r so changesets get pushed
4614 # translate -B options to -r so changesets get pushed
4614 b = repo._bookmarks.expandname(b)
4615 b = repo._bookmarks.expandname(b)
4615 if b in repo._bookmarks:
4616 if b in repo._bookmarks:
4616 opts.setdefault('rev', []).append(b)
4617 opts.setdefault('rev', []).append(b)
4617 else:
4618 else:
4618 # if we try to push a deleted bookmark, translate it to null
4619 # if we try to push a deleted bookmark, translate it to null
4619 # this lets simultaneous -r, -b options continue working
4620 # this lets simultaneous -r, -b options continue working
4620 opts.setdefault('rev', []).append("null")
4621 opts.setdefault('rev', []).append("null")
4621
4622
4622 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4623 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4623 if not path:
4624 if not path:
4624 raise error.Abort(_('default repository not configured!'),
4625 raise error.Abort(_('default repository not configured!'),
4625 hint=_("see 'hg help config.paths'"))
4626 hint=_("see 'hg help config.paths'"))
4626 dest = path.pushloc or path.loc
4627 dest = path.pushloc or path.loc
4627 branches = (path.branch, opts.get('branch') or [])
4628 branches = (path.branch, opts.get('branch') or [])
4628 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4629 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4629 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4630 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4630 other = hg.peer(repo, opts, dest)
4631 other = hg.peer(repo, opts, dest)
4631
4632
4632 if revs:
4633 if revs:
4633 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4634 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4634 if not revs:
4635 if not revs:
4635 raise error.Abort(_("specified revisions evaluate to an empty set"),
4636 raise error.Abort(_("specified revisions evaluate to an empty set"),
4636 hint=_("use different revision arguments"))
4637 hint=_("use different revision arguments"))
4637 elif path.pushrev:
4638 elif path.pushrev:
4638 # It doesn't make any sense to specify ancestor revisions. So limit
4639 # It doesn't make any sense to specify ancestor revisions. So limit
4639 # to DAG heads to make discovery simpler.
4640 # to DAG heads to make discovery simpler.
4640 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4641 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4641 revs = scmutil.revrange(repo, [expr])
4642 revs = scmutil.revrange(repo, [expr])
4642 revs = [repo[rev].node() for rev in revs]
4643 revs = [repo[rev].node() for rev in revs]
4643 if not revs:
4644 if not revs:
4644 raise error.Abort(_('default push revset for path evaluates to an '
4645 raise error.Abort(_('default push revset for path evaluates to an '
4645 'empty set'))
4646 'empty set'))
4646
4647
4647 repo._subtoppath = dest
4648 repo._subtoppath = dest
4648 try:
4649 try:
4649 # push subrepos depth-first for coherent ordering
4650 # push subrepos depth-first for coherent ordering
4650 c = repo['.']
4651 c = repo['.']
4651 subs = c.substate # only repos that are committed
4652 subs = c.substate # only repos that are committed
4652 for s in sorted(subs):
4653 for s in sorted(subs):
4653 result = c.sub(s).push(opts)
4654 result = c.sub(s).push(opts)
4654 if result == 0:
4655 if result == 0:
4655 return not result
4656 return not result
4656 finally:
4657 finally:
4657 del repo._subtoppath
4658 del repo._subtoppath
4658
4659
4659 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4660 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4660 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4661 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4661
4662
4662 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4663 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4663 newbranch=opts.get('new_branch'),
4664 newbranch=opts.get('new_branch'),
4664 bookmarks=opts.get('bookmark', ()),
4665 bookmarks=opts.get('bookmark', ()),
4665 publish=opts.get('publish'),
4666 publish=opts.get('publish'),
4666 opargs=opargs)
4667 opargs=opargs)
4667
4668
4668 result = not pushop.cgresult
4669 result = not pushop.cgresult
4669
4670
4670 if pushop.bkresult is not None:
4671 if pushop.bkresult is not None:
4671 if pushop.bkresult == 2:
4672 if pushop.bkresult == 2:
4672 result = 2
4673 result = 2
4673 elif not result and pushop.bkresult:
4674 elif not result and pushop.bkresult:
4674 result = 2
4675 result = 2
4675
4676
4676 return result
4677 return result
4677
4678
4678 @command('recover',
4679 @command('recover',
4679 [('','verify', True, "run `hg verify` after succesful recover"),
4680 [('','verify', True, "run `hg verify` after succesful recover"),
4680 ],
4681 ],
4681 helpcategory=command.CATEGORY_MAINTENANCE)
4682 helpcategory=command.CATEGORY_MAINTENANCE)
4682 def recover(ui, repo, **opts):
4683 def recover(ui, repo, **opts):
4683 """roll back an interrupted transaction
4684 """roll back an interrupted transaction
4684
4685
4685 Recover from an interrupted commit or pull.
4686 Recover from an interrupted commit or pull.
4686
4687
4687 This command tries to fix the repository status after an
4688 This command tries to fix the repository status after an
4688 interrupted operation. It should only be necessary when Mercurial
4689 interrupted operation. It should only be necessary when Mercurial
4689 suggests it.
4690 suggests it.
4690
4691
4691 Returns 0 if successful, 1 if nothing to recover or verify fails.
4692 Returns 0 if successful, 1 if nothing to recover or verify fails.
4692 """
4693 """
4693 ret = repo.recover()
4694 ret = repo.recover()
4694 if ret:
4695 if ret:
4695 if opts[r'verify']:
4696 if opts[r'verify']:
4696 return hg.verify(repo)
4697 return hg.verify(repo)
4697 else:
4698 else:
4698 msg = _("(verify step skipped, run `hg verify` to check your "
4699 msg = _("(verify step skipped, run `hg verify` to check your "
4699 "repository content)\n")
4700 "repository content)\n")
4700 ui.warn(msg)
4701 ui.warn(msg)
4701 return 0
4702 return 0
4702 return 1
4703 return 1
4703
4704
4704 @command('remove|rm',
4705 @command('remove|rm',
4705 [('A', 'after', None, _('record delete for missing files')),
4706 [('A', 'after', None, _('record delete for missing files')),
4706 ('f', 'force', None,
4707 ('f', 'force', None,
4707 _('forget added files, delete modified files')),
4708 _('forget added files, delete modified files')),
4708 ] + subrepoopts + walkopts + dryrunopts,
4709 ] + subrepoopts + walkopts + dryrunopts,
4709 _('[OPTION]... FILE...'),
4710 _('[OPTION]... FILE...'),
4710 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4711 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4711 helpbasic=True, inferrepo=True)
4712 helpbasic=True, inferrepo=True)
4712 def remove(ui, repo, *pats, **opts):
4713 def remove(ui, repo, *pats, **opts):
4713 """remove the specified files on the next commit
4714 """remove the specified files on the next commit
4714
4715
4715 Schedule the indicated files for removal from the current branch.
4716 Schedule the indicated files for removal from the current branch.
4716
4717
4717 This command schedules the files to be removed at the next commit.
4718 This command schedules the files to be removed at the next commit.
4718 To undo a remove before that, see :hg:`revert`. To undo added
4719 To undo a remove before that, see :hg:`revert`. To undo added
4719 files, see :hg:`forget`.
4720 files, see :hg:`forget`.
4720
4721
4721 .. container:: verbose
4722 .. container:: verbose
4722
4723
4723 -A/--after can be used to remove only files that have already
4724 -A/--after can be used to remove only files that have already
4724 been deleted, -f/--force can be used to force deletion, and -Af
4725 been deleted, -f/--force can be used to force deletion, and -Af
4725 can be used to remove files from the next revision without
4726 can be used to remove files from the next revision without
4726 deleting them from the working directory.
4727 deleting them from the working directory.
4727
4728
4728 The following table details the behavior of remove for different
4729 The following table details the behavior of remove for different
4729 file states (columns) and option combinations (rows). The file
4730 file states (columns) and option combinations (rows). The file
4730 states are Added [A], Clean [C], Modified [M] and Missing [!]
4731 states are Added [A], Clean [C], Modified [M] and Missing [!]
4731 (as reported by :hg:`status`). The actions are Warn, Remove
4732 (as reported by :hg:`status`). The actions are Warn, Remove
4732 (from branch) and Delete (from disk):
4733 (from branch) and Delete (from disk):
4733
4734
4734 ========= == == == ==
4735 ========= == == == ==
4735 opt/state A C M !
4736 opt/state A C M !
4736 ========= == == == ==
4737 ========= == == == ==
4737 none W RD W R
4738 none W RD W R
4738 -f R RD RD R
4739 -f R RD RD R
4739 -A W W W R
4740 -A W W W R
4740 -Af R R R R
4741 -Af R R R R
4741 ========= == == == ==
4742 ========= == == == ==
4742
4743
4743 .. note::
4744 .. note::
4744
4745
4745 :hg:`remove` never deletes files in Added [A] state from the
4746 :hg:`remove` never deletes files in Added [A] state from the
4746 working directory, not even if ``--force`` is specified.
4747 working directory, not even if ``--force`` is specified.
4747
4748
4748 Returns 0 on success, 1 if any warnings encountered.
4749 Returns 0 on success, 1 if any warnings encountered.
4749 """
4750 """
4750
4751
4751 opts = pycompat.byteskwargs(opts)
4752 opts = pycompat.byteskwargs(opts)
4752 after, force = opts.get('after'), opts.get('force')
4753 after, force = opts.get('after'), opts.get('force')
4753 dryrun = opts.get('dry_run')
4754 dryrun = opts.get('dry_run')
4754 if not pats and not after:
4755 if not pats and not after:
4755 raise error.Abort(_('no files specified'))
4756 raise error.Abort(_('no files specified'))
4756
4757
4757 m = scmutil.match(repo[None], pats, opts)
4758 m = scmutil.match(repo[None], pats, opts)
4758 subrepos = opts.get('subrepos')
4759 subrepos = opts.get('subrepos')
4759 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4760 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4760 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4761 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4761 dryrun=dryrun)
4762 dryrun=dryrun)
4762
4763
4763 @command('rename|move|mv',
4764 @command('rename|move|mv',
4764 [('A', 'after', None, _('record a rename that has already occurred')),
4765 [('A', 'after', None, _('record a rename that has already occurred')),
4765 ('f', 'force', None, _('forcibly move over an existing managed file')),
4766 ('f', 'force', None, _('forcibly move over an existing managed file')),
4766 ] + walkopts + dryrunopts,
4767 ] + walkopts + dryrunopts,
4767 _('[OPTION]... SOURCE... DEST'),
4768 _('[OPTION]... SOURCE... DEST'),
4768 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4769 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4769 def rename(ui, repo, *pats, **opts):
4770 def rename(ui, repo, *pats, **opts):
4770 """rename files; equivalent of copy + remove
4771 """rename files; equivalent of copy + remove
4771
4772
4772 Mark dest as copies of sources; mark sources for deletion. If dest
4773 Mark dest as copies of sources; mark sources for deletion. If dest
4773 is a directory, copies are put in that directory. If dest is a
4774 is a directory, copies are put in that directory. If dest is a
4774 file, there can only be one source.
4775 file, there can only be one source.
4775
4776
4776 By default, this command copies the contents of files as they
4777 By default, this command copies the contents of files as they
4777 exist in the working directory. If invoked with -A/--after, the
4778 exist in the working directory. If invoked with -A/--after, the
4778 operation is recorded, but no copying is performed.
4779 operation is recorded, but no copying is performed.
4779
4780
4780 This command takes effect at the next commit. To undo a rename
4781 This command takes effect at the next commit. To undo a rename
4781 before that, see :hg:`revert`.
4782 before that, see :hg:`revert`.
4782
4783
4783 Returns 0 on success, 1 if errors are encountered.
4784 Returns 0 on success, 1 if errors are encountered.
4784 """
4785 """
4785 opts = pycompat.byteskwargs(opts)
4786 opts = pycompat.byteskwargs(opts)
4786 with repo.wlock(False):
4787 with repo.wlock(False):
4787 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4788 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4788
4789
4789 @command('resolve',
4790 @command('resolve',
4790 [('a', 'all', None, _('select all unresolved files')),
4791 [('a', 'all', None, _('select all unresolved files')),
4791 ('l', 'list', None, _('list state of files needing merge')),
4792 ('l', 'list', None, _('list state of files needing merge')),
4792 ('m', 'mark', None, _('mark files as resolved')),
4793 ('m', 'mark', None, _('mark files as resolved')),
4793 ('u', 'unmark', None, _('mark files as unresolved')),
4794 ('u', 'unmark', None, _('mark files as unresolved')),
4794 ('n', 'no-status', None, _('hide status prefix')),
4795 ('n', 'no-status', None, _('hide status prefix')),
4795 ('', 're-merge', None, _('re-merge files'))]
4796 ('', 're-merge', None, _('re-merge files'))]
4796 + mergetoolopts + walkopts + formatteropts,
4797 + mergetoolopts + walkopts + formatteropts,
4797 _('[OPTION]... [FILE]...'),
4798 _('[OPTION]... [FILE]...'),
4798 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4799 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4799 inferrepo=True)
4800 inferrepo=True)
4800 def resolve(ui, repo, *pats, **opts):
4801 def resolve(ui, repo, *pats, **opts):
4801 """redo merges or set/view the merge status of files
4802 """redo merges or set/view the merge status of files
4802
4803
4803 Merges with unresolved conflicts are often the result of
4804 Merges with unresolved conflicts are often the result of
4804 non-interactive merging using the ``internal:merge`` configuration
4805 non-interactive merging using the ``internal:merge`` configuration
4805 setting, or a command-line merge tool like ``diff3``. The resolve
4806 setting, or a command-line merge tool like ``diff3``. The resolve
4806 command is used to manage the files involved in a merge, after
4807 command is used to manage the files involved in a merge, after
4807 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4808 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4808 working directory must have two parents). See :hg:`help
4809 working directory must have two parents). See :hg:`help
4809 merge-tools` for information on configuring merge tools.
4810 merge-tools` for information on configuring merge tools.
4810
4811
4811 The resolve command can be used in the following ways:
4812 The resolve command can be used in the following ways:
4812
4813
4813 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4814 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4814 the specified files, discarding any previous merge attempts. Re-merging
4815 the specified files, discarding any previous merge attempts. Re-merging
4815 is not performed for files already marked as resolved. Use ``--all/-a``
4816 is not performed for files already marked as resolved. Use ``--all/-a``
4816 to select all unresolved files. ``--tool`` can be used to specify
4817 to select all unresolved files. ``--tool`` can be used to specify
4817 the merge tool used for the given files. It overrides the HGMERGE
4818 the merge tool used for the given files. It overrides the HGMERGE
4818 environment variable and your configuration files. Previous file
4819 environment variable and your configuration files. Previous file
4819 contents are saved with a ``.orig`` suffix.
4820 contents are saved with a ``.orig`` suffix.
4820
4821
4821 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4822 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4822 (e.g. after having manually fixed-up the files). The default is
4823 (e.g. after having manually fixed-up the files). The default is
4823 to mark all unresolved files.
4824 to mark all unresolved files.
4824
4825
4825 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4826 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4826 default is to mark all resolved files.
4827 default is to mark all resolved files.
4827
4828
4828 - :hg:`resolve -l`: list files which had or still have conflicts.
4829 - :hg:`resolve -l`: list files which had or still have conflicts.
4829 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4830 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4830 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4831 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4831 the list. See :hg:`help filesets` for details.
4832 the list. See :hg:`help filesets` for details.
4832
4833
4833 .. note::
4834 .. note::
4834
4835
4835 Mercurial will not let you commit files with unresolved merge
4836 Mercurial will not let you commit files with unresolved merge
4836 conflicts. You must use :hg:`resolve -m ...` before you can
4837 conflicts. You must use :hg:`resolve -m ...` before you can
4837 commit after a conflicting merge.
4838 commit after a conflicting merge.
4838
4839
4839 .. container:: verbose
4840 .. container:: verbose
4840
4841
4841 Template:
4842 Template:
4842
4843
4843 The following keywords are supported in addition to the common template
4844 The following keywords are supported in addition to the common template
4844 keywords and functions. See also :hg:`help templates`.
4845 keywords and functions. See also :hg:`help templates`.
4845
4846
4846 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4847 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4847 :path: String. Repository-absolute path of the file.
4848 :path: String. Repository-absolute path of the file.
4848
4849
4849 Returns 0 on success, 1 if any files fail a resolve attempt.
4850 Returns 0 on success, 1 if any files fail a resolve attempt.
4850 """
4851 """
4851
4852
4852 opts = pycompat.byteskwargs(opts)
4853 opts = pycompat.byteskwargs(opts)
4853 confirm = ui.configbool('commands', 'resolve.confirm')
4854 confirm = ui.configbool('commands', 'resolve.confirm')
4854 flaglist = 'all mark unmark list no_status re_merge'.split()
4855 flaglist = 'all mark unmark list no_status re_merge'.split()
4855 all, mark, unmark, show, nostatus, remerge = [
4856 all, mark, unmark, show, nostatus, remerge = [
4856 opts.get(o) for o in flaglist]
4857 opts.get(o) for o in flaglist]
4857
4858
4858 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4859 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4859 if actioncount > 1:
4860 if actioncount > 1:
4860 raise error.Abort(_("too many actions specified"))
4861 raise error.Abort(_("too many actions specified"))
4861 elif (actioncount == 0
4862 elif (actioncount == 0
4862 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4863 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4863 hint = _('use --mark, --unmark, --list or --re-merge')
4864 hint = _('use --mark, --unmark, --list or --re-merge')
4864 raise error.Abort(_('no action specified'), hint=hint)
4865 raise error.Abort(_('no action specified'), hint=hint)
4865 if pats and all:
4866 if pats and all:
4866 raise error.Abort(_("can't specify --all and patterns"))
4867 raise error.Abort(_("can't specify --all and patterns"))
4867 if not (all or pats or show or mark or unmark):
4868 if not (all or pats or show or mark or unmark):
4868 raise error.Abort(_('no files or directories specified'),
4869 raise error.Abort(_('no files or directories specified'),
4869 hint=('use --all to re-merge all unresolved files'))
4870 hint=('use --all to re-merge all unresolved files'))
4870
4871
4871 if confirm:
4872 if confirm:
4872 if all:
4873 if all:
4873 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4874 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4874 b'$$ &Yes $$ &No')):
4875 b'$$ &Yes $$ &No')):
4875 raise error.Abort(_('user quit'))
4876 raise error.Abort(_('user quit'))
4876 if mark and not pats:
4877 if mark and not pats:
4877 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4878 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4878 b'$$ &Yes $$ &No')):
4879 b'$$ &Yes $$ &No')):
4879 raise error.Abort(_('user quit'))
4880 raise error.Abort(_('user quit'))
4880 if unmark and not pats:
4881 if unmark and not pats:
4881 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4882 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4882 b'$$ &Yes $$ &No')):
4883 b'$$ &Yes $$ &No')):
4883 raise error.Abort(_('user quit'))
4884 raise error.Abort(_('user quit'))
4884
4885
4885 uipathfn = scmutil.getuipathfn(repo)
4886 uipathfn = scmutil.getuipathfn(repo)
4886
4887
4887 if show:
4888 if show:
4888 ui.pager('resolve')
4889 ui.pager('resolve')
4889 fm = ui.formatter('resolve', opts)
4890 fm = ui.formatter('resolve', opts)
4890 ms = mergemod.mergestate.read(repo)
4891 ms = mergemod.mergestate.read(repo)
4891 wctx = repo[None]
4892 wctx = repo[None]
4892 m = scmutil.match(wctx, pats, opts)
4893 m = scmutil.match(wctx, pats, opts)
4893
4894
4894 # Labels and keys based on merge state. Unresolved path conflicts show
4895 # Labels and keys based on merge state. Unresolved path conflicts show
4895 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4896 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4896 # resolved conflicts.
4897 # resolved conflicts.
4897 mergestateinfo = {
4898 mergestateinfo = {
4898 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4899 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4899 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4900 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4900 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4901 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4901 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4902 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4902 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4903 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4903 'D'),
4904 'D'),
4904 }
4905 }
4905
4906
4906 for f in ms:
4907 for f in ms:
4907 if not m(f):
4908 if not m(f):
4908 continue
4909 continue
4909
4910
4910 label, key = mergestateinfo[ms[f]]
4911 label, key = mergestateinfo[ms[f]]
4911 fm.startitem()
4912 fm.startitem()
4912 fm.context(ctx=wctx)
4913 fm.context(ctx=wctx)
4913 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4914 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4914 fm.data(path=f)
4915 fm.data(path=f)
4915 fm.plain('%s\n' % uipathfn(f), label=label)
4916 fm.plain('%s\n' % uipathfn(f), label=label)
4916 fm.end()
4917 fm.end()
4917 return 0
4918 return 0
4918
4919
4919 with repo.wlock():
4920 with repo.wlock():
4920 ms = mergemod.mergestate.read(repo)
4921 ms = mergemod.mergestate.read(repo)
4921
4922
4922 if not (ms.active() or repo.dirstate.p2() != nullid):
4923 if not (ms.active() or repo.dirstate.p2() != nullid):
4923 raise error.Abort(
4924 raise error.Abort(
4924 _('resolve command not applicable when not merging'))
4925 _('resolve command not applicable when not merging'))
4925
4926
4926 wctx = repo[None]
4927 wctx = repo[None]
4927
4928
4928 if (ms.mergedriver
4929 if (ms.mergedriver
4929 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4930 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4930 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4931 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4931 ms.commit()
4932 ms.commit()
4932 # allow mark and unmark to go through
4933 # allow mark and unmark to go through
4933 if not mark and not unmark and not proceed:
4934 if not mark and not unmark and not proceed:
4934 return 1
4935 return 1
4935
4936
4936 m = scmutil.match(wctx, pats, opts)
4937 m = scmutil.match(wctx, pats, opts)
4937 ret = 0
4938 ret = 0
4938 didwork = False
4939 didwork = False
4939 runconclude = False
4940 runconclude = False
4940
4941
4941 tocomplete = []
4942 tocomplete = []
4942 hasconflictmarkers = []
4943 hasconflictmarkers = []
4943 if mark:
4944 if mark:
4944 markcheck = ui.config('commands', 'resolve.mark-check')
4945 markcheck = ui.config('commands', 'resolve.mark-check')
4945 if markcheck not in ['warn', 'abort']:
4946 if markcheck not in ['warn', 'abort']:
4946 # Treat all invalid / unrecognized values as 'none'.
4947 # Treat all invalid / unrecognized values as 'none'.
4947 markcheck = False
4948 markcheck = False
4948 for f in ms:
4949 for f in ms:
4949 if not m(f):
4950 if not m(f):
4950 continue
4951 continue
4951
4952
4952 didwork = True
4953 didwork = True
4953
4954
4954 # don't let driver-resolved files be marked, and run the conclude
4955 # don't let driver-resolved files be marked, and run the conclude
4955 # step if asked to resolve
4956 # step if asked to resolve
4956 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4957 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4957 exact = m.exact(f)
4958 exact = m.exact(f)
4958 if mark:
4959 if mark:
4959 if exact:
4960 if exact:
4960 ui.warn(_('not marking %s as it is driver-resolved\n')
4961 ui.warn(_('not marking %s as it is driver-resolved\n')
4961 % uipathfn(f))
4962 % uipathfn(f))
4962 elif unmark:
4963 elif unmark:
4963 if exact:
4964 if exact:
4964 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4965 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4965 % uipathfn(f))
4966 % uipathfn(f))
4966 else:
4967 else:
4967 runconclude = True
4968 runconclude = True
4968 continue
4969 continue
4969
4970
4970 # path conflicts must be resolved manually
4971 # path conflicts must be resolved manually
4971 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4972 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4972 mergemod.MERGE_RECORD_RESOLVED_PATH):
4973 mergemod.MERGE_RECORD_RESOLVED_PATH):
4973 if mark:
4974 if mark:
4974 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4975 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4975 elif unmark:
4976 elif unmark:
4976 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4977 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4977 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4978 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4978 ui.warn(_('%s: path conflict must be resolved manually\n')
4979 ui.warn(_('%s: path conflict must be resolved manually\n')
4979 % uipathfn(f))
4980 % uipathfn(f))
4980 continue
4981 continue
4981
4982
4982 if mark:
4983 if mark:
4983 if markcheck:
4984 if markcheck:
4984 fdata = repo.wvfs.tryread(f)
4985 fdata = repo.wvfs.tryread(f)
4985 if (filemerge.hasconflictmarkers(fdata) and
4986 if (filemerge.hasconflictmarkers(fdata) and
4986 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4987 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4987 hasconflictmarkers.append(f)
4988 hasconflictmarkers.append(f)
4988 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4989 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4989 elif unmark:
4990 elif unmark:
4990 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4991 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4991 else:
4992 else:
4992 # backup pre-resolve (merge uses .orig for its own purposes)
4993 # backup pre-resolve (merge uses .orig for its own purposes)
4993 a = repo.wjoin(f)
4994 a = repo.wjoin(f)
4994 try:
4995 try:
4995 util.copyfile(a, a + ".resolve")
4996 util.copyfile(a, a + ".resolve")
4996 except (IOError, OSError) as inst:
4997 except (IOError, OSError) as inst:
4997 if inst.errno != errno.ENOENT:
4998 if inst.errno != errno.ENOENT:
4998 raise
4999 raise
4999
5000
5000 try:
5001 try:
5001 # preresolve file
5002 # preresolve file
5002 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5003 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5003 with ui.configoverride(overrides, 'resolve'):
5004 with ui.configoverride(overrides, 'resolve'):
5004 complete, r = ms.preresolve(f, wctx)
5005 complete, r = ms.preresolve(f, wctx)
5005 if not complete:
5006 if not complete:
5006 tocomplete.append(f)
5007 tocomplete.append(f)
5007 elif r:
5008 elif r:
5008 ret = 1
5009 ret = 1
5009 finally:
5010 finally:
5010 ms.commit()
5011 ms.commit()
5011
5012
5012 # replace filemerge's .orig file with our resolve file, but only
5013 # replace filemerge's .orig file with our resolve file, but only
5013 # for merges that are complete
5014 # for merges that are complete
5014 if complete:
5015 if complete:
5015 try:
5016 try:
5016 util.rename(a + ".resolve",
5017 util.rename(a + ".resolve",
5017 scmutil.backuppath(ui, repo, f))
5018 scmutil.backuppath(ui, repo, f))
5018 except OSError as inst:
5019 except OSError as inst:
5019 if inst.errno != errno.ENOENT:
5020 if inst.errno != errno.ENOENT:
5020 raise
5021 raise
5021
5022
5022 if hasconflictmarkers:
5023 if hasconflictmarkers:
5023 ui.warn(_('warning: the following files still have conflict '
5024 ui.warn(_('warning: the following files still have conflict '
5024 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
5025 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
5025 for f in hasconflictmarkers))
5026 for f in hasconflictmarkers))
5026 if markcheck == 'abort' and not all and not pats:
5027 if markcheck == 'abort' and not all and not pats:
5027 raise error.Abort(_('conflict markers detected'),
5028 raise error.Abort(_('conflict markers detected'),
5028 hint=_('use --all to mark anyway'))
5029 hint=_('use --all to mark anyway'))
5029
5030
5030 for f in tocomplete:
5031 for f in tocomplete:
5031 try:
5032 try:
5032 # resolve file
5033 # resolve file
5033 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5034 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5034 with ui.configoverride(overrides, 'resolve'):
5035 with ui.configoverride(overrides, 'resolve'):
5035 r = ms.resolve(f, wctx)
5036 r = ms.resolve(f, wctx)
5036 if r:
5037 if r:
5037 ret = 1
5038 ret = 1
5038 finally:
5039 finally:
5039 ms.commit()
5040 ms.commit()
5040
5041
5041 # replace filemerge's .orig file with our resolve file
5042 # replace filemerge's .orig file with our resolve file
5042 a = repo.wjoin(f)
5043 a = repo.wjoin(f)
5043 try:
5044 try:
5044 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5045 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5045 except OSError as inst:
5046 except OSError as inst:
5046 if inst.errno != errno.ENOENT:
5047 if inst.errno != errno.ENOENT:
5047 raise
5048 raise
5048
5049
5049 ms.commit()
5050 ms.commit()
5050 ms.recordactions()
5051 ms.recordactions()
5051
5052
5052 if not didwork and pats:
5053 if not didwork and pats:
5053 hint = None
5054 hint = None
5054 if not any([p for p in pats if p.find(':') >= 0]):
5055 if not any([p for p in pats if p.find(':') >= 0]):
5055 pats = ['path:%s' % p for p in pats]
5056 pats = ['path:%s' % p for p in pats]
5056 m = scmutil.match(wctx, pats, opts)
5057 m = scmutil.match(wctx, pats, opts)
5057 for f in ms:
5058 for f in ms:
5058 if not m(f):
5059 if not m(f):
5059 continue
5060 continue
5060 def flag(o):
5061 def flag(o):
5061 if o == 're_merge':
5062 if o == 're_merge':
5062 return '--re-merge '
5063 return '--re-merge '
5063 return '-%s ' % o[0:1]
5064 return '-%s ' % o[0:1]
5064 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5065 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5065 hint = _("(try: hg resolve %s%s)\n") % (
5066 hint = _("(try: hg resolve %s%s)\n") % (
5066 flags,
5067 flags,
5067 ' '.join(pats))
5068 ' '.join(pats))
5068 break
5069 break
5069 ui.warn(_("arguments do not match paths that need resolving\n"))
5070 ui.warn(_("arguments do not match paths that need resolving\n"))
5070 if hint:
5071 if hint:
5071 ui.warn(hint)
5072 ui.warn(hint)
5072 elif ms.mergedriver and ms.mdstate() != 's':
5073 elif ms.mergedriver and ms.mdstate() != 's':
5073 # run conclude step when either a driver-resolved file is requested
5074 # run conclude step when either a driver-resolved file is requested
5074 # or there are no driver-resolved files
5075 # or there are no driver-resolved files
5075 # we can't use 'ret' to determine whether any files are unresolved
5076 # we can't use 'ret' to determine whether any files are unresolved
5076 # because we might not have tried to resolve some
5077 # because we might not have tried to resolve some
5077 if ((runconclude or not list(ms.driverresolved()))
5078 if ((runconclude or not list(ms.driverresolved()))
5078 and not list(ms.unresolved())):
5079 and not list(ms.unresolved())):
5079 proceed = mergemod.driverconclude(repo, ms, wctx)
5080 proceed = mergemod.driverconclude(repo, ms, wctx)
5080 ms.commit()
5081 ms.commit()
5081 if not proceed:
5082 if not proceed:
5082 return 1
5083 return 1
5083
5084
5084 # Nudge users into finishing an unfinished operation
5085 # Nudge users into finishing an unfinished operation
5085 unresolvedf = list(ms.unresolved())
5086 unresolvedf = list(ms.unresolved())
5086 driverresolvedf = list(ms.driverresolved())
5087 driverresolvedf = list(ms.driverresolved())
5087 if not unresolvedf and not driverresolvedf:
5088 if not unresolvedf and not driverresolvedf:
5088 ui.status(_('(no more unresolved files)\n'))
5089 ui.status(_('(no more unresolved files)\n'))
5089 cmdutil.checkafterresolved(repo)
5090 cmdutil.checkafterresolved(repo)
5090 elif not unresolvedf:
5091 elif not unresolvedf:
5091 ui.status(_('(no more unresolved files -- '
5092 ui.status(_('(no more unresolved files -- '
5092 'run "hg resolve --all" to conclude)\n'))
5093 'run "hg resolve --all" to conclude)\n'))
5093
5094
5094 return ret
5095 return ret
5095
5096
5096 @command('revert',
5097 @command('revert',
5097 [('a', 'all', None, _('revert all changes when no arguments given')),
5098 [('a', 'all', None, _('revert all changes when no arguments given')),
5098 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5099 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5099 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5100 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5100 ('C', 'no-backup', None, _('do not save backup copies of files')),
5101 ('C', 'no-backup', None, _('do not save backup copies of files')),
5101 ('i', 'interactive', None, _('interactively select the changes')),
5102 ('i', 'interactive', None, _('interactively select the changes')),
5102 ] + walkopts + dryrunopts,
5103 ] + walkopts + dryrunopts,
5103 _('[OPTION]... [-r REV] [NAME]...'),
5104 _('[OPTION]... [-r REV] [NAME]...'),
5104 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5105 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5105 def revert(ui, repo, *pats, **opts):
5106 def revert(ui, repo, *pats, **opts):
5106 """restore files to their checkout state
5107 """restore files to their checkout state
5107
5108
5108 .. note::
5109 .. note::
5109
5110
5110 To check out earlier revisions, you should use :hg:`update REV`.
5111 To check out earlier revisions, you should use :hg:`update REV`.
5111 To cancel an uncommitted merge (and lose your changes),
5112 To cancel an uncommitted merge (and lose your changes),
5112 use :hg:`merge --abort`.
5113 use :hg:`merge --abort`.
5113
5114
5114 With no revision specified, revert the specified files or directories
5115 With no revision specified, revert the specified files or directories
5115 to the contents they had in the parent of the working directory.
5116 to the contents they had in the parent of the working directory.
5116 This restores the contents of files to an unmodified
5117 This restores the contents of files to an unmodified
5117 state and unschedules adds, removes, copies, and renames. If the
5118 state and unschedules adds, removes, copies, and renames. If the
5118 working directory has two parents, you must explicitly specify a
5119 working directory has two parents, you must explicitly specify a
5119 revision.
5120 revision.
5120
5121
5121 Using the -r/--rev or -d/--date options, revert the given files or
5122 Using the -r/--rev or -d/--date options, revert the given files or
5122 directories to their states as of a specific revision. Because
5123 directories to their states as of a specific revision. Because
5123 revert does not change the working directory parents, this will
5124 revert does not change the working directory parents, this will
5124 cause these files to appear modified. This can be helpful to "back
5125 cause these files to appear modified. This can be helpful to "back
5125 out" some or all of an earlier change. See :hg:`backout` for a
5126 out" some or all of an earlier change. See :hg:`backout` for a
5126 related method.
5127 related method.
5127
5128
5128 Modified files are saved with a .orig suffix before reverting.
5129 Modified files are saved with a .orig suffix before reverting.
5129 To disable these backups, use --no-backup. It is possible to store
5130 To disable these backups, use --no-backup. It is possible to store
5130 the backup files in a custom directory relative to the root of the
5131 the backup files in a custom directory relative to the root of the
5131 repository by setting the ``ui.origbackuppath`` configuration
5132 repository by setting the ``ui.origbackuppath`` configuration
5132 option.
5133 option.
5133
5134
5134 See :hg:`help dates` for a list of formats valid for -d/--date.
5135 See :hg:`help dates` for a list of formats valid for -d/--date.
5135
5136
5136 See :hg:`help backout` for a way to reverse the effect of an
5137 See :hg:`help backout` for a way to reverse the effect of an
5137 earlier changeset.
5138 earlier changeset.
5138
5139
5139 Returns 0 on success.
5140 Returns 0 on success.
5140 """
5141 """
5141
5142
5142 opts = pycompat.byteskwargs(opts)
5143 opts = pycompat.byteskwargs(opts)
5143 if opts.get("date"):
5144 if opts.get("date"):
5144 if opts.get("rev"):
5145 if opts.get("rev"):
5145 raise error.Abort(_("you can't specify a revision and a date"))
5146 raise error.Abort(_("you can't specify a revision and a date"))
5146 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5147 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5147
5148
5148 parent, p2 = repo.dirstate.parents()
5149 parent, p2 = repo.dirstate.parents()
5149 if not opts.get('rev') and p2 != nullid:
5150 if not opts.get('rev') and p2 != nullid:
5150 # revert after merge is a trap for new users (issue2915)
5151 # revert after merge is a trap for new users (issue2915)
5151 raise error.Abort(_('uncommitted merge with no revision specified'),
5152 raise error.Abort(_('uncommitted merge with no revision specified'),
5152 hint=_("use 'hg update' or see 'hg help revert'"))
5153 hint=_("use 'hg update' or see 'hg help revert'"))
5153
5154
5154 rev = opts.get('rev')
5155 rev = opts.get('rev')
5155 if rev:
5156 if rev:
5156 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5157 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5157 ctx = scmutil.revsingle(repo, rev)
5158 ctx = scmutil.revsingle(repo, rev)
5158
5159
5159 if (not (pats or opts.get('include') or opts.get('exclude') or
5160 if (not (pats or opts.get('include') or opts.get('exclude') or
5160 opts.get('all') or opts.get('interactive'))):
5161 opts.get('all') or opts.get('interactive'))):
5161 msg = _("no files or directories specified")
5162 msg = _("no files or directories specified")
5162 if p2 != nullid:
5163 if p2 != nullid:
5163 hint = _("uncommitted merge, use --all to discard all changes,"
5164 hint = _("uncommitted merge, use --all to discard all changes,"
5164 " or 'hg update -C .' to abort the merge")
5165 " or 'hg update -C .' to abort the merge")
5165 raise error.Abort(msg, hint=hint)
5166 raise error.Abort(msg, hint=hint)
5166 dirty = any(repo.status())
5167 dirty = any(repo.status())
5167 node = ctx.node()
5168 node = ctx.node()
5168 if node != parent:
5169 if node != parent:
5169 if dirty:
5170 if dirty:
5170 hint = _("uncommitted changes, use --all to discard all"
5171 hint = _("uncommitted changes, use --all to discard all"
5171 " changes, or 'hg update %d' to update") % ctx.rev()
5172 " changes, or 'hg update %d' to update") % ctx.rev()
5172 else:
5173 else:
5173 hint = _("use --all to revert all files,"
5174 hint = _("use --all to revert all files,"
5174 " or 'hg update %d' to update") % ctx.rev()
5175 " or 'hg update %d' to update") % ctx.rev()
5175 elif dirty:
5176 elif dirty:
5176 hint = _("uncommitted changes, use --all to discard all changes")
5177 hint = _("uncommitted changes, use --all to discard all changes")
5177 else:
5178 else:
5178 hint = _("use --all to revert all files")
5179 hint = _("use --all to revert all files")
5179 raise error.Abort(msg, hint=hint)
5180 raise error.Abort(msg, hint=hint)
5180
5181
5181 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5182 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5182 **pycompat.strkwargs(opts))
5183 **pycompat.strkwargs(opts))
5183
5184
5184 @command(
5185 @command(
5185 'rollback',
5186 'rollback',
5186 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5187 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5187 helpcategory=command.CATEGORY_MAINTENANCE)
5188 helpcategory=command.CATEGORY_MAINTENANCE)
5188 def rollback(ui, repo, **opts):
5189 def rollback(ui, repo, **opts):
5189 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5190 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5190
5191
5191 Please use :hg:`commit --amend` instead of rollback to correct
5192 Please use :hg:`commit --amend` instead of rollback to correct
5192 mistakes in the last commit.
5193 mistakes in the last commit.
5193
5194
5194 This command should be used with care. There is only one level of
5195 This command should be used with care. There is only one level of
5195 rollback, and there is no way to undo a rollback. It will also
5196 rollback, and there is no way to undo a rollback. It will also
5196 restore the dirstate at the time of the last transaction, losing
5197 restore the dirstate at the time of the last transaction, losing
5197 any dirstate changes since that time. This command does not alter
5198 any dirstate changes since that time. This command does not alter
5198 the working directory.
5199 the working directory.
5199
5200
5200 Transactions are used to encapsulate the effects of all commands
5201 Transactions are used to encapsulate the effects of all commands
5201 that create new changesets or propagate existing changesets into a
5202 that create new changesets or propagate existing changesets into a
5202 repository.
5203 repository.
5203
5204
5204 .. container:: verbose
5205 .. container:: verbose
5205
5206
5206 For example, the following commands are transactional, and their
5207 For example, the following commands are transactional, and their
5207 effects can be rolled back:
5208 effects can be rolled back:
5208
5209
5209 - commit
5210 - commit
5210 - import
5211 - import
5211 - pull
5212 - pull
5212 - push (with this repository as the destination)
5213 - push (with this repository as the destination)
5213 - unbundle
5214 - unbundle
5214
5215
5215 To avoid permanent data loss, rollback will refuse to rollback a
5216 To avoid permanent data loss, rollback will refuse to rollback a
5216 commit transaction if it isn't checked out. Use --force to
5217 commit transaction if it isn't checked out. Use --force to
5217 override this protection.
5218 override this protection.
5218
5219
5219 The rollback command can be entirely disabled by setting the
5220 The rollback command can be entirely disabled by setting the
5220 ``ui.rollback`` configuration setting to false. If you're here
5221 ``ui.rollback`` configuration setting to false. If you're here
5221 because you want to use rollback and it's disabled, you can
5222 because you want to use rollback and it's disabled, you can
5222 re-enable the command by setting ``ui.rollback`` to true.
5223 re-enable the command by setting ``ui.rollback`` to true.
5223
5224
5224 This command is not intended for use on public repositories. Once
5225 This command is not intended for use on public repositories. Once
5225 changes are visible for pull by other users, rolling a transaction
5226 changes are visible for pull by other users, rolling a transaction
5226 back locally is ineffective (someone else may already have pulled
5227 back locally is ineffective (someone else may already have pulled
5227 the changes). Furthermore, a race is possible with readers of the
5228 the changes). Furthermore, a race is possible with readers of the
5228 repository; for example an in-progress pull from the repository
5229 repository; for example an in-progress pull from the repository
5229 may fail if a rollback is performed.
5230 may fail if a rollback is performed.
5230
5231
5231 Returns 0 on success, 1 if no rollback data is available.
5232 Returns 0 on success, 1 if no rollback data is available.
5232 """
5233 """
5233 if not ui.configbool('ui', 'rollback'):
5234 if not ui.configbool('ui', 'rollback'):
5234 raise error.Abort(_('rollback is disabled because it is unsafe'),
5235 raise error.Abort(_('rollback is disabled because it is unsafe'),
5235 hint=('see `hg help -v rollback` for information'))
5236 hint=('see `hg help -v rollback` for information'))
5236 return repo.rollback(dryrun=opts.get(r'dry_run'),
5237 return repo.rollback(dryrun=opts.get(r'dry_run'),
5237 force=opts.get(r'force'))
5238 force=opts.get(r'force'))
5238
5239
5239 @command(
5240 @command(
5240 'root', [] + formatteropts, intents={INTENT_READONLY},
5241 'root', [] + formatteropts, intents={INTENT_READONLY},
5241 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5242 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5242 def root(ui, repo, **opts):
5243 def root(ui, repo, **opts):
5243 """print the root (top) of the current working directory
5244 """print the root (top) of the current working directory
5244
5245
5245 Print the root directory of the current repository.
5246 Print the root directory of the current repository.
5246
5247
5247 .. container:: verbose
5248 .. container:: verbose
5248
5249
5249 Template:
5250 Template:
5250
5251
5251 The following keywords are supported in addition to the common template
5252 The following keywords are supported in addition to the common template
5252 keywords and functions. See also :hg:`help templates`.
5253 keywords and functions. See also :hg:`help templates`.
5253
5254
5254 :hgpath: String. Path to the .hg directory.
5255 :hgpath: String. Path to the .hg directory.
5255 :storepath: String. Path to the directory holding versioned data.
5256 :storepath: String. Path to the directory holding versioned data.
5256
5257
5257 Returns 0 on success.
5258 Returns 0 on success.
5258 """
5259 """
5259 opts = pycompat.byteskwargs(opts)
5260 opts = pycompat.byteskwargs(opts)
5260 with ui.formatter('root', opts) as fm:
5261 with ui.formatter('root', opts) as fm:
5261 fm.startitem()
5262 fm.startitem()
5262 fm.write('reporoot', '%s\n', repo.root)
5263 fm.write('reporoot', '%s\n', repo.root)
5263 fm.data(hgpath=repo.path, storepath=repo.spath)
5264 fm.data(hgpath=repo.path, storepath=repo.spath)
5264
5265
5265 @command('serve',
5266 @command('serve',
5266 [('A', 'accesslog', '', _('name of access log file to write to'),
5267 [('A', 'accesslog', '', _('name of access log file to write to'),
5267 _('FILE')),
5268 _('FILE')),
5268 ('d', 'daemon', None, _('run server in background')),
5269 ('d', 'daemon', None, _('run server in background')),
5269 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5270 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5270 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5271 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5271 # use string type, then we can check if something was passed
5272 # use string type, then we can check if something was passed
5272 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5273 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5273 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5274 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5274 _('ADDR')),
5275 _('ADDR')),
5275 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5276 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5276 _('PREFIX')),
5277 _('PREFIX')),
5277 ('n', 'name', '',
5278 ('n', 'name', '',
5278 _('name to show in web pages (default: working directory)'), _('NAME')),
5279 _('name to show in web pages (default: working directory)'), _('NAME')),
5279 ('', 'web-conf', '',
5280 ('', 'web-conf', '',
5280 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5281 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5281 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5282 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5282 _('FILE')),
5283 _('FILE')),
5283 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5284 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5284 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5285 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5285 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5286 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5286 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5287 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5287 ('', 'style', '', _('template style to use'), _('STYLE')),
5288 ('', 'style', '', _('template style to use'), _('STYLE')),
5288 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5289 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5289 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5290 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5290 ('', 'print-url', None, _('start and print only the URL'))]
5291 ('', 'print-url', None, _('start and print only the URL'))]
5291 + subrepoopts,
5292 + subrepoopts,
5292 _('[OPTION]...'),
5293 _('[OPTION]...'),
5293 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5294 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5294 helpbasic=True, optionalrepo=True)
5295 helpbasic=True, optionalrepo=True)
5295 def serve(ui, repo, **opts):
5296 def serve(ui, repo, **opts):
5296 """start stand-alone webserver
5297 """start stand-alone webserver
5297
5298
5298 Start a local HTTP repository browser and pull server. You can use
5299 Start a local HTTP repository browser and pull server. You can use
5299 this for ad-hoc sharing and browsing of repositories. It is
5300 this for ad-hoc sharing and browsing of repositories. It is
5300 recommended to use a real web server to serve a repository for
5301 recommended to use a real web server to serve a repository for
5301 longer periods of time.
5302 longer periods of time.
5302
5303
5303 Please note that the server does not implement access control.
5304 Please note that the server does not implement access control.
5304 This means that, by default, anybody can read from the server and
5305 This means that, by default, anybody can read from the server and
5305 nobody can write to it by default. Set the ``web.allow-push``
5306 nobody can write to it by default. Set the ``web.allow-push``
5306 option to ``*`` to allow everybody to push to the server. You
5307 option to ``*`` to allow everybody to push to the server. You
5307 should use a real web server if you need to authenticate users.
5308 should use a real web server if you need to authenticate users.
5308
5309
5309 By default, the server logs accesses to stdout and errors to
5310 By default, the server logs accesses to stdout and errors to
5310 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5311 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5311 files.
5312 files.
5312
5313
5313 To have the server choose a free port number to listen on, specify
5314 To have the server choose a free port number to listen on, specify
5314 a port number of 0; in this case, the server will print the port
5315 a port number of 0; in this case, the server will print the port
5315 number it uses.
5316 number it uses.
5316
5317
5317 Returns 0 on success.
5318 Returns 0 on success.
5318 """
5319 """
5319
5320
5320 opts = pycompat.byteskwargs(opts)
5321 opts = pycompat.byteskwargs(opts)
5321 if opts["stdio"] and opts["cmdserver"]:
5322 if opts["stdio"] and opts["cmdserver"]:
5322 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5323 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5323 if opts["print_url"] and ui.verbose:
5324 if opts["print_url"] and ui.verbose:
5324 raise error.Abort(_("cannot use --print-url with --verbose"))
5325 raise error.Abort(_("cannot use --print-url with --verbose"))
5325
5326
5326 if opts["stdio"]:
5327 if opts["stdio"]:
5327 if repo is None:
5328 if repo is None:
5328 raise error.RepoError(_("there is no Mercurial repository here"
5329 raise error.RepoError(_("there is no Mercurial repository here"
5329 " (.hg not found)"))
5330 " (.hg not found)"))
5330 s = wireprotoserver.sshserver(ui, repo)
5331 s = wireprotoserver.sshserver(ui, repo)
5331 s.serve_forever()
5332 s.serve_forever()
5332
5333
5333 service = server.createservice(ui, repo, opts)
5334 service = server.createservice(ui, repo, opts)
5334 return server.runservice(opts, initfn=service.init, runfn=service.run)
5335 return server.runservice(opts, initfn=service.init, runfn=service.run)
5335
5336
5336 @command('shelve',
5337 @command('shelve',
5337 [('A', 'addremove', None,
5338 [('A', 'addremove', None,
5338 _('mark new/missing files as added/removed before shelving')),
5339 _('mark new/missing files as added/removed before shelving')),
5339 ('u', 'unknown', None,
5340 ('u', 'unknown', None,
5340 _('store unknown files in the shelve')),
5341 _('store unknown files in the shelve')),
5341 ('', 'cleanup', None,
5342 ('', 'cleanup', None,
5342 _('delete all shelved changes')),
5343 _('delete all shelved changes')),
5343 ('', 'date', '',
5344 ('', 'date', '',
5344 _('shelve with the specified commit date'), _('DATE')),
5345 _('shelve with the specified commit date'), _('DATE')),
5345 ('d', 'delete', None,
5346 ('d', 'delete', None,
5346 _('delete the named shelved change(s)')),
5347 _('delete the named shelved change(s)')),
5347 ('e', 'edit', False,
5348 ('e', 'edit', False,
5348 _('invoke editor on commit messages')),
5349 _('invoke editor on commit messages')),
5349 ('k', 'keep', False,
5350 ('k', 'keep', False,
5350 _('shelve, but keep changes in the working directory')),
5351 _('shelve, but keep changes in the working directory')),
5351 ('l', 'list', None,
5352 ('l', 'list', None,
5352 _('list current shelves')),
5353 _('list current shelves')),
5353 ('m', 'message', '',
5354 ('m', 'message', '',
5354 _('use text as shelve message'), _('TEXT')),
5355 _('use text as shelve message'), _('TEXT')),
5355 ('n', 'name', '',
5356 ('n', 'name', '',
5356 _('use the given name for the shelved commit'), _('NAME')),
5357 _('use the given name for the shelved commit'), _('NAME')),
5357 ('p', 'patch', None,
5358 ('p', 'patch', None,
5358 _('output patches for changes (provide the names of the shelved '
5359 _('output patches for changes (provide the names of the shelved '
5359 'changes as positional arguments)')),
5360 'changes as positional arguments)')),
5360 ('i', 'interactive', None,
5361 ('i', 'interactive', None,
5361 _('interactive mode')),
5362 _('interactive mode')),
5362 ('', 'stat', None,
5363 ('', 'stat', None,
5363 _('output diffstat-style summary of changes (provide the names of '
5364 _('output diffstat-style summary of changes (provide the names of '
5364 'the shelved changes as positional arguments)')
5365 'the shelved changes as positional arguments)')
5365 )] + cmdutil.walkopts,
5366 )] + cmdutil.walkopts,
5366 _('hg shelve [OPTION]... [FILE]...'),
5367 _('hg shelve [OPTION]... [FILE]...'),
5367 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5368 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5368 def shelve(ui, repo, *pats, **opts):
5369 def shelve(ui, repo, *pats, **opts):
5369 '''save and set aside changes from the working directory
5370 '''save and set aside changes from the working directory
5370
5371
5371 Shelving takes files that "hg status" reports as not clean, saves
5372 Shelving takes files that "hg status" reports as not clean, saves
5372 the modifications to a bundle (a shelved change), and reverts the
5373 the modifications to a bundle (a shelved change), and reverts the
5373 files so that their state in the working directory becomes clean.
5374 files so that their state in the working directory becomes clean.
5374
5375
5375 To restore these changes to the working directory, using "hg
5376 To restore these changes to the working directory, using "hg
5376 unshelve"; this will work even if you switch to a different
5377 unshelve"; this will work even if you switch to a different
5377 commit.
5378 commit.
5378
5379
5379 When no files are specified, "hg shelve" saves all not-clean
5380 When no files are specified, "hg shelve" saves all not-clean
5380 files. If specific files or directories are named, only changes to
5381 files. If specific files or directories are named, only changes to
5381 those files are shelved.
5382 those files are shelved.
5382
5383
5383 In bare shelve (when no files are specified, without interactive,
5384 In bare shelve (when no files are specified, without interactive,
5384 include and exclude option), shelving remembers information if the
5385 include and exclude option), shelving remembers information if the
5385 working directory was on newly created branch, in other words working
5386 working directory was on newly created branch, in other words working
5386 directory was on different branch than its first parent. In this
5387 directory was on different branch than its first parent. In this
5387 situation unshelving restores branch information to the working directory.
5388 situation unshelving restores branch information to the working directory.
5388
5389
5389 Each shelved change has a name that makes it easier to find later.
5390 Each shelved change has a name that makes it easier to find later.
5390 The name of a shelved change defaults to being based on the active
5391 The name of a shelved change defaults to being based on the active
5391 bookmark, or if there is no active bookmark, the current named
5392 bookmark, or if there is no active bookmark, the current named
5392 branch. To specify a different name, use ``--name``.
5393 branch. To specify a different name, use ``--name``.
5393
5394
5394 To see a list of existing shelved changes, use the ``--list``
5395 To see a list of existing shelved changes, use the ``--list``
5395 option. For each shelved change, this will print its name, age,
5396 option. For each shelved change, this will print its name, age,
5396 and description; use ``--patch`` or ``--stat`` for more details.
5397 and description; use ``--patch`` or ``--stat`` for more details.
5397
5398
5398 To delete specific shelved changes, use ``--delete``. To delete
5399 To delete specific shelved changes, use ``--delete``. To delete
5399 all shelved changes, use ``--cleanup``.
5400 all shelved changes, use ``--cleanup``.
5400 '''
5401 '''
5401 opts = pycompat.byteskwargs(opts)
5402 opts = pycompat.byteskwargs(opts)
5402 allowables = [
5403 allowables = [
5403 ('addremove', {'create'}), # 'create' is pseudo action
5404 ('addremove', {'create'}), # 'create' is pseudo action
5404 ('unknown', {'create'}),
5405 ('unknown', {'create'}),
5405 ('cleanup', {'cleanup'}),
5406 ('cleanup', {'cleanup'}),
5406 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
5407 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
5407 ('delete', {'delete'}),
5408 ('delete', {'delete'}),
5408 ('edit', {'create'}),
5409 ('edit', {'create'}),
5409 ('keep', {'create'}),
5410 ('keep', {'create'}),
5410 ('list', {'list'}),
5411 ('list', {'list'}),
5411 ('message', {'create'}),
5412 ('message', {'create'}),
5412 ('name', {'create'}),
5413 ('name', {'create'}),
5413 ('patch', {'patch', 'list'}),
5414 ('patch', {'patch', 'list'}),
5414 ('stat', {'stat', 'list'}),
5415 ('stat', {'stat', 'list'}),
5415 ]
5416 ]
5416 def checkopt(opt):
5417 def checkopt(opt):
5417 if opts.get(opt):
5418 if opts.get(opt):
5418 for i, allowable in allowables:
5419 for i, allowable in allowables:
5419 if opts[i] and opt not in allowable:
5420 if opts[i] and opt not in allowable:
5420 raise error.Abort(_("options '--%s' and '--%s' may not be "
5421 raise error.Abort(_("options '--%s' and '--%s' may not be "
5421 "used together") % (opt, i))
5422 "used together") % (opt, i))
5422 return True
5423 return True
5423 if checkopt('cleanup'):
5424 if checkopt('cleanup'):
5424 if pats:
5425 if pats:
5425 raise error.Abort(_("cannot specify names when using '--cleanup'"))
5426 raise error.Abort(_("cannot specify names when using '--cleanup'"))
5426 return shelvemod.cleanupcmd(ui, repo)
5427 return shelvemod.cleanupcmd(ui, repo)
5427 elif checkopt('delete'):
5428 elif checkopt('delete'):
5428 return shelvemod.deletecmd(ui, repo, pats)
5429 return shelvemod.deletecmd(ui, repo, pats)
5429 elif checkopt('list'):
5430 elif checkopt('list'):
5430 return shelvemod.listcmd(ui, repo, pats, opts)
5431 return shelvemod.listcmd(ui, repo, pats, opts)
5431 elif checkopt('patch') or checkopt('stat'):
5432 elif checkopt('patch') or checkopt('stat'):
5432 return shelvemod.patchcmds(ui, repo, pats, opts)
5433 return shelvemod.patchcmds(ui, repo, pats, opts)
5433 else:
5434 else:
5434 return shelvemod.createcmd(ui, repo, pats, opts)
5435 return shelvemod.createcmd(ui, repo, pats, opts)
5435
5436
5436 _NOTTERSE = 'nothing'
5437 _NOTTERSE = 'nothing'
5437
5438
5438 @command('status|st',
5439 @command('status|st',
5439 [('A', 'all', None, _('show status of all files')),
5440 [('A', 'all', None, _('show status of all files')),
5440 ('m', 'modified', None, _('show only modified files')),
5441 ('m', 'modified', None, _('show only modified files')),
5441 ('a', 'added', None, _('show only added files')),
5442 ('a', 'added', None, _('show only added files')),
5442 ('r', 'removed', None, _('show only removed files')),
5443 ('r', 'removed', None, _('show only removed files')),
5443 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5444 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5444 ('c', 'clean', None, _('show only files without changes')),
5445 ('c', 'clean', None, _('show only files without changes')),
5445 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5446 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5446 ('i', 'ignored', None, _('show only ignored files')),
5447 ('i', 'ignored', None, _('show only ignored files')),
5447 ('n', 'no-status', None, _('hide status prefix')),
5448 ('n', 'no-status', None, _('hide status prefix')),
5448 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5449 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5449 ('C', 'copies', None, _('show source of copied files')),
5450 ('C', 'copies', None, _('show source of copied files')),
5450 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5451 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5451 ('', 'rev', [], _('show difference from revision'), _('REV')),
5452 ('', 'rev', [], _('show difference from revision'), _('REV')),
5452 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5453 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5453 ] + walkopts + subrepoopts + formatteropts,
5454 ] + walkopts + subrepoopts + formatteropts,
5454 _('[OPTION]... [FILE]...'),
5455 _('[OPTION]... [FILE]...'),
5455 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5456 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5456 helpbasic=True, inferrepo=True,
5457 helpbasic=True, inferrepo=True,
5457 intents={INTENT_READONLY})
5458 intents={INTENT_READONLY})
5458 def status(ui, repo, *pats, **opts):
5459 def status(ui, repo, *pats, **opts):
5459 """show changed files in the working directory
5460 """show changed files in the working directory
5460
5461
5461 Show status of files in the repository. If names are given, only
5462 Show status of files in the repository. If names are given, only
5462 files that match are shown. Files that are clean or ignored or
5463 files that match are shown. Files that are clean or ignored or
5463 the source of a copy/move operation, are not listed unless
5464 the source of a copy/move operation, are not listed unless
5464 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5465 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5465 Unless options described with "show only ..." are given, the
5466 Unless options described with "show only ..." are given, the
5466 options -mardu are used.
5467 options -mardu are used.
5467
5468
5468 Option -q/--quiet hides untracked (unknown and ignored) files
5469 Option -q/--quiet hides untracked (unknown and ignored) files
5469 unless explicitly requested with -u/--unknown or -i/--ignored.
5470 unless explicitly requested with -u/--unknown or -i/--ignored.
5470
5471
5471 .. note::
5472 .. note::
5472
5473
5473 :hg:`status` may appear to disagree with diff if permissions have
5474 :hg:`status` may appear to disagree with diff if permissions have
5474 changed or a merge has occurred. The standard diff format does
5475 changed or a merge has occurred. The standard diff format does
5475 not report permission changes and diff only reports changes
5476 not report permission changes and diff only reports changes
5476 relative to one merge parent.
5477 relative to one merge parent.
5477
5478
5478 If one revision is given, it is used as the base revision.
5479 If one revision is given, it is used as the base revision.
5479 If two revisions are given, the differences between them are
5480 If two revisions are given, the differences between them are
5480 shown. The --change option can also be used as a shortcut to list
5481 shown. The --change option can also be used as a shortcut to list
5481 the changed files of a revision from its first parent.
5482 the changed files of a revision from its first parent.
5482
5483
5483 The codes used to show the status of files are::
5484 The codes used to show the status of files are::
5484
5485
5485 M = modified
5486 M = modified
5486 A = added
5487 A = added
5487 R = removed
5488 R = removed
5488 C = clean
5489 C = clean
5489 ! = missing (deleted by non-hg command, but still tracked)
5490 ! = missing (deleted by non-hg command, but still tracked)
5490 ? = not tracked
5491 ? = not tracked
5491 I = ignored
5492 I = ignored
5492 = origin of the previous file (with --copies)
5493 = origin of the previous file (with --copies)
5493
5494
5494 .. container:: verbose
5495 .. container:: verbose
5495
5496
5496 The -t/--terse option abbreviates the output by showing only the directory
5497 The -t/--terse option abbreviates the output by showing only the directory
5497 name if all the files in it share the same status. The option takes an
5498 name if all the files in it share the same status. The option takes an
5498 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5499 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5499 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5500 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5500 for 'ignored' and 'c' for clean.
5501 for 'ignored' and 'c' for clean.
5501
5502
5502 It abbreviates only those statuses which are passed. Note that clean and
5503 It abbreviates only those statuses which are passed. Note that clean and
5503 ignored files are not displayed with '--terse ic' unless the -c/--clean
5504 ignored files are not displayed with '--terse ic' unless the -c/--clean
5504 and -i/--ignored options are also used.
5505 and -i/--ignored options are also used.
5505
5506
5506 The -v/--verbose option shows information when the repository is in an
5507 The -v/--verbose option shows information when the repository is in an
5507 unfinished merge, shelve, rebase state etc. You can have this behavior
5508 unfinished merge, shelve, rebase state etc. You can have this behavior
5508 turned on by default by enabling the ``commands.status.verbose`` option.
5509 turned on by default by enabling the ``commands.status.verbose`` option.
5509
5510
5510 You can skip displaying some of these states by setting
5511 You can skip displaying some of these states by setting
5511 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5512 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5512 'histedit', 'merge', 'rebase', or 'unshelve'.
5513 'histedit', 'merge', 'rebase', or 'unshelve'.
5513
5514
5514 Template:
5515 Template:
5515
5516
5516 The following keywords are supported in addition to the common template
5517 The following keywords are supported in addition to the common template
5517 keywords and functions. See also :hg:`help templates`.
5518 keywords and functions. See also :hg:`help templates`.
5518
5519
5519 :path: String. Repository-absolute path of the file.
5520 :path: String. Repository-absolute path of the file.
5520 :source: String. Repository-absolute path of the file originated from.
5521 :source: String. Repository-absolute path of the file originated from.
5521 Available if ``--copies`` is specified.
5522 Available if ``--copies`` is specified.
5522 :status: String. Character denoting file's status.
5523 :status: String. Character denoting file's status.
5523
5524
5524 Examples:
5525 Examples:
5525
5526
5526 - show changes in the working directory relative to a
5527 - show changes in the working directory relative to a
5527 changeset::
5528 changeset::
5528
5529
5529 hg status --rev 9353
5530 hg status --rev 9353
5530
5531
5531 - show changes in the working directory relative to the
5532 - show changes in the working directory relative to the
5532 current directory (see :hg:`help patterns` for more information)::
5533 current directory (see :hg:`help patterns` for more information)::
5533
5534
5534 hg status re:
5535 hg status re:
5535
5536
5536 - show all changes including copies in an existing changeset::
5537 - show all changes including copies in an existing changeset::
5537
5538
5538 hg status --copies --change 9353
5539 hg status --copies --change 9353
5539
5540
5540 - get a NUL separated list of added files, suitable for xargs::
5541 - get a NUL separated list of added files, suitable for xargs::
5541
5542
5542 hg status -an0
5543 hg status -an0
5543
5544
5544 - show more information about the repository status, abbreviating
5545 - show more information about the repository status, abbreviating
5545 added, removed, modified, deleted, and untracked paths::
5546 added, removed, modified, deleted, and untracked paths::
5546
5547
5547 hg status -v -t mardu
5548 hg status -v -t mardu
5548
5549
5549 Returns 0 on success.
5550 Returns 0 on success.
5550
5551
5551 """
5552 """
5552
5553
5553 opts = pycompat.byteskwargs(opts)
5554 opts = pycompat.byteskwargs(opts)
5554 revs = opts.get('rev')
5555 revs = opts.get('rev')
5555 change = opts.get('change')
5556 change = opts.get('change')
5556 terse = opts.get('terse')
5557 terse = opts.get('terse')
5557 if terse is _NOTTERSE:
5558 if terse is _NOTTERSE:
5558 if revs:
5559 if revs:
5559 terse = ''
5560 terse = ''
5560 else:
5561 else:
5561 terse = ui.config('commands', 'status.terse')
5562 terse = ui.config('commands', 'status.terse')
5562
5563
5563 if revs and change:
5564 if revs and change:
5564 msg = _('cannot specify --rev and --change at the same time')
5565 msg = _('cannot specify --rev and --change at the same time')
5565 raise error.Abort(msg)
5566 raise error.Abort(msg)
5566 elif revs and terse:
5567 elif revs and terse:
5567 msg = _('cannot use --terse with --rev')
5568 msg = _('cannot use --terse with --rev')
5568 raise error.Abort(msg)
5569 raise error.Abort(msg)
5569 elif change:
5570 elif change:
5570 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5571 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5571 ctx2 = scmutil.revsingle(repo, change, None)
5572 ctx2 = scmutil.revsingle(repo, change, None)
5572 ctx1 = ctx2.p1()
5573 ctx1 = ctx2.p1()
5573 else:
5574 else:
5574 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5575 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5575 ctx1, ctx2 = scmutil.revpair(repo, revs)
5576 ctx1, ctx2 = scmutil.revpair(repo, revs)
5576
5577
5577 forcerelativevalue = None
5578 forcerelativevalue = None
5578 if ui.hasconfig('commands', 'status.relative'):
5579 if ui.hasconfig('commands', 'status.relative'):
5579 forcerelativevalue = ui.configbool('commands', 'status.relative')
5580 forcerelativevalue = ui.configbool('commands', 'status.relative')
5580 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5581 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5581 forcerelativevalue=forcerelativevalue)
5582 forcerelativevalue=forcerelativevalue)
5582
5583
5583 if opts.get('print0'):
5584 if opts.get('print0'):
5584 end = '\0'
5585 end = '\0'
5585 else:
5586 else:
5586 end = '\n'
5587 end = '\n'
5587 copy = {}
5588 copy = {}
5588 states = 'modified added removed deleted unknown ignored clean'.split()
5589 states = 'modified added removed deleted unknown ignored clean'.split()
5589 show = [k for k in states if opts.get(k)]
5590 show = [k for k in states if opts.get(k)]
5590 if opts.get('all'):
5591 if opts.get('all'):
5591 show += ui.quiet and (states[:4] + ['clean']) or states
5592 show += ui.quiet and (states[:4] + ['clean']) or states
5592
5593
5593 if not show:
5594 if not show:
5594 if ui.quiet:
5595 if ui.quiet:
5595 show = states[:4]
5596 show = states[:4]
5596 else:
5597 else:
5597 show = states[:5]
5598 show = states[:5]
5598
5599
5599 m = scmutil.match(ctx2, pats, opts)
5600 m = scmutil.match(ctx2, pats, opts)
5600 if terse:
5601 if terse:
5601 # we need to compute clean and unknown to terse
5602 # we need to compute clean and unknown to terse
5602 stat = repo.status(ctx1.node(), ctx2.node(), m,
5603 stat = repo.status(ctx1.node(), ctx2.node(), m,
5603 'ignored' in show or 'i' in terse,
5604 'ignored' in show or 'i' in terse,
5604 clean=True, unknown=True,
5605 clean=True, unknown=True,
5605 listsubrepos=opts.get('subrepos'))
5606 listsubrepos=opts.get('subrepos'))
5606
5607
5607 stat = cmdutil.tersedir(stat, terse)
5608 stat = cmdutil.tersedir(stat, terse)
5608 else:
5609 else:
5609 stat = repo.status(ctx1.node(), ctx2.node(), m,
5610 stat = repo.status(ctx1.node(), ctx2.node(), m,
5610 'ignored' in show, 'clean' in show,
5611 'ignored' in show, 'clean' in show,
5611 'unknown' in show, opts.get('subrepos'))
5612 'unknown' in show, opts.get('subrepos'))
5612
5613
5613 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5614 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5614
5615
5615 if (opts.get('all') or opts.get('copies')
5616 if (opts.get('all') or opts.get('copies')
5616 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5617 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5617 copy = copies.pathcopies(ctx1, ctx2, m)
5618 copy = copies.pathcopies(ctx1, ctx2, m)
5618
5619
5619 ui.pager('status')
5620 ui.pager('status')
5620 fm = ui.formatter('status', opts)
5621 fm = ui.formatter('status', opts)
5621 fmt = '%s' + end
5622 fmt = '%s' + end
5622 showchar = not opts.get('no_status')
5623 showchar = not opts.get('no_status')
5623
5624
5624 for state, char, files in changestates:
5625 for state, char, files in changestates:
5625 if state in show:
5626 if state in show:
5626 label = 'status.' + state
5627 label = 'status.' + state
5627 for f in files:
5628 for f in files:
5628 fm.startitem()
5629 fm.startitem()
5629 fm.context(ctx=ctx2)
5630 fm.context(ctx=ctx2)
5630 fm.data(path=f)
5631 fm.data(path=f)
5631 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5632 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5632 fm.plain(fmt % uipathfn(f), label=label)
5633 fm.plain(fmt % uipathfn(f), label=label)
5633 if f in copy:
5634 if f in copy:
5634 fm.data(source=copy[f])
5635 fm.data(source=copy[f])
5635 fm.plain((' %s' + end) % uipathfn(copy[f]),
5636 fm.plain((' %s' + end) % uipathfn(copy[f]),
5636 label='status.copied')
5637 label='status.copied')
5637
5638
5638 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5639 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5639 and not ui.plain()):
5640 and not ui.plain()):
5640 cmdutil.morestatus(repo, fm)
5641 cmdutil.morestatus(repo, fm)
5641 fm.end()
5642 fm.end()
5642
5643
5643 @command('summary|sum',
5644 @command('summary|sum',
5644 [('', 'remote', None, _('check for push and pull'))],
5645 [('', 'remote', None, _('check for push and pull'))],
5645 '[--remote]',
5646 '[--remote]',
5646 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5647 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5647 helpbasic=True,
5648 helpbasic=True,
5648 intents={INTENT_READONLY})
5649 intents={INTENT_READONLY})
5649 def summary(ui, repo, **opts):
5650 def summary(ui, repo, **opts):
5650 """summarize working directory state
5651 """summarize working directory state
5651
5652
5652 This generates a brief summary of the working directory state,
5653 This generates a brief summary of the working directory state,
5653 including parents, branch, commit status, phase and available updates.
5654 including parents, branch, commit status, phase and available updates.
5654
5655
5655 With the --remote option, this will check the default paths for
5656 With the --remote option, this will check the default paths for
5656 incoming and outgoing changes. This can be time-consuming.
5657 incoming and outgoing changes. This can be time-consuming.
5657
5658
5658 Returns 0 on success.
5659 Returns 0 on success.
5659 """
5660 """
5660
5661
5661 opts = pycompat.byteskwargs(opts)
5662 opts = pycompat.byteskwargs(opts)
5662 ui.pager('summary')
5663 ui.pager('summary')
5663 ctx = repo[None]
5664 ctx = repo[None]
5664 parents = ctx.parents()
5665 parents = ctx.parents()
5665 pnode = parents[0].node()
5666 pnode = parents[0].node()
5666 marks = []
5667 marks = []
5667
5668
5668 try:
5669 try:
5669 ms = mergemod.mergestate.read(repo)
5670 ms = mergemod.mergestate.read(repo)
5670 except error.UnsupportedMergeRecords as e:
5671 except error.UnsupportedMergeRecords as e:
5671 s = ' '.join(e.recordtypes)
5672 s = ' '.join(e.recordtypes)
5672 ui.warn(
5673 ui.warn(
5673 _('warning: merge state has unsupported record types: %s\n') % s)
5674 _('warning: merge state has unsupported record types: %s\n') % s)
5674 unresolved = []
5675 unresolved = []
5675 else:
5676 else:
5676 unresolved = list(ms.unresolved())
5677 unresolved = list(ms.unresolved())
5677
5678
5678 for p in parents:
5679 for p in parents:
5679 # label with log.changeset (instead of log.parent) since this
5680 # label with log.changeset (instead of log.parent) since this
5680 # shows a working directory parent *changeset*:
5681 # shows a working directory parent *changeset*:
5681 # i18n: column positioning for "hg summary"
5682 # i18n: column positioning for "hg summary"
5682 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5683 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5683 label=logcmdutil.changesetlabels(p))
5684 label=logcmdutil.changesetlabels(p))
5684 ui.write(' '.join(p.tags()), label='log.tag')
5685 ui.write(' '.join(p.tags()), label='log.tag')
5685 if p.bookmarks():
5686 if p.bookmarks():
5686 marks.extend(p.bookmarks())
5687 marks.extend(p.bookmarks())
5687 if p.rev() == -1:
5688 if p.rev() == -1:
5688 if not len(repo):
5689 if not len(repo):
5689 ui.write(_(' (empty repository)'))
5690 ui.write(_(' (empty repository)'))
5690 else:
5691 else:
5691 ui.write(_(' (no revision checked out)'))
5692 ui.write(_(' (no revision checked out)'))
5692 if p.obsolete():
5693 if p.obsolete():
5693 ui.write(_(' (obsolete)'))
5694 ui.write(_(' (obsolete)'))
5694 if p.isunstable():
5695 if p.isunstable():
5695 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5696 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5696 for instability in p.instabilities())
5697 for instability in p.instabilities())
5697 ui.write(' ('
5698 ui.write(' ('
5698 + ', '.join(instabilities)
5699 + ', '.join(instabilities)
5699 + ')')
5700 + ')')
5700 ui.write('\n')
5701 ui.write('\n')
5701 if p.description():
5702 if p.description():
5702 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5703 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5703 label='log.summary')
5704 label='log.summary')
5704
5705
5705 branch = ctx.branch()
5706 branch = ctx.branch()
5706 bheads = repo.branchheads(branch)
5707 bheads = repo.branchheads(branch)
5707 # i18n: column positioning for "hg summary"
5708 # i18n: column positioning for "hg summary"
5708 m = _('branch: %s\n') % branch
5709 m = _('branch: %s\n') % branch
5709 if branch != 'default':
5710 if branch != 'default':
5710 ui.write(m, label='log.branch')
5711 ui.write(m, label='log.branch')
5711 else:
5712 else:
5712 ui.status(m, label='log.branch')
5713 ui.status(m, label='log.branch')
5713
5714
5714 if marks:
5715 if marks:
5715 active = repo._activebookmark
5716 active = repo._activebookmark
5716 # i18n: column positioning for "hg summary"
5717 # i18n: column positioning for "hg summary"
5717 ui.write(_('bookmarks:'), label='log.bookmark')
5718 ui.write(_('bookmarks:'), label='log.bookmark')
5718 if active is not None:
5719 if active is not None:
5719 if active in marks:
5720 if active in marks:
5720 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5721 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5721 marks.remove(active)
5722 marks.remove(active)
5722 else:
5723 else:
5723 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5724 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5724 for m in marks:
5725 for m in marks:
5725 ui.write(' ' + m, label='log.bookmark')
5726 ui.write(' ' + m, label='log.bookmark')
5726 ui.write('\n', label='log.bookmark')
5727 ui.write('\n', label='log.bookmark')
5727
5728
5728 status = repo.status(unknown=True)
5729 status = repo.status(unknown=True)
5729
5730
5730 c = repo.dirstate.copies()
5731 c = repo.dirstate.copies()
5731 copied, renamed = [], []
5732 copied, renamed = [], []
5732 for d, s in c.iteritems():
5733 for d, s in c.iteritems():
5733 if s in status.removed:
5734 if s in status.removed:
5734 status.removed.remove(s)
5735 status.removed.remove(s)
5735 renamed.append(d)
5736 renamed.append(d)
5736 else:
5737 else:
5737 copied.append(d)
5738 copied.append(d)
5738 if d in status.added:
5739 if d in status.added:
5739 status.added.remove(d)
5740 status.added.remove(d)
5740
5741
5741 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5742 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5742
5743
5743 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5744 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5744 (ui.label(_('%d added'), 'status.added'), status.added),
5745 (ui.label(_('%d added'), 'status.added'), status.added),
5745 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5746 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5746 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5747 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5747 (ui.label(_('%d copied'), 'status.copied'), copied),
5748 (ui.label(_('%d copied'), 'status.copied'), copied),
5748 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5749 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5749 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5750 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5750 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5751 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5751 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5752 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5752 t = []
5753 t = []
5753 for l, s in labels:
5754 for l, s in labels:
5754 if s:
5755 if s:
5755 t.append(l % len(s))
5756 t.append(l % len(s))
5756
5757
5757 t = ', '.join(t)
5758 t = ', '.join(t)
5758 cleanworkdir = False
5759 cleanworkdir = False
5759
5760
5760 if repo.vfs.exists('graftstate'):
5761 if repo.vfs.exists('graftstate'):
5761 t += _(' (graft in progress)')
5762 t += _(' (graft in progress)')
5762 if repo.vfs.exists('updatestate'):
5763 if repo.vfs.exists('updatestate'):
5763 t += _(' (interrupted update)')
5764 t += _(' (interrupted update)')
5764 elif len(parents) > 1:
5765 elif len(parents) > 1:
5765 t += _(' (merge)')
5766 t += _(' (merge)')
5766 elif branch != parents[0].branch():
5767 elif branch != parents[0].branch():
5767 t += _(' (new branch)')
5768 t += _(' (new branch)')
5768 elif (parents[0].closesbranch() and
5769 elif (parents[0].closesbranch() and
5769 pnode in repo.branchheads(branch, closed=True)):
5770 pnode in repo.branchheads(branch, closed=True)):
5770 t += _(' (head closed)')
5771 t += _(' (head closed)')
5771 elif not (status.modified or status.added or status.removed or renamed or
5772 elif not (status.modified or status.added or status.removed or renamed or
5772 copied or subs):
5773 copied or subs):
5773 t += _(' (clean)')
5774 t += _(' (clean)')
5774 cleanworkdir = True
5775 cleanworkdir = True
5775 elif pnode not in bheads:
5776 elif pnode not in bheads:
5776 t += _(' (new branch head)')
5777 t += _(' (new branch head)')
5777
5778
5778 if parents:
5779 if parents:
5779 pendingphase = max(p.phase() for p in parents)
5780 pendingphase = max(p.phase() for p in parents)
5780 else:
5781 else:
5781 pendingphase = phases.public
5782 pendingphase = phases.public
5782
5783
5783 if pendingphase > phases.newcommitphase(ui):
5784 if pendingphase > phases.newcommitphase(ui):
5784 t += ' (%s)' % phases.phasenames[pendingphase]
5785 t += ' (%s)' % phases.phasenames[pendingphase]
5785
5786
5786 if cleanworkdir:
5787 if cleanworkdir:
5787 # i18n: column positioning for "hg summary"
5788 # i18n: column positioning for "hg summary"
5788 ui.status(_('commit: %s\n') % t.strip())
5789 ui.status(_('commit: %s\n') % t.strip())
5789 else:
5790 else:
5790 # i18n: column positioning for "hg summary"
5791 # i18n: column positioning for "hg summary"
5791 ui.write(_('commit: %s\n') % t.strip())
5792 ui.write(_('commit: %s\n') % t.strip())
5792
5793
5793 # all ancestors of branch heads - all ancestors of parent = new csets
5794 # all ancestors of branch heads - all ancestors of parent = new csets
5794 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5795 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5795 bheads))
5796 bheads))
5796
5797
5797 if new == 0:
5798 if new == 0:
5798 # i18n: column positioning for "hg summary"
5799 # i18n: column positioning for "hg summary"
5799 ui.status(_('update: (current)\n'))
5800 ui.status(_('update: (current)\n'))
5800 elif pnode not in bheads:
5801 elif pnode not in bheads:
5801 # i18n: column positioning for "hg summary"
5802 # i18n: column positioning for "hg summary"
5802 ui.write(_('update: %d new changesets (update)\n') % new)
5803 ui.write(_('update: %d new changesets (update)\n') % new)
5803 else:
5804 else:
5804 # i18n: column positioning for "hg summary"
5805 # i18n: column positioning for "hg summary"
5805 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5806 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5806 (new, len(bheads)))
5807 (new, len(bheads)))
5807
5808
5808 t = []
5809 t = []
5809 draft = len(repo.revs('draft()'))
5810 draft = len(repo.revs('draft()'))
5810 if draft:
5811 if draft:
5811 t.append(_('%d draft') % draft)
5812 t.append(_('%d draft') % draft)
5812 secret = len(repo.revs('secret()'))
5813 secret = len(repo.revs('secret()'))
5813 if secret:
5814 if secret:
5814 t.append(_('%d secret') % secret)
5815 t.append(_('%d secret') % secret)
5815
5816
5816 if draft or secret:
5817 if draft or secret:
5817 ui.status(_('phases: %s\n') % ', '.join(t))
5818 ui.status(_('phases: %s\n') % ', '.join(t))
5818
5819
5819 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5820 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5820 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5821 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5821 numtrouble = len(repo.revs(trouble + "()"))
5822 numtrouble = len(repo.revs(trouble + "()"))
5822 # We write all the possibilities to ease translation
5823 # We write all the possibilities to ease translation
5823 troublemsg = {
5824 troublemsg = {
5824 "orphan": _("orphan: %d changesets"),
5825 "orphan": _("orphan: %d changesets"),
5825 "contentdivergent": _("content-divergent: %d changesets"),
5826 "contentdivergent": _("content-divergent: %d changesets"),
5826 "phasedivergent": _("phase-divergent: %d changesets"),
5827 "phasedivergent": _("phase-divergent: %d changesets"),
5827 }
5828 }
5828 if numtrouble > 0:
5829 if numtrouble > 0:
5829 ui.status(troublemsg[trouble] % numtrouble + "\n")
5830 ui.status(troublemsg[trouble] % numtrouble + "\n")
5830
5831
5831 cmdutil.summaryhooks(ui, repo)
5832 cmdutil.summaryhooks(ui, repo)
5832
5833
5833 if opts.get('remote'):
5834 if opts.get('remote'):
5834 needsincoming, needsoutgoing = True, True
5835 needsincoming, needsoutgoing = True, True
5835 else:
5836 else:
5836 needsincoming, needsoutgoing = False, False
5837 needsincoming, needsoutgoing = False, False
5837 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5838 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5838 if i:
5839 if i:
5839 needsincoming = True
5840 needsincoming = True
5840 if o:
5841 if o:
5841 needsoutgoing = True
5842 needsoutgoing = True
5842 if not needsincoming and not needsoutgoing:
5843 if not needsincoming and not needsoutgoing:
5843 return
5844 return
5844
5845
5845 def getincoming():
5846 def getincoming():
5846 source, branches = hg.parseurl(ui.expandpath('default'))
5847 source, branches = hg.parseurl(ui.expandpath('default'))
5847 sbranch = branches[0]
5848 sbranch = branches[0]
5848 try:
5849 try:
5849 other = hg.peer(repo, {}, source)
5850 other = hg.peer(repo, {}, source)
5850 except error.RepoError:
5851 except error.RepoError:
5851 if opts.get('remote'):
5852 if opts.get('remote'):
5852 raise
5853 raise
5853 return source, sbranch, None, None, None
5854 return source, sbranch, None, None, None
5854 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5855 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5855 if revs:
5856 if revs:
5856 revs = [other.lookup(rev) for rev in revs]
5857 revs = [other.lookup(rev) for rev in revs]
5857 ui.debug('comparing with %s\n' % util.hidepassword(source))
5858 ui.debug('comparing with %s\n' % util.hidepassword(source))
5858 repo.ui.pushbuffer()
5859 repo.ui.pushbuffer()
5859 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5860 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5860 repo.ui.popbuffer()
5861 repo.ui.popbuffer()
5861 return source, sbranch, other, commoninc, commoninc[1]
5862 return source, sbranch, other, commoninc, commoninc[1]
5862
5863
5863 if needsincoming:
5864 if needsincoming:
5864 source, sbranch, sother, commoninc, incoming = getincoming()
5865 source, sbranch, sother, commoninc, incoming = getincoming()
5865 else:
5866 else:
5866 source = sbranch = sother = commoninc = incoming = None
5867 source = sbranch = sother = commoninc = incoming = None
5867
5868
5868 def getoutgoing():
5869 def getoutgoing():
5869 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5870 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5870 dbranch = branches[0]
5871 dbranch = branches[0]
5871 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5872 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5872 if source != dest:
5873 if source != dest:
5873 try:
5874 try:
5874 dother = hg.peer(repo, {}, dest)
5875 dother = hg.peer(repo, {}, dest)
5875 except error.RepoError:
5876 except error.RepoError:
5876 if opts.get('remote'):
5877 if opts.get('remote'):
5877 raise
5878 raise
5878 return dest, dbranch, None, None
5879 return dest, dbranch, None, None
5879 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5880 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5880 elif sother is None:
5881 elif sother is None:
5881 # there is no explicit destination peer, but source one is invalid
5882 # there is no explicit destination peer, but source one is invalid
5882 return dest, dbranch, None, None
5883 return dest, dbranch, None, None
5883 else:
5884 else:
5884 dother = sother
5885 dother = sother
5885 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5886 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5886 common = None
5887 common = None
5887 else:
5888 else:
5888 common = commoninc
5889 common = commoninc
5889 if revs:
5890 if revs:
5890 revs = [repo.lookup(rev) for rev in revs]
5891 revs = [repo.lookup(rev) for rev in revs]
5891 repo.ui.pushbuffer()
5892 repo.ui.pushbuffer()
5892 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5893 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5893 commoninc=common)
5894 commoninc=common)
5894 repo.ui.popbuffer()
5895 repo.ui.popbuffer()
5895 return dest, dbranch, dother, outgoing
5896 return dest, dbranch, dother, outgoing
5896
5897
5897 if needsoutgoing:
5898 if needsoutgoing:
5898 dest, dbranch, dother, outgoing = getoutgoing()
5899 dest, dbranch, dother, outgoing = getoutgoing()
5899 else:
5900 else:
5900 dest = dbranch = dother = outgoing = None
5901 dest = dbranch = dother = outgoing = None
5901
5902
5902 if opts.get('remote'):
5903 if opts.get('remote'):
5903 t = []
5904 t = []
5904 if incoming:
5905 if incoming:
5905 t.append(_('1 or more incoming'))
5906 t.append(_('1 or more incoming'))
5906 o = outgoing.missing
5907 o = outgoing.missing
5907 if o:
5908 if o:
5908 t.append(_('%d outgoing') % len(o))
5909 t.append(_('%d outgoing') % len(o))
5909 other = dother or sother
5910 other = dother or sother
5910 if 'bookmarks' in other.listkeys('namespaces'):
5911 if 'bookmarks' in other.listkeys('namespaces'):
5911 counts = bookmarks.summary(repo, other)
5912 counts = bookmarks.summary(repo, other)
5912 if counts[0] > 0:
5913 if counts[0] > 0:
5913 t.append(_('%d incoming bookmarks') % counts[0])
5914 t.append(_('%d incoming bookmarks') % counts[0])
5914 if counts[1] > 0:
5915 if counts[1] > 0:
5915 t.append(_('%d outgoing bookmarks') % counts[1])
5916 t.append(_('%d outgoing bookmarks') % counts[1])
5916
5917
5917 if t:
5918 if t:
5918 # i18n: column positioning for "hg summary"
5919 # i18n: column positioning for "hg summary"
5919 ui.write(_('remote: %s\n') % (', '.join(t)))
5920 ui.write(_('remote: %s\n') % (', '.join(t)))
5920 else:
5921 else:
5921 # i18n: column positioning for "hg summary"
5922 # i18n: column positioning for "hg summary"
5922 ui.status(_('remote: (synced)\n'))
5923 ui.status(_('remote: (synced)\n'))
5923
5924
5924 cmdutil.summaryremotehooks(ui, repo, opts,
5925 cmdutil.summaryremotehooks(ui, repo, opts,
5925 ((source, sbranch, sother, commoninc),
5926 ((source, sbranch, sother, commoninc),
5926 (dest, dbranch, dother, outgoing)))
5927 (dest, dbranch, dother, outgoing)))
5927
5928
5928 @command('tag',
5929 @command('tag',
5929 [('f', 'force', None, _('force tag')),
5930 [('f', 'force', None, _('force tag')),
5930 ('l', 'local', None, _('make the tag local')),
5931 ('l', 'local', None, _('make the tag local')),
5931 ('r', 'rev', '', _('revision to tag'), _('REV')),
5932 ('r', 'rev', '', _('revision to tag'), _('REV')),
5932 ('', 'remove', None, _('remove a tag')),
5933 ('', 'remove', None, _('remove a tag')),
5933 # -l/--local is already there, commitopts cannot be used
5934 # -l/--local is already there, commitopts cannot be used
5934 ('e', 'edit', None, _('invoke editor on commit messages')),
5935 ('e', 'edit', None, _('invoke editor on commit messages')),
5935 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5936 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5936 ] + commitopts2,
5937 ] + commitopts2,
5937 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5938 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5938 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5939 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5939 def tag(ui, repo, name1, *names, **opts):
5940 def tag(ui, repo, name1, *names, **opts):
5940 """add one or more tags for the current or given revision
5941 """add one or more tags for the current or given revision
5941
5942
5942 Name a particular revision using <name>.
5943 Name a particular revision using <name>.
5943
5944
5944 Tags are used to name particular revisions of the repository and are
5945 Tags are used to name particular revisions of the repository and are
5945 very useful to compare different revisions, to go back to significant
5946 very useful to compare different revisions, to go back to significant
5946 earlier versions or to mark branch points as releases, etc. Changing
5947 earlier versions or to mark branch points as releases, etc. Changing
5947 an existing tag is normally disallowed; use -f/--force to override.
5948 an existing tag is normally disallowed; use -f/--force to override.
5948
5949
5949 If no revision is given, the parent of the working directory is
5950 If no revision is given, the parent of the working directory is
5950 used.
5951 used.
5951
5952
5952 To facilitate version control, distribution, and merging of tags,
5953 To facilitate version control, distribution, and merging of tags,
5953 they are stored as a file named ".hgtags" which is managed similarly
5954 they are stored as a file named ".hgtags" which is managed similarly
5954 to other project files and can be hand-edited if necessary. This
5955 to other project files and can be hand-edited if necessary. This
5955 also means that tagging creates a new commit. The file
5956 also means that tagging creates a new commit. The file
5956 ".hg/localtags" is used for local tags (not shared among
5957 ".hg/localtags" is used for local tags (not shared among
5957 repositories).
5958 repositories).
5958
5959
5959 Tag commits are usually made at the head of a branch. If the parent
5960 Tag commits are usually made at the head of a branch. If the parent
5960 of the working directory is not a branch head, :hg:`tag` aborts; use
5961 of the working directory is not a branch head, :hg:`tag` aborts; use
5961 -f/--force to force the tag commit to be based on a non-head
5962 -f/--force to force the tag commit to be based on a non-head
5962 changeset.
5963 changeset.
5963
5964
5964 See :hg:`help dates` for a list of formats valid for -d/--date.
5965 See :hg:`help dates` for a list of formats valid for -d/--date.
5965
5966
5966 Since tag names have priority over branch names during revision
5967 Since tag names have priority over branch names during revision
5967 lookup, using an existing branch name as a tag name is discouraged.
5968 lookup, using an existing branch name as a tag name is discouraged.
5968
5969
5969 Returns 0 on success.
5970 Returns 0 on success.
5970 """
5971 """
5971 opts = pycompat.byteskwargs(opts)
5972 opts = pycompat.byteskwargs(opts)
5972 with repo.wlock(), repo.lock():
5973 with repo.wlock(), repo.lock():
5973 rev_ = "."
5974 rev_ = "."
5974 names = [t.strip() for t in (name1,) + names]
5975 names = [t.strip() for t in (name1,) + names]
5975 if len(names) != len(set(names)):
5976 if len(names) != len(set(names)):
5976 raise error.Abort(_('tag names must be unique'))
5977 raise error.Abort(_('tag names must be unique'))
5977 for n in names:
5978 for n in names:
5978 scmutil.checknewlabel(repo, n, 'tag')
5979 scmutil.checknewlabel(repo, n, 'tag')
5979 if not n:
5980 if not n:
5980 raise error.Abort(_('tag names cannot consist entirely of '
5981 raise error.Abort(_('tag names cannot consist entirely of '
5981 'whitespace'))
5982 'whitespace'))
5982 if opts.get('rev') and opts.get('remove'):
5983 if opts.get('rev') and opts.get('remove'):
5983 raise error.Abort(_("--rev and --remove are incompatible"))
5984 raise error.Abort(_("--rev and --remove are incompatible"))
5984 if opts.get('rev'):
5985 if opts.get('rev'):
5985 rev_ = opts['rev']
5986 rev_ = opts['rev']
5986 message = opts.get('message')
5987 message = opts.get('message')
5987 if opts.get('remove'):
5988 if opts.get('remove'):
5988 if opts.get('local'):
5989 if opts.get('local'):
5989 expectedtype = 'local'
5990 expectedtype = 'local'
5990 else:
5991 else:
5991 expectedtype = 'global'
5992 expectedtype = 'global'
5992
5993
5993 for n in names:
5994 for n in names:
5994 if repo.tagtype(n) == 'global':
5995 if repo.tagtype(n) == 'global':
5995 alltags = tagsmod.findglobaltags(ui, repo)
5996 alltags = tagsmod.findglobaltags(ui, repo)
5996 if alltags[n][0] == nullid:
5997 if alltags[n][0] == nullid:
5997 raise error.Abort(_("tag '%s' is already removed") % n)
5998 raise error.Abort(_("tag '%s' is already removed") % n)
5998 if not repo.tagtype(n):
5999 if not repo.tagtype(n):
5999 raise error.Abort(_("tag '%s' does not exist") % n)
6000 raise error.Abort(_("tag '%s' does not exist") % n)
6000 if repo.tagtype(n) != expectedtype:
6001 if repo.tagtype(n) != expectedtype:
6001 if expectedtype == 'global':
6002 if expectedtype == 'global':
6002 raise error.Abort(_("tag '%s' is not a global tag") % n)
6003 raise error.Abort(_("tag '%s' is not a global tag") % n)
6003 else:
6004 else:
6004 raise error.Abort(_("tag '%s' is not a local tag") % n)
6005 raise error.Abort(_("tag '%s' is not a local tag") % n)
6005 rev_ = 'null'
6006 rev_ = 'null'
6006 if not message:
6007 if not message:
6007 # we don't translate commit messages
6008 # we don't translate commit messages
6008 message = 'Removed tag %s' % ', '.join(names)
6009 message = 'Removed tag %s' % ', '.join(names)
6009 elif not opts.get('force'):
6010 elif not opts.get('force'):
6010 for n in names:
6011 for n in names:
6011 if n in repo.tags():
6012 if n in repo.tags():
6012 raise error.Abort(_("tag '%s' already exists "
6013 raise error.Abort(_("tag '%s' already exists "
6013 "(use -f to force)") % n)
6014 "(use -f to force)") % n)
6014 if not opts.get('local'):
6015 if not opts.get('local'):
6015 p1, p2 = repo.dirstate.parents()
6016 p1, p2 = repo.dirstate.parents()
6016 if p2 != nullid:
6017 if p2 != nullid:
6017 raise error.Abort(_('uncommitted merge'))
6018 raise error.Abort(_('uncommitted merge'))
6018 bheads = repo.branchheads()
6019 bheads = repo.branchheads()
6019 if not opts.get('force') and bheads and p1 not in bheads:
6020 if not opts.get('force') and bheads and p1 not in bheads:
6020 raise error.Abort(_('working directory is not at a branch head '
6021 raise error.Abort(_('working directory is not at a branch head '
6021 '(use -f to force)'))
6022 '(use -f to force)'))
6022 node = scmutil.revsingle(repo, rev_).node()
6023 node = scmutil.revsingle(repo, rev_).node()
6023
6024
6024 if not message:
6025 if not message:
6025 # we don't translate commit messages
6026 # we don't translate commit messages
6026 message = ('Added tag %s for changeset %s' %
6027 message = ('Added tag %s for changeset %s' %
6027 (', '.join(names), short(node)))
6028 (', '.join(names), short(node)))
6028
6029
6029 date = opts.get('date')
6030 date = opts.get('date')
6030 if date:
6031 if date:
6031 date = dateutil.parsedate(date)
6032 date = dateutil.parsedate(date)
6032
6033
6033 if opts.get('remove'):
6034 if opts.get('remove'):
6034 editform = 'tag.remove'
6035 editform = 'tag.remove'
6035 else:
6036 else:
6036 editform = 'tag.add'
6037 editform = 'tag.add'
6037 editor = cmdutil.getcommiteditor(editform=editform,
6038 editor = cmdutil.getcommiteditor(editform=editform,
6038 **pycompat.strkwargs(opts))
6039 **pycompat.strkwargs(opts))
6039
6040
6040 # don't allow tagging the null rev
6041 # don't allow tagging the null rev
6041 if (not opts.get('remove') and
6042 if (not opts.get('remove') and
6042 scmutil.revsingle(repo, rev_).rev() == nullrev):
6043 scmutil.revsingle(repo, rev_).rev() == nullrev):
6043 raise error.Abort(_("cannot tag null revision"))
6044 raise error.Abort(_("cannot tag null revision"))
6044
6045
6045 tagsmod.tag(repo, names, node, message, opts.get('local'),
6046 tagsmod.tag(repo, names, node, message, opts.get('local'),
6046 opts.get('user'), date, editor=editor)
6047 opts.get('user'), date, editor=editor)
6047
6048
6048 @command(
6049 @command(
6049 'tags', formatteropts, '',
6050 'tags', formatteropts, '',
6050 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
6051 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
6051 intents={INTENT_READONLY})
6052 intents={INTENT_READONLY})
6052 def tags(ui, repo, **opts):
6053 def tags(ui, repo, **opts):
6053 """list repository tags
6054 """list repository tags
6054
6055
6055 This lists both regular and local tags. When the -v/--verbose
6056 This lists both regular and local tags. When the -v/--verbose
6056 switch is used, a third column "local" is printed for local tags.
6057 switch is used, a third column "local" is printed for local tags.
6057 When the -q/--quiet switch is used, only the tag name is printed.
6058 When the -q/--quiet switch is used, only the tag name is printed.
6058
6059
6059 .. container:: verbose
6060 .. container:: verbose
6060
6061
6061 Template:
6062 Template:
6062
6063
6063 The following keywords are supported in addition to the common template
6064 The following keywords are supported in addition to the common template
6064 keywords and functions such as ``{tag}``. See also
6065 keywords and functions such as ``{tag}``. See also
6065 :hg:`help templates`.
6066 :hg:`help templates`.
6066
6067
6067 :type: String. ``local`` for local tags.
6068 :type: String. ``local`` for local tags.
6068
6069
6069 Returns 0 on success.
6070 Returns 0 on success.
6070 """
6071 """
6071
6072
6072 opts = pycompat.byteskwargs(opts)
6073 opts = pycompat.byteskwargs(opts)
6073 ui.pager('tags')
6074 ui.pager('tags')
6074 fm = ui.formatter('tags', opts)
6075 fm = ui.formatter('tags', opts)
6075 hexfunc = fm.hexfunc
6076 hexfunc = fm.hexfunc
6076
6077
6077 for t, n in reversed(repo.tagslist()):
6078 for t, n in reversed(repo.tagslist()):
6078 hn = hexfunc(n)
6079 hn = hexfunc(n)
6079 label = 'tags.normal'
6080 label = 'tags.normal'
6080 tagtype = ''
6081 tagtype = ''
6081 if repo.tagtype(t) == 'local':
6082 if repo.tagtype(t) == 'local':
6082 label = 'tags.local'
6083 label = 'tags.local'
6083 tagtype = 'local'
6084 tagtype = 'local'
6084
6085
6085 fm.startitem()
6086 fm.startitem()
6086 fm.context(repo=repo)
6087 fm.context(repo=repo)
6087 fm.write('tag', '%s', t, label=label)
6088 fm.write('tag', '%s', t, label=label)
6088 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6089 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6089 fm.condwrite(not ui.quiet, 'rev node', fmt,
6090 fm.condwrite(not ui.quiet, 'rev node', fmt,
6090 repo.changelog.rev(n), hn, label=label)
6091 repo.changelog.rev(n), hn, label=label)
6091 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6092 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6092 tagtype, label=label)
6093 tagtype, label=label)
6093 fm.plain('\n')
6094 fm.plain('\n')
6094 fm.end()
6095 fm.end()
6095
6096
6096 @command('tip',
6097 @command('tip',
6097 [('p', 'patch', None, _('show patch')),
6098 [('p', 'patch', None, _('show patch')),
6098 ('g', 'git', None, _('use git extended diff format')),
6099 ('g', 'git', None, _('use git extended diff format')),
6099 ] + templateopts,
6100 ] + templateopts,
6100 _('[-p] [-g]'),
6101 _('[-p] [-g]'),
6101 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
6102 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
6102 def tip(ui, repo, **opts):
6103 def tip(ui, repo, **opts):
6103 """show the tip revision (DEPRECATED)
6104 """show the tip revision (DEPRECATED)
6104
6105
6105 The tip revision (usually just called the tip) is the changeset
6106 The tip revision (usually just called the tip) is the changeset
6106 most recently added to the repository (and therefore the most
6107 most recently added to the repository (and therefore the most
6107 recently changed head).
6108 recently changed head).
6108
6109
6109 If you have just made a commit, that commit will be the tip. If
6110 If you have just made a commit, that commit will be the tip. If
6110 you have just pulled changes from another repository, the tip of
6111 you have just pulled changes from another repository, the tip of
6111 that repository becomes the current tip. The "tip" tag is special
6112 that repository becomes the current tip. The "tip" tag is special
6112 and cannot be renamed or assigned to a different changeset.
6113 and cannot be renamed or assigned to a different changeset.
6113
6114
6114 This command is deprecated, please use :hg:`heads` instead.
6115 This command is deprecated, please use :hg:`heads` instead.
6115
6116
6116 Returns 0 on success.
6117 Returns 0 on success.
6117 """
6118 """
6118 opts = pycompat.byteskwargs(opts)
6119 opts = pycompat.byteskwargs(opts)
6119 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6120 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6120 displayer.show(repo['tip'])
6121 displayer.show(repo['tip'])
6121 displayer.close()
6122 displayer.close()
6122
6123
6123 @command('unbundle',
6124 @command('unbundle',
6124 [('u', 'update', None,
6125 [('u', 'update', None,
6125 _('update to new branch head if changesets were unbundled'))],
6126 _('update to new branch head if changesets were unbundled'))],
6126 _('[-u] FILE...'),
6127 _('[-u] FILE...'),
6127 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6128 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6128 def unbundle(ui, repo, fname1, *fnames, **opts):
6129 def unbundle(ui, repo, fname1, *fnames, **opts):
6129 """apply one or more bundle files
6130 """apply one or more bundle files
6130
6131
6131 Apply one or more bundle files generated by :hg:`bundle`.
6132 Apply one or more bundle files generated by :hg:`bundle`.
6132
6133
6133 Returns 0 on success, 1 if an update has unresolved files.
6134 Returns 0 on success, 1 if an update has unresolved files.
6134 """
6135 """
6135 fnames = (fname1,) + fnames
6136 fnames = (fname1,) + fnames
6136
6137
6137 with repo.lock():
6138 with repo.lock():
6138 for fname in fnames:
6139 for fname in fnames:
6139 f = hg.openpath(ui, fname)
6140 f = hg.openpath(ui, fname)
6140 gen = exchange.readbundle(ui, f, fname)
6141 gen = exchange.readbundle(ui, f, fname)
6141 if isinstance(gen, streamclone.streamcloneapplier):
6142 if isinstance(gen, streamclone.streamcloneapplier):
6142 raise error.Abort(
6143 raise error.Abort(
6143 _('packed bundles cannot be applied with '
6144 _('packed bundles cannot be applied with '
6144 '"hg unbundle"'),
6145 '"hg unbundle"'),
6145 hint=_('use "hg debugapplystreamclonebundle"'))
6146 hint=_('use "hg debugapplystreamclonebundle"'))
6146 url = 'bundle:' + fname
6147 url = 'bundle:' + fname
6147 try:
6148 try:
6148 txnname = 'unbundle'
6149 txnname = 'unbundle'
6149 if not isinstance(gen, bundle2.unbundle20):
6150 if not isinstance(gen, bundle2.unbundle20):
6150 txnname = 'unbundle\n%s' % util.hidepassword(url)
6151 txnname = 'unbundle\n%s' % util.hidepassword(url)
6151 with repo.transaction(txnname) as tr:
6152 with repo.transaction(txnname) as tr:
6152 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6153 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6153 url=url)
6154 url=url)
6154 except error.BundleUnknownFeatureError as exc:
6155 except error.BundleUnknownFeatureError as exc:
6155 raise error.Abort(
6156 raise error.Abort(
6156 _('%s: unknown bundle feature, %s') % (fname, exc),
6157 _('%s: unknown bundle feature, %s') % (fname, exc),
6157 hint=_("see https://mercurial-scm.org/"
6158 hint=_("see https://mercurial-scm.org/"
6158 "wiki/BundleFeature for more "
6159 "wiki/BundleFeature for more "
6159 "information"))
6160 "information"))
6160 modheads = bundle2.combinechangegroupresults(op)
6161 modheads = bundle2.combinechangegroupresults(op)
6161
6162
6162 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6163 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6163
6164
6164 @command('unshelve',
6165 @command('unshelve',
6165 [('a', 'abort', None,
6166 [('a', 'abort', None,
6166 _('abort an incomplete unshelve operation')),
6167 _('abort an incomplete unshelve operation')),
6167 ('c', 'continue', None,
6168 ('c', 'continue', None,
6168 _('continue an incomplete unshelve operation')),
6169 _('continue an incomplete unshelve operation')),
6169 ('i', 'interactive', None,
6170 ('i', 'interactive', None,
6170 _('use interactive mode (EXPERIMENTAL)')),
6171 _('use interactive mode (EXPERIMENTAL)')),
6171 ('k', 'keep', None,
6172 ('k', 'keep', None,
6172 _('keep shelve after unshelving')),
6173 _('keep shelve after unshelving')),
6173 ('n', 'name', '',
6174 ('n', 'name', '',
6174 _('restore shelved change with given name'), _('NAME')),
6175 _('restore shelved change with given name'), _('NAME')),
6175 ('t', 'tool', '', _('specify merge tool')),
6176 ('t', 'tool', '', _('specify merge tool')),
6176 ('', 'date', '',
6177 ('', 'date', '',
6177 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
6178 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
6178 _('hg unshelve [OPTION]... [FILE]... [-n SHELVED]'),
6179 _('hg unshelve [OPTION]... [FILE]... [-n SHELVED]'),
6179 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
6180 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
6180 def unshelve(ui, repo, *shelved, **opts):
6181 def unshelve(ui, repo, *shelved, **opts):
6181 """restore a shelved change to the working directory
6182 """restore a shelved change to the working directory
6182
6183
6183 This command accepts an optional name of a shelved change to
6184 This command accepts an optional name of a shelved change to
6184 restore. If none is given, the most recent shelved change is used.
6185 restore. If none is given, the most recent shelved change is used.
6185
6186
6186 If a shelved change is applied successfully, the bundle that
6187 If a shelved change is applied successfully, the bundle that
6187 contains the shelved changes is moved to a backup location
6188 contains the shelved changes is moved to a backup location
6188 (.hg/shelve-backup).
6189 (.hg/shelve-backup).
6189
6190
6190 Since you can restore a shelved change on top of an arbitrary
6191 Since you can restore a shelved change on top of an arbitrary
6191 commit, it is possible that unshelving will result in a conflict
6192 commit, it is possible that unshelving will result in a conflict
6192 between your changes and the commits you are unshelving onto. If
6193 between your changes and the commits you are unshelving onto. If
6193 this occurs, you must resolve the conflict, then use
6194 this occurs, you must resolve the conflict, then use
6194 ``--continue`` to complete the unshelve operation. (The bundle
6195 ``--continue`` to complete the unshelve operation. (The bundle
6195 will not be moved until you successfully complete the unshelve.)
6196 will not be moved until you successfully complete the unshelve.)
6196
6197
6197 (Alternatively, you can use ``--abort`` to abandon an unshelve
6198 (Alternatively, you can use ``--abort`` to abandon an unshelve
6198 that causes a conflict. This reverts the unshelved changes, and
6199 that causes a conflict. This reverts the unshelved changes, and
6199 leaves the bundle in place.)
6200 leaves the bundle in place.)
6200
6201
6201 If bare shelved change (when no files are specified, without interactive,
6202 If bare shelved change (when no files are specified, without interactive,
6202 include and exclude option) was done on newly created branch it would
6203 include and exclude option) was done on newly created branch it would
6203 restore branch information to the working directory.
6204 restore branch information to the working directory.
6204
6205
6205 After a successful unshelve, the shelved changes are stored in a
6206 After a successful unshelve, the shelved changes are stored in a
6206 backup directory. Only the N most recent backups are kept. N
6207 backup directory. Only the N most recent backups are kept. N
6207 defaults to 10 but can be overridden using the ``shelve.maxbackups``
6208 defaults to 10 but can be overridden using the ``shelve.maxbackups``
6208 configuration option.
6209 configuration option.
6209
6210
6210 .. container:: verbose
6211 .. container:: verbose
6211
6212
6212 Timestamp in seconds is used to decide order of backups. More
6213 Timestamp in seconds is used to decide order of backups. More
6213 than ``maxbackups`` backups are kept, if same timestamp
6214 than ``maxbackups`` backups are kept, if same timestamp
6214 prevents from deciding exact order of them, for safety.
6215 prevents from deciding exact order of them, for safety.
6215
6216
6216 Selected changes can be unshelved with ``--interactive`` flag.
6217 Selected changes can be unshelved with ``--interactive`` flag.
6217 The working directory is updated with the selected changes, and
6218 The working directory is updated with the selected changes, and
6218 only the unselected changes remain shelved.
6219 only the unselected changes remain shelved.
6219 Note: The whole shelve is applied to working directory first before
6220 Note: The whole shelve is applied to working directory first before
6220 running interactively. So, this will bring up all the conflicts between
6221 running interactively. So, this will bring up all the conflicts between
6221 working directory and the shelve, irrespective of which changes will be
6222 working directory and the shelve, irrespective of which changes will be
6222 unshelved.
6223 unshelved.
6223 """
6224 """
6224 with repo.wlock():
6225 with repo.wlock():
6225 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
6226 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
6226
6227
6227 statemod.addunfinished(
6228 statemod.addunfinished(
6228 'unshelve', fname='shelvedstate', continueflag=True,
6229 'unshelve', fname='shelvedstate', continueflag=True,
6229 abortfunc=shelvemod.hgabortunshelve,
6230 abortfunc=shelvemod.hgabortunshelve,
6230 continuefunc=shelvemod.hgcontinueunshelve,
6231 continuefunc=shelvemod.hgcontinueunshelve,
6231 cmdmsg=_('unshelve already in progress'),
6232 cmdmsg=_('unshelve already in progress'),
6232 )
6233 )
6233
6234
6234 @command('update|up|checkout|co',
6235 @command('update|up|checkout|co',
6235 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6236 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6236 ('c', 'check', None, _('require clean working directory')),
6237 ('c', 'check', None, _('require clean working directory')),
6237 ('m', 'merge', None, _('merge uncommitted changes')),
6238 ('m', 'merge', None, _('merge uncommitted changes')),
6238 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6239 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6239 ('r', 'rev', '', _('revision'), _('REV'))
6240 ('r', 'rev', '', _('revision'), _('REV'))
6240 ] + mergetoolopts,
6241 ] + mergetoolopts,
6241 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6242 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6242 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6243 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6243 helpbasic=True)
6244 helpbasic=True)
6244 def update(ui, repo, node=None, **opts):
6245 def update(ui, repo, node=None, **opts):
6245 """update working directory (or switch revisions)
6246 """update working directory (or switch revisions)
6246
6247
6247 Update the repository's working directory to the specified
6248 Update the repository's working directory to the specified
6248 changeset. If no changeset is specified, update to the tip of the
6249 changeset. If no changeset is specified, update to the tip of the
6249 current named branch and move the active bookmark (see :hg:`help
6250 current named branch and move the active bookmark (see :hg:`help
6250 bookmarks`).
6251 bookmarks`).
6251
6252
6252 Update sets the working directory's parent revision to the specified
6253 Update sets the working directory's parent revision to the specified
6253 changeset (see :hg:`help parents`).
6254 changeset (see :hg:`help parents`).
6254
6255
6255 If the changeset is not a descendant or ancestor of the working
6256 If the changeset is not a descendant or ancestor of the working
6256 directory's parent and there are uncommitted changes, the update is
6257 directory's parent and there are uncommitted changes, the update is
6257 aborted. With the -c/--check option, the working directory is checked
6258 aborted. With the -c/--check option, the working directory is checked
6258 for uncommitted changes; if none are found, the working directory is
6259 for uncommitted changes; if none are found, the working directory is
6259 updated to the specified changeset.
6260 updated to the specified changeset.
6260
6261
6261 .. container:: verbose
6262 .. container:: verbose
6262
6263
6263 The -C/--clean, -c/--check, and -m/--merge options control what
6264 The -C/--clean, -c/--check, and -m/--merge options control what
6264 happens if the working directory contains uncommitted changes.
6265 happens if the working directory contains uncommitted changes.
6265 At most of one of them can be specified.
6266 At most of one of them can be specified.
6266
6267
6267 1. If no option is specified, and if
6268 1. If no option is specified, and if
6268 the requested changeset is an ancestor or descendant of
6269 the requested changeset is an ancestor or descendant of
6269 the working directory's parent, the uncommitted changes
6270 the working directory's parent, the uncommitted changes
6270 are merged into the requested changeset and the merged
6271 are merged into the requested changeset and the merged
6271 result is left uncommitted. If the requested changeset is
6272 result is left uncommitted. If the requested changeset is
6272 not an ancestor or descendant (that is, it is on another
6273 not an ancestor or descendant (that is, it is on another
6273 branch), the update is aborted and the uncommitted changes
6274 branch), the update is aborted and the uncommitted changes
6274 are preserved.
6275 are preserved.
6275
6276
6276 2. With the -m/--merge option, the update is allowed even if the
6277 2. With the -m/--merge option, the update is allowed even if the
6277 requested changeset is not an ancestor or descendant of
6278 requested changeset is not an ancestor or descendant of
6278 the working directory's parent.
6279 the working directory's parent.
6279
6280
6280 3. With the -c/--check option, the update is aborted and the
6281 3. With the -c/--check option, the update is aborted and the
6281 uncommitted changes are preserved.
6282 uncommitted changes are preserved.
6282
6283
6283 4. With the -C/--clean option, uncommitted changes are discarded and
6284 4. With the -C/--clean option, uncommitted changes are discarded and
6284 the working directory is updated to the requested changeset.
6285 the working directory is updated to the requested changeset.
6285
6286
6286 To cancel an uncommitted merge (and lose your changes), use
6287 To cancel an uncommitted merge (and lose your changes), use
6287 :hg:`merge --abort`.
6288 :hg:`merge --abort`.
6288
6289
6289 Use null as the changeset to remove the working directory (like
6290 Use null as the changeset to remove the working directory (like
6290 :hg:`clone -U`).
6291 :hg:`clone -U`).
6291
6292
6292 If you want to revert just one file to an older revision, use
6293 If you want to revert just one file to an older revision, use
6293 :hg:`revert [-r REV] NAME`.
6294 :hg:`revert [-r REV] NAME`.
6294
6295
6295 See :hg:`help dates` for a list of formats valid for -d/--date.
6296 See :hg:`help dates` for a list of formats valid for -d/--date.
6296
6297
6297 Returns 0 on success, 1 if there are unresolved files.
6298 Returns 0 on success, 1 if there are unresolved files.
6298 """
6299 """
6299 rev = opts.get(r'rev')
6300 rev = opts.get(r'rev')
6300 date = opts.get(r'date')
6301 date = opts.get(r'date')
6301 clean = opts.get(r'clean')
6302 clean = opts.get(r'clean')
6302 check = opts.get(r'check')
6303 check = opts.get(r'check')
6303 merge = opts.get(r'merge')
6304 merge = opts.get(r'merge')
6304 if rev and node:
6305 if rev and node:
6305 raise error.Abort(_("please specify just one revision"))
6306 raise error.Abort(_("please specify just one revision"))
6306
6307
6307 if ui.configbool('commands', 'update.requiredest'):
6308 if ui.configbool('commands', 'update.requiredest'):
6308 if not node and not rev and not date:
6309 if not node and not rev and not date:
6309 raise error.Abort(_('you must specify a destination'),
6310 raise error.Abort(_('you must specify a destination'),
6310 hint=_('for example: hg update ".::"'))
6311 hint=_('for example: hg update ".::"'))
6311
6312
6312 if rev is None or rev == '':
6313 if rev is None or rev == '':
6313 rev = node
6314 rev = node
6314
6315
6315 if date and rev is not None:
6316 if date and rev is not None:
6316 raise error.Abort(_("you can't specify a revision and a date"))
6317 raise error.Abort(_("you can't specify a revision and a date"))
6317
6318
6318 if len([x for x in (clean, check, merge) if x]) > 1:
6319 if len([x for x in (clean, check, merge) if x]) > 1:
6319 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6320 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6320 "or -m/--merge"))
6321 "or -m/--merge"))
6321
6322
6322 updatecheck = None
6323 updatecheck = None
6323 if check:
6324 if check:
6324 updatecheck = 'abort'
6325 updatecheck = 'abort'
6325 elif merge:
6326 elif merge:
6326 updatecheck = 'none'
6327 updatecheck = 'none'
6327
6328
6328 with repo.wlock():
6329 with repo.wlock():
6329 cmdutil.clearunfinished(repo)
6330 cmdutil.clearunfinished(repo)
6330 if date:
6331 if date:
6331 rev = cmdutil.finddate(ui, repo, date)
6332 rev = cmdutil.finddate(ui, repo, date)
6332
6333
6333 # if we defined a bookmark, we have to remember the original name
6334 # if we defined a bookmark, we have to remember the original name
6334 brev = rev
6335 brev = rev
6335 if rev:
6336 if rev:
6336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6337 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6337 ctx = scmutil.revsingle(repo, rev, default=None)
6338 ctx = scmutil.revsingle(repo, rev, default=None)
6338 rev = ctx.rev()
6339 rev = ctx.rev()
6339 hidden = ctx.hidden()
6340 hidden = ctx.hidden()
6340 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6341 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6341 with ui.configoverride(overrides, 'update'):
6342 with ui.configoverride(overrides, 'update'):
6342 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6343 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6343 updatecheck=updatecheck)
6344 updatecheck=updatecheck)
6344 if hidden:
6345 if hidden:
6345 ctxstr = ctx.hex()[:12]
6346 ctxstr = ctx.hex()[:12]
6346 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6347 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6347
6348
6348 if ctx.obsolete():
6349 if ctx.obsolete():
6349 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6350 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6350 ui.warn("(%s)\n" % obsfatemsg)
6351 ui.warn("(%s)\n" % obsfatemsg)
6351 return ret
6352 return ret
6352
6353
6353 @command('verify',
6354 @command('verify',
6354 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6355 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6355 helpcategory=command.CATEGORY_MAINTENANCE)
6356 helpcategory=command.CATEGORY_MAINTENANCE)
6356 def verify(ui, repo, **opts):
6357 def verify(ui, repo, **opts):
6357 """verify the integrity of the repository
6358 """verify the integrity of the repository
6358
6359
6359 Verify the integrity of the current repository.
6360 Verify the integrity of the current repository.
6360
6361
6361 This will perform an extensive check of the repository's
6362 This will perform an extensive check of the repository's
6362 integrity, validating the hashes and checksums of each entry in
6363 integrity, validating the hashes and checksums of each entry in
6363 the changelog, manifest, and tracked files, as well as the
6364 the changelog, manifest, and tracked files, as well as the
6364 integrity of their crosslinks and indices.
6365 integrity of their crosslinks and indices.
6365
6366
6366 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6367 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6367 for more information about recovery from corruption of the
6368 for more information about recovery from corruption of the
6368 repository.
6369 repository.
6369
6370
6370 Returns 0 on success, 1 if errors are encountered.
6371 Returns 0 on success, 1 if errors are encountered.
6371 """
6372 """
6372 opts = pycompat.byteskwargs(opts)
6373 opts = pycompat.byteskwargs(opts)
6373
6374
6374 level = None
6375 level = None
6375 if opts['full']:
6376 if opts['full']:
6376 level = verifymod.VERIFY_FULL
6377 level = verifymod.VERIFY_FULL
6377 return hg.verify(repo, level)
6378 return hg.verify(repo, level)
6378
6379
6379 @command(
6380 @command(
6380 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6381 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6381 norepo=True, intents={INTENT_READONLY})
6382 norepo=True, intents={INTENT_READONLY})
6382 def version_(ui, **opts):
6383 def version_(ui, **opts):
6383 """output version and copyright information
6384 """output version and copyright information
6384
6385
6385 .. container:: verbose
6386 .. container:: verbose
6386
6387
6387 Template:
6388 Template:
6388
6389
6389 The following keywords are supported. See also :hg:`help templates`.
6390 The following keywords are supported. See also :hg:`help templates`.
6390
6391
6391 :extensions: List of extensions.
6392 :extensions: List of extensions.
6392 :ver: String. Version number.
6393 :ver: String. Version number.
6393
6394
6394 And each entry of ``{extensions}`` provides the following sub-keywords
6395 And each entry of ``{extensions}`` provides the following sub-keywords
6395 in addition to ``{ver}``.
6396 in addition to ``{ver}``.
6396
6397
6397 :bundled: Boolean. True if included in the release.
6398 :bundled: Boolean. True if included in the release.
6398 :name: String. Extension name.
6399 :name: String. Extension name.
6399 """
6400 """
6400 opts = pycompat.byteskwargs(opts)
6401 opts = pycompat.byteskwargs(opts)
6401 if ui.verbose:
6402 if ui.verbose:
6402 ui.pager('version')
6403 ui.pager('version')
6403 fm = ui.formatter("version", opts)
6404 fm = ui.formatter("version", opts)
6404 fm.startitem()
6405 fm.startitem()
6405 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6406 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6406 util.version())
6407 util.version())
6407 license = _(
6408 license = _(
6408 "(see https://mercurial-scm.org for more information)\n"
6409 "(see https://mercurial-scm.org for more information)\n"
6409 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6410 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6410 "This is free software; see the source for copying conditions. "
6411 "This is free software; see the source for copying conditions. "
6411 "There is NO\nwarranty; "
6412 "There is NO\nwarranty; "
6412 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6413 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6413 )
6414 )
6414 if not ui.quiet:
6415 if not ui.quiet:
6415 fm.plain(license)
6416 fm.plain(license)
6416
6417
6417 if ui.verbose:
6418 if ui.verbose:
6418 fm.plain(_("\nEnabled extensions:\n\n"))
6419 fm.plain(_("\nEnabled extensions:\n\n"))
6419 # format names and versions into columns
6420 # format names and versions into columns
6420 names = []
6421 names = []
6421 vers = []
6422 vers = []
6422 isinternals = []
6423 isinternals = []
6423 for name, module in extensions.extensions():
6424 for name, module in extensions.extensions():
6424 names.append(name)
6425 names.append(name)
6425 vers.append(extensions.moduleversion(module) or None)
6426 vers.append(extensions.moduleversion(module) or None)
6426 isinternals.append(extensions.ismoduleinternal(module))
6427 isinternals.append(extensions.ismoduleinternal(module))
6427 fn = fm.nested("extensions", tmpl='{name}\n')
6428 fn = fm.nested("extensions", tmpl='{name}\n')
6428 if names:
6429 if names:
6429 namefmt = " %%-%ds " % max(len(n) for n in names)
6430 namefmt = " %%-%ds " % max(len(n) for n in names)
6430 places = [_("external"), _("internal")]
6431 places = [_("external"), _("internal")]
6431 for n, v, p in zip(names, vers, isinternals):
6432 for n, v, p in zip(names, vers, isinternals):
6432 fn.startitem()
6433 fn.startitem()
6433 fn.condwrite(ui.verbose, "name", namefmt, n)
6434 fn.condwrite(ui.verbose, "name", namefmt, n)
6434 if ui.verbose:
6435 if ui.verbose:
6435 fn.plain("%s " % places[p])
6436 fn.plain("%s " % places[p])
6436 fn.data(bundled=p)
6437 fn.data(bundled=p)
6437 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6438 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6438 if ui.verbose:
6439 if ui.verbose:
6439 fn.plain("\n")
6440 fn.plain("\n")
6440 fn.end()
6441 fn.end()
6441 fm.end()
6442 fm.end()
6442
6443
6443 def loadcmdtable(ui, name, cmdtable):
6444 def loadcmdtable(ui, name, cmdtable):
6444 """Load command functions from specified cmdtable
6445 """Load command functions from specified cmdtable
6445 """
6446 """
6446 overrides = [cmd for cmd in cmdtable if cmd in table]
6447 overrides = [cmd for cmd in cmdtable if cmd in table]
6447 if overrides:
6448 if overrides:
6448 ui.warn(_("extension '%s' overrides commands: %s\n")
6449 ui.warn(_("extension '%s' overrides commands: %s\n")
6449 % (name, " ".join(overrides)))
6450 % (name, " ".join(overrides)))
6450 table.update(cmdtable)
6451 table.update(cmdtable)
@@ -1,584 +1,586 b''
1 Test uncommit - set up the config
1 Test uncommit - set up the config
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [experimental]
4 > [experimental]
5 > evolution.createmarkers=True
5 > evolution.createmarkers=True
6 > evolution.allowunstable=True
6 > evolution.allowunstable=True
7 > [extensions]
7 > [extensions]
8 > uncommit =
8 > uncommit =
9 > drawdag=$TESTDIR/drawdag.py
9 > drawdag=$TESTDIR/drawdag.py
10 > EOF
10 > EOF
11
11
12 Build up a repo
12 Build up a repo
13
13
14 $ hg init repo
14 $ hg init repo
15 $ cd repo
15 $ cd repo
16 $ hg bookmark foo
16 $ hg bookmark foo
17
17
18 Help for uncommit
18 Help for uncommit
19
19
20 $ hg help uncommit
20 $ hg help uncommit
21 hg uncommit [OPTION]... [FILE]...
21 hg uncommit [OPTION]... [FILE]...
22
22
23 uncommit part or all of a local changeset
23 uncommit part or all of a local changeset
24
24
25 This command undoes the effect of a local commit, returning the affected
25 This command undoes the effect of a local commit, returning the affected
26 files to their uncommitted state. This means that files modified or
26 files to their uncommitted state. This means that files modified or
27 deleted in the changeset will be left unchanged, and so will remain
27 deleted in the changeset will be left unchanged, and so will remain
28 modified in the working directory.
28 modified in the working directory.
29
29
30 If no files are specified, the commit will be pruned, unless --keep is
30 If no files are specified, the commit will be pruned, unless --keep is
31 given.
31 given.
32
32
33 (use 'hg help -e uncommit' to show help for the uncommit extension)
33 (use 'hg help -e uncommit' to show help for the uncommit extension)
34
34
35 options ([+] can be repeated):
35 options ([+] can be repeated):
36
36
37 --keep allow an empty commit after uncommiting
37 --keep allow an empty commit after uncommiting
38 --allow-dirty-working-copy allow uncommit with outstanding changes
38 --allow-dirty-working-copy allow uncommit with outstanding changes
39 -I --include PATTERN [+] include names matching the given patterns
39 -I --include PATTERN [+] include names matching the given patterns
40 -X --exclude PATTERN [+] exclude names matching the given patterns
40 -X --exclude PATTERN [+] exclude names matching the given patterns
41 -m --message TEXT use text as commit message
41 -m --message TEXT use text as commit message
42 -l --logfile FILE read commit message from file
42 -l --logfile FILE read commit message from file
43 -d --date DATE record the specified date as commit date
43 -d --date DATE record the specified date as commit date
44 -u --user USER record the specified user as committer
44 -u --user USER record the specified user as committer
45 -D --current-date record the current date as commit date
46 -U --current-user record the current user as committer
45
47
46 (some details hidden, use --verbose to show complete help)
48 (some details hidden, use --verbose to show complete help)
47
49
48 Uncommit with no commits should fail
50 Uncommit with no commits should fail
49
51
50 $ hg uncommit
52 $ hg uncommit
51 abort: cannot uncommit null changeset
53 abort: cannot uncommit null changeset
52 (no changeset checked out)
54 (no changeset checked out)
53 [255]
55 [255]
54
56
55 Create some commits
57 Create some commits
56
58
57 $ touch files
59 $ touch files
58 $ hg add files
60 $ hg add files
59 $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
61 $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
60 $ ls
62 $ ls
61 file-a
63 file-a
62 file-ab
64 file-ab
63 file-abc
65 file-abc
64 file-abcd
66 file-abcd
65 file-abcde
67 file-abcde
66 files
68 files
67
69
68 $ hg log -G -T '{rev}:{node} {desc}' --hidden
70 $ hg log -G -T '{rev}:{node} {desc}' --hidden
69 @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
71 @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
70 |
72 |
71 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
73 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
72 |
74 |
73 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
75 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
74 |
76 |
75 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
77 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
76 |
78 |
77 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
79 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
78
80
79 Simple uncommit off the top, also moves bookmark
81 Simple uncommit off the top, also moves bookmark
80
82
81 $ hg bookmark
83 $ hg bookmark
82 * foo 4:6c4fd43ed714
84 * foo 4:6c4fd43ed714
83 $ hg uncommit
85 $ hg uncommit
84 $ hg status
86 $ hg status
85 M files
87 M files
86 A file-abcde
88 A file-abcde
87 $ hg bookmark
89 $ hg bookmark
88 * foo 3:6db330d65db4
90 * foo 3:6db330d65db4
89
91
90 $ hg log -G -T '{rev}:{node} {desc}' --hidden
92 $ hg log -G -T '{rev}:{node} {desc}' --hidden
91 x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
93 x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
92 |
94 |
93 @ 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
95 @ 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
94 |
96 |
95 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
97 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
96 |
98 |
97 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
99 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
98 |
100 |
99 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
101 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
100
102
101
103
102 Recommit
104 Recommit
103
105
104 $ hg commit -m 'new change abcde'
106 $ hg commit -m 'new change abcde'
105 $ hg status
107 $ hg status
106 $ hg heads -T '{rev}:{node} {desc}'
108 $ hg heads -T '{rev}:{node} {desc}'
107 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
109 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
108
110
109 Uncommit of non-existent and unchanged files aborts
111 Uncommit of non-existent and unchanged files aborts
110 $ hg uncommit nothinghere
112 $ hg uncommit nothinghere
111 abort: cannot uncommit "nothinghere"
113 abort: cannot uncommit "nothinghere"
112 (file does not exist)
114 (file does not exist)
113 [255]
115 [255]
114 $ hg status
116 $ hg status
115 $ hg uncommit file-abc
117 $ hg uncommit file-abc
116 abort: cannot uncommit "file-abc"
118 abort: cannot uncommit "file-abc"
117 (file was not changed in working directory parent)
119 (file was not changed in working directory parent)
118 [255]
120 [255]
119 $ hg status
121 $ hg status
120
122
121 Try partial uncommit, also moves bookmark
123 Try partial uncommit, also moves bookmark
122
124
123 $ hg bookmark
125 $ hg bookmark
124 * foo 5:0c07a3ccda77
126 * foo 5:0c07a3ccda77
125 $ hg uncommit files
127 $ hg uncommit files
126 $ hg status
128 $ hg status
127 M files
129 M files
128 $ hg bookmark
130 $ hg bookmark
129 * foo 6:3727deee06f7
131 * foo 6:3727deee06f7
130 $ hg heads -T '{rev}:{node} {desc}'
132 $ hg heads -T '{rev}:{node} {desc}'
131 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
133 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
132 $ hg log -r . -p -T '{rev}:{node} {desc}'
134 $ hg log -r . -p -T '{rev}:{node} {desc}'
133 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
135 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
134 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
136 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
135 +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000
137 +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000
136 @@ -0,0 +1,1 @@
138 @@ -0,0 +1,1 @@
137 +abcde
139 +abcde
138
140
139 $ hg log -G -T '{rev}:{node} {desc}' --hidden
141 $ hg log -G -T '{rev}:{node} {desc}' --hidden
140 @ 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
142 @ 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
141 |
143 |
142 | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
144 | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
143 |/
145 |/
144 | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
146 | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
145 |/
147 |/
146 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
148 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
147 |
149 |
148 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
150 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
149 |
151 |
150 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
152 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
151 |
153 |
152 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
154 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
153
155
154 $ hg commit -m 'update files for abcde'
156 $ hg commit -m 'update files for abcde'
155
157
156 Uncommit with dirty state
158 Uncommit with dirty state
157
159
158 $ echo "foo" >> files
160 $ echo "foo" >> files
159 $ cat files
161 $ cat files
160 abcde
162 abcde
161 foo
163 foo
162 $ hg status
164 $ hg status
163 M files
165 M files
164 $ hg uncommit
166 $ hg uncommit
165 abort: uncommitted changes
167 abort: uncommitted changes
166 (requires --allow-dirty-working-copy to uncommit)
168 (requires --allow-dirty-working-copy to uncommit)
167 [255]
169 [255]
168 $ hg uncommit files
170 $ hg uncommit files
169 abort: uncommitted changes
171 abort: uncommitted changes
170 (requires --allow-dirty-working-copy to uncommit)
172 (requires --allow-dirty-working-copy to uncommit)
171 [255]
173 [255]
172 $ cat files
174 $ cat files
173 abcde
175 abcde
174 foo
176 foo
175 $ hg commit --amend -m "files abcde + foo"
177 $ hg commit --amend -m "files abcde + foo"
176
178
177 Testing the 'experimental.uncommitondirtywdir' config
179 Testing the 'experimental.uncommitondirtywdir' config
178
180
179 $ echo "bar" >> files
181 $ echo "bar" >> files
180 $ hg uncommit
182 $ hg uncommit
181 abort: uncommitted changes
183 abort: uncommitted changes
182 (requires --allow-dirty-working-copy to uncommit)
184 (requires --allow-dirty-working-copy to uncommit)
183 [255]
185 [255]
184 $ hg uncommit --config experimental.uncommitondirtywdir=True
186 $ hg uncommit --config experimental.uncommitondirtywdir=True
185 $ hg commit -m "files abcde + foo"
187 $ hg commit -m "files abcde + foo"
186
188
187 Uncommit in the middle of a stack, does not move bookmark
189 Uncommit in the middle of a stack, does not move bookmark
188
190
189 $ hg checkout '.^^^'
191 $ hg checkout '.^^^'
190 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
192 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
191 (leaving bookmark foo)
193 (leaving bookmark foo)
192 $ hg log -r . -p -T '{rev}:{node} {desc}'
194 $ hg log -r . -p -T '{rev}:{node} {desc}'
193 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
195 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
194 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
196 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
195 +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000
197 +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000
196 @@ -0,0 +1,1 @@
198 @@ -0,0 +1,1 @@
197 +abc
199 +abc
198 diff -r 69a232e754b0 -r abf2df566fc1 files
200 diff -r 69a232e754b0 -r abf2df566fc1 files
199 --- a/files Thu Jan 01 00:00:00 1970 +0000
201 --- a/files Thu Jan 01 00:00:00 1970 +0000
200 +++ b/files Thu Jan 01 00:00:00 1970 +0000
202 +++ b/files Thu Jan 01 00:00:00 1970 +0000
201 @@ -1,1 +1,1 @@
203 @@ -1,1 +1,1 @@
202 -ab
204 -ab
203 +abc
205 +abc
204
206
205 $ hg bookmark
207 $ hg bookmark
206 foo 9:48e5bd7cd583
208 foo 9:48e5bd7cd583
207 $ hg uncommit
209 $ hg uncommit
208 3 new orphan changesets
210 3 new orphan changesets
209 $ hg status
211 $ hg status
210 M files
212 M files
211 A file-abc
213 A file-abc
212 $ hg heads -T '{rev}:{node} {desc}'
214 $ hg heads -T '{rev}:{node} {desc}'
213 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
215 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
214 $ hg bookmark
216 $ hg bookmark
215 foo 9:48e5bd7cd583
217 foo 9:48e5bd7cd583
216 $ hg commit -m 'new abc'
218 $ hg commit -m 'new abc'
217 created new head
219 created new head
218
220
219 Partial uncommit in the middle, does not move bookmark
221 Partial uncommit in the middle, does not move bookmark
220
222
221 $ hg checkout '.^'
223 $ hg checkout '.^'
222 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
224 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
223 $ hg log -r . -p -T '{rev}:{node} {desc}'
225 $ hg log -r . -p -T '{rev}:{node} {desc}'
224 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
226 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
225 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
227 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
226 +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000
228 +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000
227 @@ -0,0 +1,1 @@
229 @@ -0,0 +1,1 @@
228 +ab
230 +ab
229 diff -r 3004d2d9b508 -r 69a232e754b0 files
231 diff -r 3004d2d9b508 -r 69a232e754b0 files
230 --- a/files Thu Jan 01 00:00:00 1970 +0000
232 --- a/files Thu Jan 01 00:00:00 1970 +0000
231 +++ b/files Thu Jan 01 00:00:00 1970 +0000
233 +++ b/files Thu Jan 01 00:00:00 1970 +0000
232 @@ -1,1 +1,1 @@
234 @@ -1,1 +1,1 @@
233 -a
235 -a
234 +ab
236 +ab
235
237
236 $ hg bookmark
238 $ hg bookmark
237 foo 9:48e5bd7cd583
239 foo 9:48e5bd7cd583
238 $ hg uncommit file-ab
240 $ hg uncommit file-ab
239 1 new orphan changesets
241 1 new orphan changesets
240 $ hg status
242 $ hg status
241 A file-ab
243 A file-ab
242
244
243 $ hg heads -T '{rev}:{node} {desc}\n'
245 $ hg heads -T '{rev}:{node} {desc}\n'
244 11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
246 11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
245 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
247 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
246 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
248 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
247
249
248 $ hg bookmark
250 $ hg bookmark
249 foo 9:48e5bd7cd583
251 foo 9:48e5bd7cd583
250 $ hg commit -m 'update ab'
252 $ hg commit -m 'update ab'
251 $ hg status
253 $ hg status
252 $ hg heads -T '{rev}:{node} {desc}\n'
254 $ hg heads -T '{rev}:{node} {desc}\n'
253 12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
255 12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
254 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
256 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
255 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
257 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
256
258
257 $ hg log -G -T '{rev}:{node} {desc}' --hidden
259 $ hg log -G -T '{rev}:{node} {desc}' --hidden
258 @ 12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
260 @ 12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
259 |
261 |
260 o 11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
262 o 11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
261 |
263 |
262 | * 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
264 | * 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
263 | |
265 | |
264 | | * 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
266 | | * 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
265 | | |
267 | | |
266 | | | x 8:84beeba0ac30e19521c036e4d2dd3a5fa02586ff files abcde + foo
268 | | | x 8:84beeba0ac30e19521c036e4d2dd3a5fa02586ff files abcde + foo
267 | | |/
269 | | |/
268 | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
270 | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
269 | | |/
271 | | |/
270 | | * 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
272 | | * 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
271 | | |
273 | | |
272 | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
274 | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
273 | | |/
275 | | |/
274 | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
276 | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
275 | | |/
277 | | |/
276 | | * 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
278 | | * 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
277 | | |
279 | | |
278 | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
280 | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
279 | |/
281 | |/
280 | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
282 | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
281 |/
283 |/
282 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
284 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
283
285
284 Uncommit with draft parent
286 Uncommit with draft parent
285
287
286 $ hg uncommit
288 $ hg uncommit
287 $ hg phase -r .
289 $ hg phase -r .
288 11: draft
290 11: draft
289 $ hg commit -m 'update ab again'
291 $ hg commit -m 'update ab again'
290
292
291 Phase is preserved
293 Phase is preserved
292
294
293 $ hg uncommit --keep --config phases.new-commit=secret
295 $ hg uncommit --keep --config phases.new-commit=secret
294 note: keeping empty commit
296 note: keeping empty commit
295 $ hg phase -r .
297 $ hg phase -r .
296 14: draft
298 14: draft
297 $ hg commit --amend -m 'update ab again'
299 $ hg commit --amend -m 'update ab again'
298
300
299 Uncommit with public parent
301 Uncommit with public parent
300
302
301 $ hg phase -p "::.^"
303 $ hg phase -p "::.^"
302 $ hg uncommit
304 $ hg uncommit
303 $ hg phase -r .
305 $ hg phase -r .
304 11: public
306 11: public
305
307
306 Partial uncommit with public parent
308 Partial uncommit with public parent
307
309
308 $ echo xyz > xyz
310 $ echo xyz > xyz
309 $ hg add xyz
311 $ hg add xyz
310 $ hg commit -m "update ab and add xyz"
312 $ hg commit -m "update ab and add xyz"
311 $ hg uncommit xyz
313 $ hg uncommit xyz
312 $ hg status
314 $ hg status
313 A xyz
315 A xyz
314 $ hg phase -r .
316 $ hg phase -r .
315 17: draft
317 17: draft
316 $ hg phase -r ".^"
318 $ hg phase -r ".^"
317 11: public
319 11: public
318
320
319 Uncommit with --keep or experimental.uncommit.keep leaves an empty changeset
321 Uncommit with --keep or experimental.uncommit.keep leaves an empty changeset
320
322
321 $ cd $TESTTMP
323 $ cd $TESTTMP
322 $ hg init repo1
324 $ hg init repo1
323 $ cd repo1
325 $ cd repo1
324 $ hg debugdrawdag <<'EOS'
326 $ hg debugdrawdag <<'EOS'
325 > Q
327 > Q
326 > |
328 > |
327 > P
329 > P
328 > EOS
330 > EOS
329 $ hg up Q -q
331 $ hg up Q -q
330 $ hg uncommit --keep
332 $ hg uncommit --keep
331 note: keeping empty commit
333 note: keeping empty commit
332 $ hg log -G -T '{desc} FILES: {files}'
334 $ hg log -G -T '{desc} FILES: {files}'
333 @ Q FILES:
335 @ Q FILES:
334 |
336 |
335 | x Q FILES: Q
337 | x Q FILES: Q
336 |/
338 |/
337 o P FILES: P
339 o P FILES: P
338
340
339 $ cat >> .hg/hgrc <<EOF
341 $ cat >> .hg/hgrc <<EOF
340 > [experimental]
342 > [experimental]
341 > uncommit.keep=True
343 > uncommit.keep=True
342 > EOF
344 > EOF
343 $ hg ci --amend
345 $ hg ci --amend
344 $ hg uncommit
346 $ hg uncommit
345 note: keeping empty commit
347 note: keeping empty commit
346 $ hg log -G -T '{desc} FILES: {files}'
348 $ hg log -G -T '{desc} FILES: {files}'
347 @ Q FILES:
349 @ Q FILES:
348 |
350 |
349 | x Q FILES: Q
351 | x Q FILES: Q
350 |/
352 |/
351 o P FILES: P
353 o P FILES: P
352
354
353 $ hg status
355 $ hg status
354 A Q
356 A Q
355 $ hg ci --amend
357 $ hg ci --amend
356 $ hg uncommit --no-keep
358 $ hg uncommit --no-keep
357 $ hg log -G -T '{desc} FILES: {files}'
359 $ hg log -G -T '{desc} FILES: {files}'
358 x Q FILES: Q
360 x Q FILES: Q
359 |
361 |
360 @ P FILES: P
362 @ P FILES: P
361
363
362 $ hg status
364 $ hg status
363 A Q
365 A Q
364 $ cd ..
366 $ cd ..
365 $ rm -rf repo1
367 $ rm -rf repo1
366
368
367 Testing uncommit while merge
369 Testing uncommit while merge
368
370
369 $ hg init repo2
371 $ hg init repo2
370 $ cd repo2
372 $ cd repo2
371
373
372 Create some history
374 Create some history
373
375
374 $ touch a
376 $ touch a
375 $ hg add a
377 $ hg add a
376 $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
378 $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
377 $ hg checkout 0
379 $ hg checkout 0
378 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
379 $ touch b
381 $ touch b
380 $ hg add b
382 $ hg add b
381 $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
383 $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
382 created new head
384 created new head
383 $ hg log -G -T '{rev}:{node} {desc}' --hidden
385 $ hg log -G -T '{rev}:{node} {desc}' --hidden
384 @ 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
386 @ 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
385 |
387 |
386 o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
388 o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
387 |
389 |
388 o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
390 o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
389 |
391 |
390 | o 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
392 | o 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
391 | |
393 | |
392 | o 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
394 | o 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
393 |/
395 |/
394 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
396 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
395
397
396
398
397 Add and expect uncommit to fail on both merge working dir and merge changeset
399 Add and expect uncommit to fail on both merge working dir and merge changeset
398
400
399 $ hg merge 2
401 $ hg merge 2
400 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
401 (branch merge, don't forget to commit)
403 (branch merge, don't forget to commit)
402
404
403 $ hg uncommit
405 $ hg uncommit
404 abort: outstanding uncommitted merge
406 abort: outstanding uncommitted merge
405 (requires --allow-dirty-working-copy to uncommit)
407 (requires --allow-dirty-working-copy to uncommit)
406 [255]
408 [255]
407
409
408 $ hg uncommit --config experimental.uncommitondirtywdir=True
410 $ hg uncommit --config experimental.uncommitondirtywdir=True
409 abort: cannot uncommit while merging
411 abort: cannot uncommit while merging
410 [255]
412 [255]
411
413
412 $ hg status
414 $ hg status
413 M a
415 M a
414 $ hg commit -m 'merge a and b'
416 $ hg commit -m 'merge a and b'
415
417
416 $ hg uncommit
418 $ hg uncommit
417 abort: cannot uncommit merge changeset
419 abort: cannot uncommit merge changeset
418 [255]
420 [255]
419
421
420 $ hg status
422 $ hg status
421 $ hg log -G -T '{rev}:{node} {desc}' --hidden
423 $ hg log -G -T '{rev}:{node} {desc}' --hidden
422 @ 6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
424 @ 6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
423 |\
425 |\
424 | o 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
426 | o 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
425 | |
427 | |
426 | o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
428 | o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
427 | |
429 | |
428 | o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
430 | o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
429 | |
431 | |
430 o | 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
432 o | 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
431 | |
433 | |
432 o | 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
434 o | 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
433 |/
435 |/
434 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
436 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
435
437
436
438
437 Rename a->b, then remove b in working copy. Result should remove a.
439 Rename a->b, then remove b in working copy. Result should remove a.
438
440
439 $ hg co -q 0
441 $ hg co -q 0
440 $ hg mv a b
442 $ hg mv a b
441 $ hg ci -qm 'move a to b'
443 $ hg ci -qm 'move a to b'
442 $ hg rm b
444 $ hg rm b
443 $ hg uncommit --config experimental.uncommitondirtywdir=True
445 $ hg uncommit --config experimental.uncommitondirtywdir=True
444 $ hg st --copies
446 $ hg st --copies
445 R a
447 R a
446 $ hg revert a
448 $ hg revert a
447
449
448 Rename a->b, then rename b->c in working copy. Result should rename a->c.
450 Rename a->b, then rename b->c in working copy. Result should rename a->c.
449
451
450 $ hg co -q 0
452 $ hg co -q 0
451 $ hg mv a b
453 $ hg mv a b
452 $ hg ci -qm 'move a to b'
454 $ hg ci -qm 'move a to b'
453 $ hg mv b c
455 $ hg mv b c
454 $ hg uncommit --config experimental.uncommitondirtywdir=True
456 $ hg uncommit --config experimental.uncommitondirtywdir=True
455 $ hg st --copies
457 $ hg st --copies
456 A c
458 A c
457 a
459 a
458 R a
460 R a
459 $ hg revert a
461 $ hg revert a
460 $ hg forget c
462 $ hg forget c
461 $ rm c
463 $ rm c
462
464
463 Copy a->b1 and a->b2, then rename b1->c in working copy. Result should copy a->b2 and a->c.
465 Copy a->b1 and a->b2, then rename b1->c in working copy. Result should copy a->b2 and a->c.
464
466
465 $ hg co -q 0
467 $ hg co -q 0
466 $ hg cp a b1
468 $ hg cp a b1
467 $ hg cp a b2
469 $ hg cp a b2
468 $ hg ci -qm 'move a to b1 and b2'
470 $ hg ci -qm 'move a to b1 and b2'
469 $ hg mv b1 c
471 $ hg mv b1 c
470 $ hg uncommit --config experimental.uncommitondirtywdir=True
472 $ hg uncommit --config experimental.uncommitondirtywdir=True
471 $ hg st --copies
473 $ hg st --copies
472 A b2
474 A b2
473 a
475 a
474 A c
476 A c
475 a
477 a
476 $ cd ..
478 $ cd ..
477
479
478 --allow-dirty-working-copy should also work on a dirty PATH
480 --allow-dirty-working-copy should also work on a dirty PATH
479
481
480 $ hg init issue5977
482 $ hg init issue5977
481 $ cd issue5977
483 $ cd issue5977
482 $ echo 'super critical info!' > a
484 $ echo 'super critical info!' > a
483 $ hg ci -Am 'add a'
485 $ hg ci -Am 'add a'
484 adding a
486 adding a
485 $ echo 'foo' > b
487 $ echo 'foo' > b
486 $ hg add b
488 $ hg add b
487 $ hg status
489 $ hg status
488 A b
490 A b
489 $ hg unc a
491 $ hg unc a
490 note: keeping empty commit
492 note: keeping empty commit
491 $ cat a
493 $ cat a
492 super critical info!
494 super critical info!
493 $ hg log
495 $ hg log
494 changeset: 1:656ba143d384
496 changeset: 1:656ba143d384
495 tag: tip
497 tag: tip
496 parent: -1:000000000000
498 parent: -1:000000000000
497 user: test
499 user: test
498 date: Thu Jan 01 00:00:00 1970 +0000
500 date: Thu Jan 01 00:00:00 1970 +0000
499 summary: add a
501 summary: add a
500
502
501 $ hg ci -Am 'add b'
503 $ hg ci -Am 'add b'
502 $ echo 'foo bar' > b
504 $ echo 'foo bar' > b
503 $ hg unc b
505 $ hg unc b
504 abort: uncommitted changes
506 abort: uncommitted changes
505 (requires --allow-dirty-working-copy to uncommit)
507 (requires --allow-dirty-working-copy to uncommit)
506 [255]
508 [255]
507 $ hg unc --allow-dirty-working-copy b
509 $ hg unc --allow-dirty-working-copy b
508 $ hg log
510 $ hg log
509 changeset: 3:30fa958635b2
511 changeset: 3:30fa958635b2
510 tag: tip
512 tag: tip
511 parent: 1:656ba143d384
513 parent: 1:656ba143d384
512 user: test
514 user: test
513 date: Thu Jan 01 00:00:00 1970 +0000
515 date: Thu Jan 01 00:00:00 1970 +0000
514 summary: add b
516 summary: add b
515
517
516 changeset: 1:656ba143d384
518 changeset: 1:656ba143d384
517 parent: -1:000000000000
519 parent: -1:000000000000
518 user: test
520 user: test
519 date: Thu Jan 01 00:00:00 1970 +0000
521 date: Thu Jan 01 00:00:00 1970 +0000
520 summary: add a
522 summary: add a
521
523
522 Removes can be uncommitted
524 Removes can be uncommitted
523
525
524 $ hg ci -m 'modified b'
526 $ hg ci -m 'modified b'
525 $ hg rm b
527 $ hg rm b
526 $ hg ci -m 'remove b'
528 $ hg ci -m 'remove b'
527 $ hg uncommit b
529 $ hg uncommit b
528 note: keeping empty commit
530 note: keeping empty commit
529 $ hg status
531 $ hg status
530 R b
532 R b
531
533
532 Uncommitting a directory won't run afoul of the checks that an explicit file
534 Uncommitting a directory won't run afoul of the checks that an explicit file
533 can be uncommitted.
535 can be uncommitted.
534
536
535 $ mkdir dir
537 $ mkdir dir
536 $ echo 1 > dir/file.txt
538 $ echo 1 > dir/file.txt
537 $ hg ci -Aqm 'add file in directory'
539 $ hg ci -Aqm 'add file in directory'
538 $ hg uncommit dir -m 'uncommit with message' -u 'different user' \
540 $ hg uncommit dir -m 'uncommit with message' -u 'different user' \
539 > -d 'Jun 30 12:12:12 1980 +0000'
541 > -d 'Jun 30 12:12:12 1980 +0000'
540 $ hg status
542 $ hg status
541 A dir/file.txt
543 A dir/file.txt
542 $ hg log -r .
544 $ hg log -r .
543 changeset: 8:b4dd26dc42e0
545 changeset: 8:b4dd26dc42e0
544 tag: tip
546 tag: tip
545 parent: 6:2278a4c24330
547 parent: 6:2278a4c24330
546 user: different user
548 user: different user
547 date: Mon Jun 30 12:12:12 1980 +0000
549 date: Mon Jun 30 12:12:12 1980 +0000
548 summary: uncommit with message
550 summary: uncommit with message
549
551
550
552
551 `uncommit <dir>` and `cd <dir> && uncommit .` behave the same...
553 `uncommit <dir>` and `cd <dir> && uncommit .` behave the same...
552
554
553 $ hg rollback -q --config ui.rollback=True
555 $ hg rollback -q --config ui.rollback=True
554 $ echo 2 > dir/file2.txt
556 $ echo 2 > dir/file2.txt
555 $ hg ci -Aqm 'add file2 in directory'
557 $ hg ci -Aqm 'add file2 in directory'
556 $ hg uncommit dir
558 $ hg uncommit dir
557 note: keeping empty commit
559 note: keeping empty commit
558 $ hg status
560 $ hg status
559 A dir/file2.txt
561 A dir/file2.txt
560
562
561 $ hg rollback -q --config ui.rollback=True
563 $ hg rollback -q --config ui.rollback=True
562 $ cd dir
564 $ cd dir
563 $ hg uncommit .
565 $ hg uncommit .
564 note: keeping empty commit
566 note: keeping empty commit
565 $ hg status
567 $ hg status
566 A dir/file2.txt
568 A dir/file2.txt
567 $ cd ..
569 $ cd ..
568
570
569 ... and errors out the same way when nothing can be uncommitted
571 ... and errors out the same way when nothing can be uncommitted
570
572
571 $ hg rollback -q --config ui.rollback=True
573 $ hg rollback -q --config ui.rollback=True
572 $ mkdir emptydir
574 $ mkdir emptydir
573 $ hg uncommit emptydir
575 $ hg uncommit emptydir
574 abort: cannot uncommit "emptydir"
576 abort: cannot uncommit "emptydir"
575 (file was untracked in working directory parent)
577 (file was untracked in working directory parent)
576 [255]
578 [255]
577
579
578 $ cd emptydir
580 $ cd emptydir
579 $ hg uncommit .
581 $ hg uncommit .
580 abort: cannot uncommit "emptydir"
582 abort: cannot uncommit "emptydir"
581 (file was untracked in working directory parent)
583 (file was untracked in working directory parent)
582 [255]
584 [255]
583 $ hg status
585 $ hg status
584 $ cd ..
586 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now