##// END OF EJS Templates
merge: disallow merge abort in case of an unfinished operation (issue6160)...
Taapas Agrawal -
r42780:b8d54f46 default
parent child Browse files
Show More
@@ -1,3413 +1,3421 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 formatteropts = [
103 formatteropts = [
104 ('T', 'template', '',
104 ('T', 'template', '',
105 _('display with template'), _('TEMPLATE')),
105 _('display with template'), _('TEMPLATE')),
106 ]
106 ]
107
107
108 templateopts = [
108 templateopts = [
109 ('', 'style', '',
109 ('', 'style', '',
110 _('display using template map file (DEPRECATED)'), _('STYLE')),
110 _('display using template map file (DEPRECATED)'), _('STYLE')),
111 ('T', 'template', '',
111 ('T', 'template', '',
112 _('display with template'), _('TEMPLATE')),
112 _('display with template'), _('TEMPLATE')),
113 ]
113 ]
114
114
115 logopts = [
115 logopts = [
116 ('p', 'patch', None, _('show patch')),
116 ('p', 'patch', None, _('show patch')),
117 ('g', 'git', None, _('use git extended diff format')),
117 ('g', 'git', None, _('use git extended diff format')),
118 ('l', 'limit', '',
118 ('l', 'limit', '',
119 _('limit number of changes displayed'), _('NUM')),
119 _('limit number of changes displayed'), _('NUM')),
120 ('M', 'no-merges', None, _('do not show merges')),
120 ('M', 'no-merges', None, _('do not show merges')),
121 ('', 'stat', None, _('output diffstat-style summary of changes')),
121 ('', 'stat', None, _('output diffstat-style summary of changes')),
122 ('G', 'graph', None, _("show the revision DAG")),
122 ('G', 'graph', None, _("show the revision DAG")),
123 ] + templateopts
123 ] + templateopts
124
124
125 diffopts = [
125 diffopts = [
126 ('a', 'text', None, _('treat all files as text')),
126 ('a', 'text', None, _('treat all files as text')),
127 ('g', 'git', None, _('use git extended diff format')),
127 ('g', 'git', None, _('use git extended diff format')),
128 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
128 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
129 ('', 'nodates', None, _('omit dates from diff headers'))
129 ('', 'nodates', None, _('omit dates from diff headers'))
130 ]
130 ]
131
131
132 diffwsopts = [
132 diffwsopts = [
133 ('w', 'ignore-all-space', None,
133 ('w', 'ignore-all-space', None,
134 _('ignore white space when comparing lines')),
134 _('ignore white space when comparing lines')),
135 ('b', 'ignore-space-change', None,
135 ('b', 'ignore-space-change', None,
136 _('ignore changes in the amount of white space')),
136 _('ignore changes in the amount of white space')),
137 ('B', 'ignore-blank-lines', None,
137 ('B', 'ignore-blank-lines', None,
138 _('ignore changes whose lines are all blank')),
138 _('ignore changes whose lines are all blank')),
139 ('Z', 'ignore-space-at-eol', None,
139 ('Z', 'ignore-space-at-eol', None,
140 _('ignore changes in whitespace at EOL')),
140 _('ignore changes in whitespace at EOL')),
141 ]
141 ]
142
142
143 diffopts2 = [
143 diffopts2 = [
144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
145 ('p', 'show-function', None, _('show which function each change is in')),
145 ('p', 'show-function', None, _('show which function each change is in')),
146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
147 ] + diffwsopts + [
147 ] + diffwsopts + [
148 ('U', 'unified', '',
148 ('U', 'unified', '',
149 _('number of lines of context to show'), _('NUM')),
149 _('number of lines of context to show'), _('NUM')),
150 ('', 'stat', None, _('output diffstat-style summary of changes')),
150 ('', 'stat', None, _('output diffstat-style summary of changes')),
151 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
151 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
152 ]
152 ]
153
153
154 mergetoolopts = [
154 mergetoolopts = [
155 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
155 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
156 ]
156 ]
157
157
158 similarityopts = [
158 similarityopts = [
159 ('s', 'similarity', '',
159 ('s', 'similarity', '',
160 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
160 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
161 ]
161 ]
162
162
163 subrepoopts = [
163 subrepoopts = [
164 ('S', 'subrepos', None,
164 ('S', 'subrepos', None,
165 _('recurse into subrepositories'))
165 _('recurse into subrepositories'))
166 ]
166 ]
167
167
168 debugrevlogopts = [
168 debugrevlogopts = [
169 ('c', 'changelog', False, _('open changelog')),
169 ('c', 'changelog', False, _('open changelog')),
170 ('m', 'manifest', False, _('open manifest')),
170 ('m', 'manifest', False, _('open manifest')),
171 ('', 'dir', '', _('open directory manifest')),
171 ('', 'dir', '', _('open directory manifest')),
172 ]
172 ]
173
173
174 # special string such that everything below this line will be ingored in the
174 # special string such that everything below this line will be ingored in the
175 # editor text
175 # editor text
176 _linebelow = "^HG: ------------------------ >8 ------------------------$"
176 _linebelow = "^HG: ------------------------ >8 ------------------------$"
177
177
178 def ishunk(x):
178 def ishunk(x):
179 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
179 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
180 return isinstance(x, hunkclasses)
180 return isinstance(x, hunkclasses)
181
181
182 def newandmodified(chunks, originalchunks):
182 def newandmodified(chunks, originalchunks):
183 newlyaddedandmodifiedfiles = set()
183 newlyaddedandmodifiedfiles = set()
184 for chunk in chunks:
184 for chunk in chunks:
185 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in
185 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in
186 originalchunks):
186 originalchunks):
187 newlyaddedandmodifiedfiles.add(chunk.header.filename())
187 newlyaddedandmodifiedfiles.add(chunk.header.filename())
188 return newlyaddedandmodifiedfiles
188 return newlyaddedandmodifiedfiles
189
189
190 def parsealiases(cmd):
190 def parsealiases(cmd):
191 return cmd.split("|")
191 return cmd.split("|")
192
192
193 def setupwrapcolorwrite(ui):
193 def setupwrapcolorwrite(ui):
194 # wrap ui.write so diff output can be labeled/colorized
194 # wrap ui.write so diff output can be labeled/colorized
195 def wrapwrite(orig, *args, **kw):
195 def wrapwrite(orig, *args, **kw):
196 label = kw.pop(r'label', '')
196 label = kw.pop(r'label', '')
197 for chunk, l in patch.difflabel(lambda: args):
197 for chunk, l in patch.difflabel(lambda: args):
198 orig(chunk, label=label + l)
198 orig(chunk, label=label + l)
199
199
200 oldwrite = ui.write
200 oldwrite = ui.write
201 def wrap(*args, **kwargs):
201 def wrap(*args, **kwargs):
202 return wrapwrite(oldwrite, *args, **kwargs)
202 return wrapwrite(oldwrite, *args, **kwargs)
203 setattr(ui, 'write', wrap)
203 setattr(ui, 'write', wrap)
204 return oldwrite
204 return oldwrite
205
205
206 def filterchunks(ui, originalhunks, usecurses, testfile, match,
206 def filterchunks(ui, originalhunks, usecurses, testfile, match,
207 operation=None):
207 operation=None):
208 try:
208 try:
209 if usecurses:
209 if usecurses:
210 if testfile:
210 if testfile:
211 recordfn = crecordmod.testdecorator(
211 recordfn = crecordmod.testdecorator(
212 testfile, crecordmod.testchunkselector)
212 testfile, crecordmod.testchunkselector)
213 else:
213 else:
214 recordfn = crecordmod.chunkselector
214 recordfn = crecordmod.chunkselector
215
215
216 return crecordmod.filterpatch(ui, originalhunks, recordfn,
216 return crecordmod.filterpatch(ui, originalhunks, recordfn,
217 operation)
217 operation)
218 except crecordmod.fallbackerror as e:
218 except crecordmod.fallbackerror as e:
219 ui.warn('%s\n' % e.message)
219 ui.warn('%s\n' % e.message)
220 ui.warn(_('falling back to text mode\n'))
220 ui.warn(_('falling back to text mode\n'))
221
221
222 return patch.filterpatch(ui, originalhunks, match, operation)
222 return patch.filterpatch(ui, originalhunks, match, operation)
223
223
224 def recordfilter(ui, originalhunks, match, operation=None):
224 def recordfilter(ui, originalhunks, match, operation=None):
225 """ Prompts the user to filter the originalhunks and return a list of
225 """ Prompts the user to filter the originalhunks and return a list of
226 selected hunks.
226 selected hunks.
227 *operation* is used for to build ui messages to indicate the user what
227 *operation* is used for to build ui messages to indicate the user what
228 kind of filtering they are doing: reverting, committing, shelving, etc.
228 kind of filtering they are doing: reverting, committing, shelving, etc.
229 (see patch.filterpatch).
229 (see patch.filterpatch).
230 """
230 """
231 usecurses = crecordmod.checkcurses(ui)
231 usecurses = crecordmod.checkcurses(ui)
232 testfile = ui.config('experimental', 'crecordtest')
232 testfile = ui.config('experimental', 'crecordtest')
233 oldwrite = setupwrapcolorwrite(ui)
233 oldwrite = setupwrapcolorwrite(ui)
234 try:
234 try:
235 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
235 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
236 testfile, match, operation)
236 testfile, match, operation)
237 finally:
237 finally:
238 ui.write = oldwrite
238 ui.write = oldwrite
239 return newchunks, newopts
239 return newchunks, newopts
240
240
241 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
241 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
242 filterfn, *pats, **opts):
242 filterfn, *pats, **opts):
243 opts = pycompat.byteskwargs(opts)
243 opts = pycompat.byteskwargs(opts)
244 if not ui.interactive():
244 if not ui.interactive():
245 if cmdsuggest:
245 if cmdsuggest:
246 msg = _('running non-interactively, use %s instead') % cmdsuggest
246 msg = _('running non-interactively, use %s instead') % cmdsuggest
247 else:
247 else:
248 msg = _('running non-interactively')
248 msg = _('running non-interactively')
249 raise error.Abort(msg)
249 raise error.Abort(msg)
250
250
251 # make sure username is set before going interactive
251 # make sure username is set before going interactive
252 if not opts.get('user'):
252 if not opts.get('user'):
253 ui.username() # raise exception, username not provided
253 ui.username() # raise exception, username not provided
254
254
255 def recordfunc(ui, repo, message, match, opts):
255 def recordfunc(ui, repo, message, match, opts):
256 """This is generic record driver.
256 """This is generic record driver.
257
257
258 Its job is to interactively filter local changes, and
258 Its job is to interactively filter local changes, and
259 accordingly prepare working directory into a state in which the
259 accordingly prepare working directory into a state in which the
260 job can be delegated to a non-interactive commit command such as
260 job can be delegated to a non-interactive commit command such as
261 'commit' or 'qrefresh'.
261 'commit' or 'qrefresh'.
262
262
263 After the actual job is done by non-interactive command, the
263 After the actual job is done by non-interactive command, the
264 working directory is restored to its original state.
264 working directory is restored to its original state.
265
265
266 In the end we'll record interesting changes, and everything else
266 In the end we'll record interesting changes, and everything else
267 will be left in place, so the user can continue working.
267 will be left in place, so the user can continue working.
268 """
268 """
269
269
270 checkunfinished(repo, commit=True)
270 checkunfinished(repo, commit=True)
271 wctx = repo[None]
271 wctx = repo[None]
272 merge = len(wctx.parents()) > 1
272 merge = len(wctx.parents()) > 1
273 if merge:
273 if merge:
274 raise error.Abort(_('cannot partially commit a merge '
274 raise error.Abort(_('cannot partially commit a merge '
275 '(use "hg commit" instead)'))
275 '(use "hg commit" instead)'))
276
276
277 def fail(f, msg):
277 def fail(f, msg):
278 raise error.Abort('%s: %s' % (f, msg))
278 raise error.Abort('%s: %s' % (f, msg))
279
279
280 force = opts.get('force')
280 force = opts.get('force')
281 if not force:
281 if not force:
282 vdirs = []
282 vdirs = []
283 match = matchmod.badmatch(match, fail)
283 match = matchmod.badmatch(match, fail)
284 match.explicitdir = vdirs.append
284 match.explicitdir = vdirs.append
285
285
286 status = repo.status(match=match)
286 status = repo.status(match=match)
287
287
288 overrides = {(b'ui', b'commitsubrepos'): True}
288 overrides = {(b'ui', b'commitsubrepos'): True}
289
289
290 with repo.ui.configoverride(overrides, b'record'):
290 with repo.ui.configoverride(overrides, b'record'):
291 # subrepoutil.precommit() modifies the status
291 # subrepoutil.precommit() modifies the status
292 tmpstatus = scmutil.status(copymod.copy(status[0]),
292 tmpstatus = scmutil.status(copymod.copy(status[0]),
293 copymod.copy(status[1]),
293 copymod.copy(status[1]),
294 copymod.copy(status[2]),
294 copymod.copy(status[2]),
295 copymod.copy(status[3]),
295 copymod.copy(status[3]),
296 copymod.copy(status[4]),
296 copymod.copy(status[4]),
297 copymod.copy(status[5]),
297 copymod.copy(status[5]),
298 copymod.copy(status[6]))
298 copymod.copy(status[6]))
299
299
300 # Force allows -X subrepo to skip the subrepo.
300 # Force allows -X subrepo to skip the subrepo.
301 subs, commitsubs, newstate = subrepoutil.precommit(
301 subs, commitsubs, newstate = subrepoutil.precommit(
302 repo.ui, wctx, tmpstatus, match, force=True)
302 repo.ui, wctx, tmpstatus, match, force=True)
303 for s in subs:
303 for s in subs:
304 if s in commitsubs:
304 if s in commitsubs:
305 dirtyreason = wctx.sub(s).dirtyreason(True)
305 dirtyreason = wctx.sub(s).dirtyreason(True)
306 raise error.Abort(dirtyreason)
306 raise error.Abort(dirtyreason)
307
307
308 if not force:
308 if not force:
309 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
309 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
310 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
310 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
311 section='commands',
311 section='commands',
312 configprefix='commit.interactive.')
312 configprefix='commit.interactive.')
313 diffopts.nodates = True
313 diffopts.nodates = True
314 diffopts.git = True
314 diffopts.git = True
315 diffopts.showfunc = True
315 diffopts.showfunc = True
316 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
316 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
317 originalchunks = patch.parsepatch(originaldiff)
317 originalchunks = patch.parsepatch(originaldiff)
318 match = scmutil.match(repo[None], pats)
318 match = scmutil.match(repo[None], pats)
319
319
320 # 1. filter patch, since we are intending to apply subset of it
320 # 1. filter patch, since we are intending to apply subset of it
321 try:
321 try:
322 chunks, newopts = filterfn(ui, originalchunks, match)
322 chunks, newopts = filterfn(ui, originalchunks, match)
323 except error.PatchError as err:
323 except error.PatchError as err:
324 raise error.Abort(_('error parsing patch: %s') % err)
324 raise error.Abort(_('error parsing patch: %s') % err)
325 opts.update(newopts)
325 opts.update(newopts)
326
326
327 # We need to keep a backup of files that have been newly added and
327 # We need to keep a backup of files that have been newly added and
328 # modified during the recording process because there is a previous
328 # modified during the recording process because there is a previous
329 # version without the edit in the workdir
329 # version without the edit in the workdir
330 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
330 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
331 contenders = set()
331 contenders = set()
332 for h in chunks:
332 for h in chunks:
333 try:
333 try:
334 contenders.update(set(h.files()))
334 contenders.update(set(h.files()))
335 except AttributeError:
335 except AttributeError:
336 pass
336 pass
337
337
338 changed = status.modified + status.added + status.removed
338 changed = status.modified + status.added + status.removed
339 newfiles = [f for f in changed if f in contenders]
339 newfiles = [f for f in changed if f in contenders]
340 if not newfiles:
340 if not newfiles:
341 ui.status(_('no changes to record\n'))
341 ui.status(_('no changes to record\n'))
342 return 0
342 return 0
343
343
344 modified = set(status.modified)
344 modified = set(status.modified)
345
345
346 # 2. backup changed files, so we can restore them in the end
346 # 2. backup changed files, so we can restore them in the end
347
347
348 if backupall:
348 if backupall:
349 tobackup = changed
349 tobackup = changed
350 else:
350 else:
351 tobackup = [f for f in newfiles if f in modified or f in
351 tobackup = [f for f in newfiles if f in modified or f in
352 newlyaddedandmodifiedfiles]
352 newlyaddedandmodifiedfiles]
353 backups = {}
353 backups = {}
354 if tobackup:
354 if tobackup:
355 backupdir = repo.vfs.join('record-backups')
355 backupdir = repo.vfs.join('record-backups')
356 try:
356 try:
357 os.mkdir(backupdir)
357 os.mkdir(backupdir)
358 except OSError as err:
358 except OSError as err:
359 if err.errno != errno.EEXIST:
359 if err.errno != errno.EEXIST:
360 raise
360 raise
361 try:
361 try:
362 # backup continues
362 # backup continues
363 for f in tobackup:
363 for f in tobackup:
364 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
364 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
365 dir=backupdir)
365 dir=backupdir)
366 os.close(fd)
366 os.close(fd)
367 ui.debug('backup %r as %r\n' % (f, tmpname))
367 ui.debug('backup %r as %r\n' % (f, tmpname))
368 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
368 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
369 backups[f] = tmpname
369 backups[f] = tmpname
370
370
371 fp = stringio()
371 fp = stringio()
372 for c in chunks:
372 for c in chunks:
373 fname = c.filename()
373 fname = c.filename()
374 if fname in backups:
374 if fname in backups:
375 c.write(fp)
375 c.write(fp)
376 dopatch = fp.tell()
376 dopatch = fp.tell()
377 fp.seek(0)
377 fp.seek(0)
378
378
379 # 2.5 optionally review / modify patch in text editor
379 # 2.5 optionally review / modify patch in text editor
380 if opts.get('review', False):
380 if opts.get('review', False):
381 patchtext = (crecordmod.diffhelptext
381 patchtext = (crecordmod.diffhelptext
382 + crecordmod.patchhelptext
382 + crecordmod.patchhelptext
383 + fp.read())
383 + fp.read())
384 reviewedpatch = ui.edit(patchtext, "",
384 reviewedpatch = ui.edit(patchtext, "",
385 action="diff",
385 action="diff",
386 repopath=repo.path)
386 repopath=repo.path)
387 fp.truncate(0)
387 fp.truncate(0)
388 fp.write(reviewedpatch)
388 fp.write(reviewedpatch)
389 fp.seek(0)
389 fp.seek(0)
390
390
391 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
391 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
392 # 3a. apply filtered patch to clean repo (clean)
392 # 3a. apply filtered patch to clean repo (clean)
393 if backups:
393 if backups:
394 # Equivalent to hg.revert
394 # Equivalent to hg.revert
395 m = scmutil.matchfiles(repo, backups.keys())
395 m = scmutil.matchfiles(repo, backups.keys())
396 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
396 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
397 force=True, matcher=m)
397 force=True, matcher=m)
398
398
399 # 3b. (apply)
399 # 3b. (apply)
400 if dopatch:
400 if dopatch:
401 try:
401 try:
402 ui.debug('applying patch\n')
402 ui.debug('applying patch\n')
403 ui.debug(fp.getvalue())
403 ui.debug(fp.getvalue())
404 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
404 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
405 except error.PatchError as err:
405 except error.PatchError as err:
406 raise error.Abort(pycompat.bytestr(err))
406 raise error.Abort(pycompat.bytestr(err))
407 del fp
407 del fp
408
408
409 # 4. We prepared working directory according to filtered
409 # 4. We prepared working directory according to filtered
410 # patch. Now is the time to delegate the job to
410 # patch. Now is the time to delegate the job to
411 # commit/qrefresh or the like!
411 # commit/qrefresh or the like!
412
412
413 # Make all of the pathnames absolute.
413 # Make all of the pathnames absolute.
414 newfiles = [repo.wjoin(nf) for nf in newfiles]
414 newfiles = [repo.wjoin(nf) for nf in newfiles]
415 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
415 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
416 finally:
416 finally:
417 # 5. finally restore backed-up files
417 # 5. finally restore backed-up files
418 try:
418 try:
419 dirstate = repo.dirstate
419 dirstate = repo.dirstate
420 for realname, tmpname in backups.iteritems():
420 for realname, tmpname in backups.iteritems():
421 ui.debug('restoring %r to %r\n' % (tmpname, realname))
421 ui.debug('restoring %r to %r\n' % (tmpname, realname))
422
422
423 if dirstate[realname] == 'n':
423 if dirstate[realname] == 'n':
424 # without normallookup, restoring timestamp
424 # without normallookup, restoring timestamp
425 # may cause partially committed files
425 # may cause partially committed files
426 # to be treated as unmodified
426 # to be treated as unmodified
427 dirstate.normallookup(realname)
427 dirstate.normallookup(realname)
428
428
429 # copystat=True here and above are a hack to trick any
429 # copystat=True here and above are a hack to trick any
430 # editors that have f open that we haven't modified them.
430 # editors that have f open that we haven't modified them.
431 #
431 #
432 # Also note that this racy as an editor could notice the
432 # Also note that this racy as an editor could notice the
433 # file's mtime before we've finished writing it.
433 # file's mtime before we've finished writing it.
434 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
434 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
435 os.unlink(tmpname)
435 os.unlink(tmpname)
436 if tobackup:
436 if tobackup:
437 os.rmdir(backupdir)
437 os.rmdir(backupdir)
438 except OSError:
438 except OSError:
439 pass
439 pass
440
440
441 def recordinwlock(ui, repo, message, match, opts):
441 def recordinwlock(ui, repo, message, match, opts):
442 with repo.wlock():
442 with repo.wlock():
443 return recordfunc(ui, repo, message, match, opts)
443 return recordfunc(ui, repo, message, match, opts)
444
444
445 return commit(ui, repo, recordinwlock, pats, opts)
445 return commit(ui, repo, recordinwlock, pats, opts)
446
446
447 class dirnode(object):
447 class dirnode(object):
448 """
448 """
449 Represent a directory in user working copy with information required for
449 Represent a directory in user working copy with information required for
450 the purpose of tersing its status.
450 the purpose of tersing its status.
451
451
452 path is the path to the directory, without a trailing '/'
452 path is the path to the directory, without a trailing '/'
453
453
454 statuses is a set of statuses of all files in this directory (this includes
454 statuses is a set of statuses of all files in this directory (this includes
455 all the files in all the subdirectories too)
455 all the files in all the subdirectories too)
456
456
457 files is a list of files which are direct child of this directory
457 files is a list of files which are direct child of this directory
458
458
459 subdirs is a dictionary of sub-directory name as the key and it's own
459 subdirs is a dictionary of sub-directory name as the key and it's own
460 dirnode object as the value
460 dirnode object as the value
461 """
461 """
462
462
463 def __init__(self, dirpath):
463 def __init__(self, dirpath):
464 self.path = dirpath
464 self.path = dirpath
465 self.statuses = set()
465 self.statuses = set()
466 self.files = []
466 self.files = []
467 self.subdirs = {}
467 self.subdirs = {}
468
468
469 def _addfileindir(self, filename, status):
469 def _addfileindir(self, filename, status):
470 """Add a file in this directory as a direct child."""
470 """Add a file in this directory as a direct child."""
471 self.files.append((filename, status))
471 self.files.append((filename, status))
472
472
473 def addfile(self, filename, status):
473 def addfile(self, filename, status):
474 """
474 """
475 Add a file to this directory or to its direct parent directory.
475 Add a file to this directory or to its direct parent directory.
476
476
477 If the file is not direct child of this directory, we traverse to the
477 If the file is not direct child of this directory, we traverse to the
478 directory of which this file is a direct child of and add the file
478 directory of which this file is a direct child of and add the file
479 there.
479 there.
480 """
480 """
481
481
482 # the filename contains a path separator, it means it's not the direct
482 # the filename contains a path separator, it means it's not the direct
483 # child of this directory
483 # child of this directory
484 if '/' in filename:
484 if '/' in filename:
485 subdir, filep = filename.split('/', 1)
485 subdir, filep = filename.split('/', 1)
486
486
487 # does the dirnode object for subdir exists
487 # does the dirnode object for subdir exists
488 if subdir not in self.subdirs:
488 if subdir not in self.subdirs:
489 subdirpath = pathutil.join(self.path, subdir)
489 subdirpath = pathutil.join(self.path, subdir)
490 self.subdirs[subdir] = dirnode(subdirpath)
490 self.subdirs[subdir] = dirnode(subdirpath)
491
491
492 # try adding the file in subdir
492 # try adding the file in subdir
493 self.subdirs[subdir].addfile(filep, status)
493 self.subdirs[subdir].addfile(filep, status)
494
494
495 else:
495 else:
496 self._addfileindir(filename, status)
496 self._addfileindir(filename, status)
497
497
498 if status not in self.statuses:
498 if status not in self.statuses:
499 self.statuses.add(status)
499 self.statuses.add(status)
500
500
501 def iterfilepaths(self):
501 def iterfilepaths(self):
502 """Yield (status, path) for files directly under this directory."""
502 """Yield (status, path) for files directly under this directory."""
503 for f, st in self.files:
503 for f, st in self.files:
504 yield st, pathutil.join(self.path, f)
504 yield st, pathutil.join(self.path, f)
505
505
506 def tersewalk(self, terseargs):
506 def tersewalk(self, terseargs):
507 """
507 """
508 Yield (status, path) obtained by processing the status of this
508 Yield (status, path) obtained by processing the status of this
509 dirnode.
509 dirnode.
510
510
511 terseargs is the string of arguments passed by the user with `--terse`
511 terseargs is the string of arguments passed by the user with `--terse`
512 flag.
512 flag.
513
513
514 Following are the cases which can happen:
514 Following are the cases which can happen:
515
515
516 1) All the files in the directory (including all the files in its
516 1) All the files in the directory (including all the files in its
517 subdirectories) share the same status and the user has asked us to terse
517 subdirectories) share the same status and the user has asked us to terse
518 that status. -> yield (status, dirpath). dirpath will end in '/'.
518 that status. -> yield (status, dirpath). dirpath will end in '/'.
519
519
520 2) Otherwise, we do following:
520 2) Otherwise, we do following:
521
521
522 a) Yield (status, filepath) for all the files which are in this
522 a) Yield (status, filepath) for all the files which are in this
523 directory (only the ones in this directory, not the subdirs)
523 directory (only the ones in this directory, not the subdirs)
524
524
525 b) Recurse the function on all the subdirectories of this
525 b) Recurse the function on all the subdirectories of this
526 directory
526 directory
527 """
527 """
528
528
529 if len(self.statuses) == 1:
529 if len(self.statuses) == 1:
530 onlyst = self.statuses.pop()
530 onlyst = self.statuses.pop()
531
531
532 # Making sure we terse only when the status abbreviation is
532 # Making sure we terse only when the status abbreviation is
533 # passed as terse argument
533 # passed as terse argument
534 if onlyst in terseargs:
534 if onlyst in terseargs:
535 yield onlyst, self.path + '/'
535 yield onlyst, self.path + '/'
536 return
536 return
537
537
538 # add the files to status list
538 # add the files to status list
539 for st, fpath in self.iterfilepaths():
539 for st, fpath in self.iterfilepaths():
540 yield st, fpath
540 yield st, fpath
541
541
542 #recurse on the subdirs
542 #recurse on the subdirs
543 for dirobj in self.subdirs.values():
543 for dirobj in self.subdirs.values():
544 for st, fpath in dirobj.tersewalk(terseargs):
544 for st, fpath in dirobj.tersewalk(terseargs):
545 yield st, fpath
545 yield st, fpath
546
546
547 def tersedir(statuslist, terseargs):
547 def tersedir(statuslist, terseargs):
548 """
548 """
549 Terse the status if all the files in a directory shares the same status.
549 Terse the status if all the files in a directory shares the same status.
550
550
551 statuslist is scmutil.status() object which contains a list of files for
551 statuslist is scmutil.status() object which contains a list of files for
552 each status.
552 each status.
553 terseargs is string which is passed by the user as the argument to `--terse`
553 terseargs is string which is passed by the user as the argument to `--terse`
554 flag.
554 flag.
555
555
556 The function makes a tree of objects of dirnode class, and at each node it
556 The function makes a tree of objects of dirnode class, and at each node it
557 stores the information required to know whether we can terse a certain
557 stores the information required to know whether we can terse a certain
558 directory or not.
558 directory or not.
559 """
559 """
560 # the order matters here as that is used to produce final list
560 # the order matters here as that is used to produce final list
561 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
561 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
562
562
563 # checking the argument validity
563 # checking the argument validity
564 for s in pycompat.bytestr(terseargs):
564 for s in pycompat.bytestr(terseargs):
565 if s not in allst:
565 if s not in allst:
566 raise error.Abort(_("'%s' not recognized") % s)
566 raise error.Abort(_("'%s' not recognized") % s)
567
567
568 # creating a dirnode object for the root of the repo
568 # creating a dirnode object for the root of the repo
569 rootobj = dirnode('')
569 rootobj = dirnode('')
570 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
570 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
571 'ignored', 'removed')
571 'ignored', 'removed')
572
572
573 tersedict = {}
573 tersedict = {}
574 for attrname in pstatus:
574 for attrname in pstatus:
575 statuschar = attrname[0:1]
575 statuschar = attrname[0:1]
576 for f in getattr(statuslist, attrname):
576 for f in getattr(statuslist, attrname):
577 rootobj.addfile(f, statuschar)
577 rootobj.addfile(f, statuschar)
578 tersedict[statuschar] = []
578 tersedict[statuschar] = []
579
579
580 # we won't be tersing the root dir, so add files in it
580 # we won't be tersing the root dir, so add files in it
581 for st, fpath in rootobj.iterfilepaths():
581 for st, fpath in rootobj.iterfilepaths():
582 tersedict[st].append(fpath)
582 tersedict[st].append(fpath)
583
583
584 # process each sub-directory and build tersedict
584 # process each sub-directory and build tersedict
585 for subdir in rootobj.subdirs.values():
585 for subdir in rootobj.subdirs.values():
586 for st, f in subdir.tersewalk(terseargs):
586 for st, f in subdir.tersewalk(terseargs):
587 tersedict[st].append(f)
587 tersedict[st].append(f)
588
588
589 tersedlist = []
589 tersedlist = []
590 for st in allst:
590 for st in allst:
591 tersedict[st].sort()
591 tersedict[st].sort()
592 tersedlist.append(tersedict[st])
592 tersedlist.append(tersedict[st])
593
593
594 return tersedlist
594 return tersedlist
595
595
596 def _commentlines(raw):
596 def _commentlines(raw):
597 '''Surround lineswith a comment char and a new line'''
597 '''Surround lineswith a comment char and a new line'''
598 lines = raw.splitlines()
598 lines = raw.splitlines()
599 commentedlines = ['# %s' % line for line in lines]
599 commentedlines = ['# %s' % line for line in lines]
600 return '\n'.join(commentedlines) + '\n'
600 return '\n'.join(commentedlines) + '\n'
601
601
602 def _conflictsmsg(repo):
602 def _conflictsmsg(repo):
603 mergestate = mergemod.mergestate.read(repo)
603 mergestate = mergemod.mergestate.read(repo)
604 if not mergestate.active():
604 if not mergestate.active():
605 return
605 return
606
606
607 m = scmutil.match(repo[None])
607 m = scmutil.match(repo[None])
608 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
608 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
609 if unresolvedlist:
609 if unresolvedlist:
610 mergeliststr = '\n'.join(
610 mergeliststr = '\n'.join(
611 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
611 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
612 for path in sorted(unresolvedlist)])
612 for path in sorted(unresolvedlist)])
613 msg = _('''Unresolved merge conflicts:
613 msg = _('''Unresolved merge conflicts:
614
614
615 %s
615 %s
616
616
617 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
617 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
618 else:
618 else:
619 msg = _('No unresolved merge conflicts.')
619 msg = _('No unresolved merge conflicts.')
620
620
621 return _commentlines(msg)
621 return _commentlines(msg)
622
622
623 def morestatus(repo, fm):
623 def morestatus(repo, fm):
624 statetuple = statemod.getrepostate(repo)
624 statetuple = statemod.getrepostate(repo)
625 label = 'status.morestatus'
625 label = 'status.morestatus'
626 if statetuple:
626 if statetuple:
627 state, helpfulmsg = statetuple
627 state, helpfulmsg = statetuple
628 statemsg = _('The repository is in an unfinished *%s* state.') % state
628 statemsg = _('The repository is in an unfinished *%s* state.') % state
629 fm.plain('%s\n' % _commentlines(statemsg), label=label)
629 fm.plain('%s\n' % _commentlines(statemsg), label=label)
630 conmsg = _conflictsmsg(repo)
630 conmsg = _conflictsmsg(repo)
631 if conmsg:
631 if conmsg:
632 fm.plain('%s\n' % conmsg, label=label)
632 fm.plain('%s\n' % conmsg, label=label)
633 if helpfulmsg:
633 if helpfulmsg:
634 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label)
634 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label)
635
635
636 def findpossible(cmd, table, strict=False):
636 def findpossible(cmd, table, strict=False):
637 """
637 """
638 Return cmd -> (aliases, command table entry)
638 Return cmd -> (aliases, command table entry)
639 for each matching command.
639 for each matching command.
640 Return debug commands (or their aliases) only if no normal command matches.
640 Return debug commands (or their aliases) only if no normal command matches.
641 """
641 """
642 choice = {}
642 choice = {}
643 debugchoice = {}
643 debugchoice = {}
644
644
645 if cmd in table:
645 if cmd in table:
646 # short-circuit exact matches, "log" alias beats "log|history"
646 # short-circuit exact matches, "log" alias beats "log|history"
647 keys = [cmd]
647 keys = [cmd]
648 else:
648 else:
649 keys = table.keys()
649 keys = table.keys()
650
650
651 allcmds = []
651 allcmds = []
652 for e in keys:
652 for e in keys:
653 aliases = parsealiases(e)
653 aliases = parsealiases(e)
654 allcmds.extend(aliases)
654 allcmds.extend(aliases)
655 found = None
655 found = None
656 if cmd in aliases:
656 if cmd in aliases:
657 found = cmd
657 found = cmd
658 elif not strict:
658 elif not strict:
659 for a in aliases:
659 for a in aliases:
660 if a.startswith(cmd):
660 if a.startswith(cmd):
661 found = a
661 found = a
662 break
662 break
663 if found is not None:
663 if found is not None:
664 if aliases[0].startswith("debug") or found.startswith("debug"):
664 if aliases[0].startswith("debug") or found.startswith("debug"):
665 debugchoice[found] = (aliases, table[e])
665 debugchoice[found] = (aliases, table[e])
666 else:
666 else:
667 choice[found] = (aliases, table[e])
667 choice[found] = (aliases, table[e])
668
668
669 if not choice and debugchoice:
669 if not choice and debugchoice:
670 choice = debugchoice
670 choice = debugchoice
671
671
672 return choice, allcmds
672 return choice, allcmds
673
673
674 def findcmd(cmd, table, strict=True):
674 def findcmd(cmd, table, strict=True):
675 """Return (aliases, command table entry) for command string."""
675 """Return (aliases, command table entry) for command string."""
676 choice, allcmds = findpossible(cmd, table, strict)
676 choice, allcmds = findpossible(cmd, table, strict)
677
677
678 if cmd in choice:
678 if cmd in choice:
679 return choice[cmd]
679 return choice[cmd]
680
680
681 if len(choice) > 1:
681 if len(choice) > 1:
682 clist = sorted(choice)
682 clist = sorted(choice)
683 raise error.AmbiguousCommand(cmd, clist)
683 raise error.AmbiguousCommand(cmd, clist)
684
684
685 if choice:
685 if choice:
686 return list(choice.values())[0]
686 return list(choice.values())[0]
687
687
688 raise error.UnknownCommand(cmd, allcmds)
688 raise error.UnknownCommand(cmd, allcmds)
689
689
690 def changebranch(ui, repo, revs, label):
690 def changebranch(ui, repo, revs, label):
691 """ Change the branch name of given revs to label """
691 """ Change the branch name of given revs to label """
692
692
693 with repo.wlock(), repo.lock(), repo.transaction('branches'):
693 with repo.wlock(), repo.lock(), repo.transaction('branches'):
694 # abort in case of uncommitted merge or dirty wdir
694 # abort in case of uncommitted merge or dirty wdir
695 bailifchanged(repo)
695 bailifchanged(repo)
696 revs = scmutil.revrange(repo, revs)
696 revs = scmutil.revrange(repo, revs)
697 if not revs:
697 if not revs:
698 raise error.Abort("empty revision set")
698 raise error.Abort("empty revision set")
699 roots = repo.revs('roots(%ld)', revs)
699 roots = repo.revs('roots(%ld)', revs)
700 if len(roots) > 1:
700 if len(roots) > 1:
701 raise error.Abort(_("cannot change branch of non-linear revisions"))
701 raise error.Abort(_("cannot change branch of non-linear revisions"))
702 rewriteutil.precheck(repo, revs, 'change branch of')
702 rewriteutil.precheck(repo, revs, 'change branch of')
703
703
704 root = repo[roots.first()]
704 root = repo[roots.first()]
705 rpb = {parent.branch() for parent in root.parents()}
705 rpb = {parent.branch() for parent in root.parents()}
706 if label not in rpb and label in repo.branchmap():
706 if label not in rpb and label in repo.branchmap():
707 raise error.Abort(_("a branch of the same name already exists"))
707 raise error.Abort(_("a branch of the same name already exists"))
708
708
709 if repo.revs('obsolete() and %ld', revs):
709 if repo.revs('obsolete() and %ld', revs):
710 raise error.Abort(_("cannot change branch of a obsolete changeset"))
710 raise error.Abort(_("cannot change branch of a obsolete changeset"))
711
711
712 # make sure only topological heads
712 # make sure only topological heads
713 if repo.revs('heads(%ld) - head()', revs):
713 if repo.revs('heads(%ld) - head()', revs):
714 raise error.Abort(_("cannot change branch in middle of a stack"))
714 raise error.Abort(_("cannot change branch in middle of a stack"))
715
715
716 replacements = {}
716 replacements = {}
717 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
717 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
718 # mercurial.subrepo -> mercurial.cmdutil
718 # mercurial.subrepo -> mercurial.cmdutil
719 from . import context
719 from . import context
720 for rev in revs:
720 for rev in revs:
721 ctx = repo[rev]
721 ctx = repo[rev]
722 oldbranch = ctx.branch()
722 oldbranch = ctx.branch()
723 # check if ctx has same branch
723 # check if ctx has same branch
724 if oldbranch == label:
724 if oldbranch == label:
725 continue
725 continue
726
726
727 def filectxfn(repo, newctx, path):
727 def filectxfn(repo, newctx, path):
728 try:
728 try:
729 return ctx[path]
729 return ctx[path]
730 except error.ManifestLookupError:
730 except error.ManifestLookupError:
731 return None
731 return None
732
732
733 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
733 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
734 % (hex(ctx.node()), oldbranch, label))
734 % (hex(ctx.node()), oldbranch, label))
735 extra = ctx.extra()
735 extra = ctx.extra()
736 extra['branch_change'] = hex(ctx.node())
736 extra['branch_change'] = hex(ctx.node())
737 # While changing branch of set of linear commits, make sure that
737 # While changing branch of set of linear commits, make sure that
738 # we base our commits on new parent rather than old parent which
738 # we base our commits on new parent rather than old parent which
739 # was obsoleted while changing the branch
739 # was obsoleted while changing the branch
740 p1 = ctx.p1().node()
740 p1 = ctx.p1().node()
741 p2 = ctx.p2().node()
741 p2 = ctx.p2().node()
742 if p1 in replacements:
742 if p1 in replacements:
743 p1 = replacements[p1][0]
743 p1 = replacements[p1][0]
744 if p2 in replacements:
744 if p2 in replacements:
745 p2 = replacements[p2][0]
745 p2 = replacements[p2][0]
746
746
747 mc = context.memctx(repo, (p1, p2),
747 mc = context.memctx(repo, (p1, p2),
748 ctx.description(),
748 ctx.description(),
749 ctx.files(),
749 ctx.files(),
750 filectxfn,
750 filectxfn,
751 user=ctx.user(),
751 user=ctx.user(),
752 date=ctx.date(),
752 date=ctx.date(),
753 extra=extra,
753 extra=extra,
754 branch=label)
754 branch=label)
755
755
756 newnode = repo.commitctx(mc)
756 newnode = repo.commitctx(mc)
757 replacements[ctx.node()] = (newnode,)
757 replacements[ctx.node()] = (newnode,)
758 ui.debug('new node id is %s\n' % hex(newnode))
758 ui.debug('new node id is %s\n' % hex(newnode))
759
759
760 # create obsmarkers and move bookmarks
760 # create obsmarkers and move bookmarks
761 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
761 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
762
762
763 # move the working copy too
763 # move the working copy too
764 wctx = repo[None]
764 wctx = repo[None]
765 # in-progress merge is a bit too complex for now.
765 # in-progress merge is a bit too complex for now.
766 if len(wctx.parents()) == 1:
766 if len(wctx.parents()) == 1:
767 newid = replacements.get(wctx.p1().node())
767 newid = replacements.get(wctx.p1().node())
768 if newid is not None:
768 if newid is not None:
769 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
769 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
770 # mercurial.cmdutil
770 # mercurial.cmdutil
771 from . import hg
771 from . import hg
772 hg.update(repo, newid[0], quietempty=True)
772 hg.update(repo, newid[0], quietempty=True)
773
773
774 ui.status(_("changed branch on %d changesets\n") % len(replacements))
774 ui.status(_("changed branch on %d changesets\n") % len(replacements))
775
775
776 def findrepo(p):
776 def findrepo(p):
777 while not os.path.isdir(os.path.join(p, ".hg")):
777 while not os.path.isdir(os.path.join(p, ".hg")):
778 oldp, p = p, os.path.dirname(p)
778 oldp, p = p, os.path.dirname(p)
779 if p == oldp:
779 if p == oldp:
780 return None
780 return None
781
781
782 return p
782 return p
783
783
784 def bailifchanged(repo, merge=True, hint=None):
784 def bailifchanged(repo, merge=True, hint=None):
785 """ enforce the precondition that working directory must be clean.
785 """ enforce the precondition that working directory must be clean.
786
786
787 'merge' can be set to false if a pending uncommitted merge should be
787 'merge' can be set to false if a pending uncommitted merge should be
788 ignored (such as when 'update --check' runs).
788 ignored (such as when 'update --check' runs).
789
789
790 'hint' is the usual hint given to Abort exception.
790 'hint' is the usual hint given to Abort exception.
791 """
791 """
792
792
793 if merge and repo.dirstate.p2() != nullid:
793 if merge and repo.dirstate.p2() != nullid:
794 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
794 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
795 modified, added, removed, deleted = repo.status()[:4]
795 modified, added, removed, deleted = repo.status()[:4]
796 if modified or added or removed or deleted:
796 if modified or added or removed or deleted:
797 raise error.Abort(_('uncommitted changes'), hint=hint)
797 raise error.Abort(_('uncommitted changes'), hint=hint)
798 ctx = repo[None]
798 ctx = repo[None]
799 for s in sorted(ctx.substate):
799 for s in sorted(ctx.substate):
800 ctx.sub(s).bailifchanged(hint=hint)
800 ctx.sub(s).bailifchanged(hint=hint)
801
801
802 def logmessage(ui, opts):
802 def logmessage(ui, opts):
803 """ get the log message according to -m and -l option """
803 """ get the log message according to -m and -l option """
804 message = opts.get('message')
804 message = opts.get('message')
805 logfile = opts.get('logfile')
805 logfile = opts.get('logfile')
806
806
807 if message and logfile:
807 if message and logfile:
808 raise error.Abort(_('options --message and --logfile are mutually '
808 raise error.Abort(_('options --message and --logfile are mutually '
809 'exclusive'))
809 'exclusive'))
810 if not message and logfile:
810 if not message and logfile:
811 try:
811 try:
812 if isstdiofilename(logfile):
812 if isstdiofilename(logfile):
813 message = ui.fin.read()
813 message = ui.fin.read()
814 else:
814 else:
815 message = '\n'.join(util.readfile(logfile).splitlines())
815 message = '\n'.join(util.readfile(logfile).splitlines())
816 except IOError as inst:
816 except IOError as inst:
817 raise error.Abort(_("can't read commit message '%s': %s") %
817 raise error.Abort(_("can't read commit message '%s': %s") %
818 (logfile, encoding.strtolocal(inst.strerror)))
818 (logfile, encoding.strtolocal(inst.strerror)))
819 return message
819 return message
820
820
821 def mergeeditform(ctxorbool, baseformname):
821 def mergeeditform(ctxorbool, baseformname):
822 """return appropriate editform name (referencing a committemplate)
822 """return appropriate editform name (referencing a committemplate)
823
823
824 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
824 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
825 merging is committed.
825 merging is committed.
826
826
827 This returns baseformname with '.merge' appended if it is a merge,
827 This returns baseformname with '.merge' appended if it is a merge,
828 otherwise '.normal' is appended.
828 otherwise '.normal' is appended.
829 """
829 """
830 if isinstance(ctxorbool, bool):
830 if isinstance(ctxorbool, bool):
831 if ctxorbool:
831 if ctxorbool:
832 return baseformname + ".merge"
832 return baseformname + ".merge"
833 elif len(ctxorbool.parents()) > 1:
833 elif len(ctxorbool.parents()) > 1:
834 return baseformname + ".merge"
834 return baseformname + ".merge"
835
835
836 return baseformname + ".normal"
836 return baseformname + ".normal"
837
837
838 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
838 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
839 editform='', **opts):
839 editform='', **opts):
840 """get appropriate commit message editor according to '--edit' option
840 """get appropriate commit message editor according to '--edit' option
841
841
842 'finishdesc' is a function to be called with edited commit message
842 'finishdesc' is a function to be called with edited commit message
843 (= 'description' of the new changeset) just after editing, but
843 (= 'description' of the new changeset) just after editing, but
844 before checking empty-ness. It should return actual text to be
844 before checking empty-ness. It should return actual text to be
845 stored into history. This allows to change description before
845 stored into history. This allows to change description before
846 storing.
846 storing.
847
847
848 'extramsg' is a extra message to be shown in the editor instead of
848 'extramsg' is a extra message to be shown in the editor instead of
849 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
849 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
850 is automatically added.
850 is automatically added.
851
851
852 'editform' is a dot-separated list of names, to distinguish
852 'editform' is a dot-separated list of names, to distinguish
853 the purpose of commit text editing.
853 the purpose of commit text editing.
854
854
855 'getcommiteditor' returns 'commitforceeditor' regardless of
855 'getcommiteditor' returns 'commitforceeditor' regardless of
856 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
856 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
857 they are specific for usage in MQ.
857 they are specific for usage in MQ.
858 """
858 """
859 if edit or finishdesc or extramsg:
859 if edit or finishdesc or extramsg:
860 return lambda r, c, s: commitforceeditor(r, c, s,
860 return lambda r, c, s: commitforceeditor(r, c, s,
861 finishdesc=finishdesc,
861 finishdesc=finishdesc,
862 extramsg=extramsg,
862 extramsg=extramsg,
863 editform=editform)
863 editform=editform)
864 elif editform:
864 elif editform:
865 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
865 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
866 else:
866 else:
867 return commiteditor
867 return commiteditor
868
868
869 def _escapecommandtemplate(tmpl):
869 def _escapecommandtemplate(tmpl):
870 parts = []
870 parts = []
871 for typ, start, end in templater.scantemplate(tmpl, raw=True):
871 for typ, start, end in templater.scantemplate(tmpl, raw=True):
872 if typ == b'string':
872 if typ == b'string':
873 parts.append(stringutil.escapestr(tmpl[start:end]))
873 parts.append(stringutil.escapestr(tmpl[start:end]))
874 else:
874 else:
875 parts.append(tmpl[start:end])
875 parts.append(tmpl[start:end])
876 return b''.join(parts)
876 return b''.join(parts)
877
877
878 def rendercommandtemplate(ui, tmpl, props):
878 def rendercommandtemplate(ui, tmpl, props):
879 r"""Expand a literal template 'tmpl' in a way suitable for command line
879 r"""Expand a literal template 'tmpl' in a way suitable for command line
880
880
881 '\' in outermost string is not taken as an escape character because it
881 '\' in outermost string is not taken as an escape character because it
882 is a directory separator on Windows.
882 is a directory separator on Windows.
883
883
884 >>> from . import ui as uimod
884 >>> from . import ui as uimod
885 >>> ui = uimod.ui()
885 >>> ui = uimod.ui()
886 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
886 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
887 'c:\\foo'
887 'c:\\foo'
888 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
888 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
889 'c:{path}'
889 'c:{path}'
890 """
890 """
891 if not tmpl:
891 if not tmpl:
892 return tmpl
892 return tmpl
893 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
893 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
894 return t.renderdefault(props)
894 return t.renderdefault(props)
895
895
896 def rendertemplate(ctx, tmpl, props=None):
896 def rendertemplate(ctx, tmpl, props=None):
897 """Expand a literal template 'tmpl' byte-string against one changeset
897 """Expand a literal template 'tmpl' byte-string against one changeset
898
898
899 Each props item must be a stringify-able value or a callable returning
899 Each props item must be a stringify-able value or a callable returning
900 such value, i.e. no bare list nor dict should be passed.
900 such value, i.e. no bare list nor dict should be passed.
901 """
901 """
902 repo = ctx.repo()
902 repo = ctx.repo()
903 tres = formatter.templateresources(repo.ui, repo)
903 tres = formatter.templateresources(repo.ui, repo)
904 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
904 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
905 resources=tres)
905 resources=tres)
906 mapping = {'ctx': ctx}
906 mapping = {'ctx': ctx}
907 if props:
907 if props:
908 mapping.update(props)
908 mapping.update(props)
909 return t.renderdefault(mapping)
909 return t.renderdefault(mapping)
910
910
911 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
911 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
912 r"""Convert old-style filename format string to template string
912 r"""Convert old-style filename format string to template string
913
913
914 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
914 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
915 'foo-{reporoot|basename}-{seqno}.patch'
915 'foo-{reporoot|basename}-{seqno}.patch'
916 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
916 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
917 '{rev}{tags % "{tag}"}{node}'
917 '{rev}{tags % "{tag}"}{node}'
918
918
919 '\' in outermost strings has to be escaped because it is a directory
919 '\' in outermost strings has to be escaped because it is a directory
920 separator on Windows:
920 separator on Windows:
921
921
922 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
922 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
923 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
923 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
924 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
924 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
925 '\\\\\\\\foo\\\\bar.patch'
925 '\\\\\\\\foo\\\\bar.patch'
926 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
926 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
927 '\\\\{tags % "{tag}"}'
927 '\\\\{tags % "{tag}"}'
928
928
929 but inner strings follow the template rules (i.e. '\' is taken as an
929 but inner strings follow the template rules (i.e. '\' is taken as an
930 escape character):
930 escape character):
931
931
932 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
932 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
933 '{"c:\\tmp"}'
933 '{"c:\\tmp"}'
934 """
934 """
935 expander = {
935 expander = {
936 b'H': b'{node}',
936 b'H': b'{node}',
937 b'R': b'{rev}',
937 b'R': b'{rev}',
938 b'h': b'{node|short}',
938 b'h': b'{node|short}',
939 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
939 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
940 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
940 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
941 b'%': b'%',
941 b'%': b'%',
942 b'b': b'{reporoot|basename}',
942 b'b': b'{reporoot|basename}',
943 }
943 }
944 if total is not None:
944 if total is not None:
945 expander[b'N'] = b'{total}'
945 expander[b'N'] = b'{total}'
946 if seqno is not None:
946 if seqno is not None:
947 expander[b'n'] = b'{seqno}'
947 expander[b'n'] = b'{seqno}'
948 if total is not None and seqno is not None:
948 if total is not None and seqno is not None:
949 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
949 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
950 if pathname is not None:
950 if pathname is not None:
951 expander[b's'] = b'{pathname|basename}'
951 expander[b's'] = b'{pathname|basename}'
952 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
952 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
953 expander[b'p'] = b'{pathname}'
953 expander[b'p'] = b'{pathname}'
954
954
955 newname = []
955 newname = []
956 for typ, start, end in templater.scantemplate(pat, raw=True):
956 for typ, start, end in templater.scantemplate(pat, raw=True):
957 if typ != b'string':
957 if typ != b'string':
958 newname.append(pat[start:end])
958 newname.append(pat[start:end])
959 continue
959 continue
960 i = start
960 i = start
961 while i < end:
961 while i < end:
962 n = pat.find(b'%', i, end)
962 n = pat.find(b'%', i, end)
963 if n < 0:
963 if n < 0:
964 newname.append(stringutil.escapestr(pat[i:end]))
964 newname.append(stringutil.escapestr(pat[i:end]))
965 break
965 break
966 newname.append(stringutil.escapestr(pat[i:n]))
966 newname.append(stringutil.escapestr(pat[i:n]))
967 if n + 2 > end:
967 if n + 2 > end:
968 raise error.Abort(_("incomplete format spec in output "
968 raise error.Abort(_("incomplete format spec in output "
969 "filename"))
969 "filename"))
970 c = pat[n + 1:n + 2]
970 c = pat[n + 1:n + 2]
971 i = n + 2
971 i = n + 2
972 try:
972 try:
973 newname.append(expander[c])
973 newname.append(expander[c])
974 except KeyError:
974 except KeyError:
975 raise error.Abort(_("invalid format spec '%%%s' in output "
975 raise error.Abort(_("invalid format spec '%%%s' in output "
976 "filename") % c)
976 "filename") % c)
977 return ''.join(newname)
977 return ''.join(newname)
978
978
979 def makefilename(ctx, pat, **props):
979 def makefilename(ctx, pat, **props):
980 if not pat:
980 if not pat:
981 return pat
981 return pat
982 tmpl = _buildfntemplate(pat, **props)
982 tmpl = _buildfntemplate(pat, **props)
983 # BUG: alias expansion shouldn't be made against template fragments
983 # BUG: alias expansion shouldn't be made against template fragments
984 # rewritten from %-format strings, but we have no easy way to partially
984 # rewritten from %-format strings, but we have no easy way to partially
985 # disable the expansion.
985 # disable the expansion.
986 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
986 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
987
987
988 def isstdiofilename(pat):
988 def isstdiofilename(pat):
989 """True if the given pat looks like a filename denoting stdin/stdout"""
989 """True if the given pat looks like a filename denoting stdin/stdout"""
990 return not pat or pat == '-'
990 return not pat or pat == '-'
991
991
992 class _unclosablefile(object):
992 class _unclosablefile(object):
993 def __init__(self, fp):
993 def __init__(self, fp):
994 self._fp = fp
994 self._fp = fp
995
995
996 def close(self):
996 def close(self):
997 pass
997 pass
998
998
999 def __iter__(self):
999 def __iter__(self):
1000 return iter(self._fp)
1000 return iter(self._fp)
1001
1001
1002 def __getattr__(self, attr):
1002 def __getattr__(self, attr):
1003 return getattr(self._fp, attr)
1003 return getattr(self._fp, attr)
1004
1004
1005 def __enter__(self):
1005 def __enter__(self):
1006 return self
1006 return self
1007
1007
1008 def __exit__(self, exc_type, exc_value, exc_tb):
1008 def __exit__(self, exc_type, exc_value, exc_tb):
1009 pass
1009 pass
1010
1010
1011 def makefileobj(ctx, pat, mode='wb', **props):
1011 def makefileobj(ctx, pat, mode='wb', **props):
1012 writable = mode not in ('r', 'rb')
1012 writable = mode not in ('r', 'rb')
1013
1013
1014 if isstdiofilename(pat):
1014 if isstdiofilename(pat):
1015 repo = ctx.repo()
1015 repo = ctx.repo()
1016 if writable:
1016 if writable:
1017 fp = repo.ui.fout
1017 fp = repo.ui.fout
1018 else:
1018 else:
1019 fp = repo.ui.fin
1019 fp = repo.ui.fin
1020 return _unclosablefile(fp)
1020 return _unclosablefile(fp)
1021 fn = makefilename(ctx, pat, **props)
1021 fn = makefilename(ctx, pat, **props)
1022 return open(fn, mode)
1022 return open(fn, mode)
1023
1023
1024 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1024 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1025 """opens the changelog, manifest, a filelog or a given revlog"""
1025 """opens the changelog, manifest, a filelog or a given revlog"""
1026 cl = opts['changelog']
1026 cl = opts['changelog']
1027 mf = opts['manifest']
1027 mf = opts['manifest']
1028 dir = opts['dir']
1028 dir = opts['dir']
1029 msg = None
1029 msg = None
1030 if cl and mf:
1030 if cl and mf:
1031 msg = _('cannot specify --changelog and --manifest at the same time')
1031 msg = _('cannot specify --changelog and --manifest at the same time')
1032 elif cl and dir:
1032 elif cl and dir:
1033 msg = _('cannot specify --changelog and --dir at the same time')
1033 msg = _('cannot specify --changelog and --dir at the same time')
1034 elif cl or mf or dir:
1034 elif cl or mf or dir:
1035 if file_:
1035 if file_:
1036 msg = _('cannot specify filename with --changelog or --manifest')
1036 msg = _('cannot specify filename with --changelog or --manifest')
1037 elif not repo:
1037 elif not repo:
1038 msg = _('cannot specify --changelog or --manifest or --dir '
1038 msg = _('cannot specify --changelog or --manifest or --dir '
1039 'without a repository')
1039 'without a repository')
1040 if msg:
1040 if msg:
1041 raise error.Abort(msg)
1041 raise error.Abort(msg)
1042
1042
1043 r = None
1043 r = None
1044 if repo:
1044 if repo:
1045 if cl:
1045 if cl:
1046 r = repo.unfiltered().changelog
1046 r = repo.unfiltered().changelog
1047 elif dir:
1047 elif dir:
1048 if 'treemanifest' not in repo.requirements:
1048 if 'treemanifest' not in repo.requirements:
1049 raise error.Abort(_("--dir can only be used on repos with "
1049 raise error.Abort(_("--dir can only be used on repos with "
1050 "treemanifest enabled"))
1050 "treemanifest enabled"))
1051 if not dir.endswith('/'):
1051 if not dir.endswith('/'):
1052 dir = dir + '/'
1052 dir = dir + '/'
1053 dirlog = repo.manifestlog.getstorage(dir)
1053 dirlog = repo.manifestlog.getstorage(dir)
1054 if len(dirlog):
1054 if len(dirlog):
1055 r = dirlog
1055 r = dirlog
1056 elif mf:
1056 elif mf:
1057 r = repo.manifestlog.getstorage(b'')
1057 r = repo.manifestlog.getstorage(b'')
1058 elif file_:
1058 elif file_:
1059 filelog = repo.file(file_)
1059 filelog = repo.file(file_)
1060 if len(filelog):
1060 if len(filelog):
1061 r = filelog
1061 r = filelog
1062
1062
1063 # Not all storage may be revlogs. If requested, try to return an actual
1063 # Not all storage may be revlogs. If requested, try to return an actual
1064 # revlog instance.
1064 # revlog instance.
1065 if returnrevlog:
1065 if returnrevlog:
1066 if isinstance(r, revlog.revlog):
1066 if isinstance(r, revlog.revlog):
1067 pass
1067 pass
1068 elif util.safehasattr(r, '_revlog'):
1068 elif util.safehasattr(r, '_revlog'):
1069 r = r._revlog
1069 r = r._revlog
1070 elif r is not None:
1070 elif r is not None:
1071 raise error.Abort(_('%r does not appear to be a revlog') % r)
1071 raise error.Abort(_('%r does not appear to be a revlog') % r)
1072
1072
1073 if not r:
1073 if not r:
1074 if not returnrevlog:
1074 if not returnrevlog:
1075 raise error.Abort(_('cannot give path to non-revlog'))
1075 raise error.Abort(_('cannot give path to non-revlog'))
1076
1076
1077 if not file_:
1077 if not file_:
1078 raise error.CommandError(cmd, _('invalid arguments'))
1078 raise error.CommandError(cmd, _('invalid arguments'))
1079 if not os.path.isfile(file_):
1079 if not os.path.isfile(file_):
1080 raise error.Abort(_("revlog '%s' not found") % file_)
1080 raise error.Abort(_("revlog '%s' not found") % file_)
1081 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1081 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1082 file_[:-2] + ".i")
1082 file_[:-2] + ".i")
1083 return r
1083 return r
1084
1084
1085 def openrevlog(repo, cmd, file_, opts):
1085 def openrevlog(repo, cmd, file_, opts):
1086 """Obtain a revlog backing storage of an item.
1086 """Obtain a revlog backing storage of an item.
1087
1087
1088 This is similar to ``openstorage()`` except it always returns a revlog.
1088 This is similar to ``openstorage()`` except it always returns a revlog.
1089
1089
1090 In most cases, a caller cares about the main storage object - not the
1090 In most cases, a caller cares about the main storage object - not the
1091 revlog backing it. Therefore, this function should only be used by code
1091 revlog backing it. Therefore, this function should only be used by code
1092 that needs to examine low-level revlog implementation details. e.g. debug
1092 that needs to examine low-level revlog implementation details. e.g. debug
1093 commands.
1093 commands.
1094 """
1094 """
1095 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1095 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1096
1096
1097 def copy(ui, repo, pats, opts, rename=False):
1097 def copy(ui, repo, pats, opts, rename=False):
1098 # called with the repo lock held
1098 # called with the repo lock held
1099 #
1099 #
1100 # hgsep => pathname that uses "/" to separate directories
1100 # hgsep => pathname that uses "/" to separate directories
1101 # ossep => pathname that uses os.sep to separate directories
1101 # ossep => pathname that uses os.sep to separate directories
1102 cwd = repo.getcwd()
1102 cwd = repo.getcwd()
1103 targets = {}
1103 targets = {}
1104 after = opts.get("after")
1104 after = opts.get("after")
1105 dryrun = opts.get("dry_run")
1105 dryrun = opts.get("dry_run")
1106 wctx = repo[None]
1106 wctx = repo[None]
1107
1107
1108 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1108 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1109 def walkpat(pat):
1109 def walkpat(pat):
1110 srcs = []
1110 srcs = []
1111 if after:
1111 if after:
1112 badstates = '?'
1112 badstates = '?'
1113 else:
1113 else:
1114 badstates = '?r'
1114 badstates = '?r'
1115 m = scmutil.match(wctx, [pat], opts, globbed=True)
1115 m = scmutil.match(wctx, [pat], opts, globbed=True)
1116 for abs in wctx.walk(m):
1116 for abs in wctx.walk(m):
1117 state = repo.dirstate[abs]
1117 state = repo.dirstate[abs]
1118 rel = uipathfn(abs)
1118 rel = uipathfn(abs)
1119 exact = m.exact(abs)
1119 exact = m.exact(abs)
1120 if state in badstates:
1120 if state in badstates:
1121 if exact and state == '?':
1121 if exact and state == '?':
1122 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1122 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1123 if exact and state == 'r':
1123 if exact and state == 'r':
1124 ui.warn(_('%s: not copying - file has been marked for'
1124 ui.warn(_('%s: not copying - file has been marked for'
1125 ' remove\n') % rel)
1125 ' remove\n') % rel)
1126 continue
1126 continue
1127 # abs: hgsep
1127 # abs: hgsep
1128 # rel: ossep
1128 # rel: ossep
1129 srcs.append((abs, rel, exact))
1129 srcs.append((abs, rel, exact))
1130 return srcs
1130 return srcs
1131
1131
1132 # abssrc: hgsep
1132 # abssrc: hgsep
1133 # relsrc: ossep
1133 # relsrc: ossep
1134 # otarget: ossep
1134 # otarget: ossep
1135 def copyfile(abssrc, relsrc, otarget, exact):
1135 def copyfile(abssrc, relsrc, otarget, exact):
1136 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1136 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1137 if '/' in abstarget:
1137 if '/' in abstarget:
1138 # We cannot normalize abstarget itself, this would prevent
1138 # We cannot normalize abstarget itself, this would prevent
1139 # case only renames, like a => A.
1139 # case only renames, like a => A.
1140 abspath, absname = abstarget.rsplit('/', 1)
1140 abspath, absname = abstarget.rsplit('/', 1)
1141 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1141 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1142 reltarget = repo.pathto(abstarget, cwd)
1142 reltarget = repo.pathto(abstarget, cwd)
1143 target = repo.wjoin(abstarget)
1143 target = repo.wjoin(abstarget)
1144 src = repo.wjoin(abssrc)
1144 src = repo.wjoin(abssrc)
1145 state = repo.dirstate[abstarget]
1145 state = repo.dirstate[abstarget]
1146
1146
1147 scmutil.checkportable(ui, abstarget)
1147 scmutil.checkportable(ui, abstarget)
1148
1148
1149 # check for collisions
1149 # check for collisions
1150 prevsrc = targets.get(abstarget)
1150 prevsrc = targets.get(abstarget)
1151 if prevsrc is not None:
1151 if prevsrc is not None:
1152 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1152 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1153 (reltarget, repo.pathto(abssrc, cwd),
1153 (reltarget, repo.pathto(abssrc, cwd),
1154 repo.pathto(prevsrc, cwd)))
1154 repo.pathto(prevsrc, cwd)))
1155 return True # report a failure
1155 return True # report a failure
1156
1156
1157 # check for overwrites
1157 # check for overwrites
1158 exists = os.path.lexists(target)
1158 exists = os.path.lexists(target)
1159 samefile = False
1159 samefile = False
1160 if exists and abssrc != abstarget:
1160 if exists and abssrc != abstarget:
1161 if (repo.dirstate.normalize(abssrc) ==
1161 if (repo.dirstate.normalize(abssrc) ==
1162 repo.dirstate.normalize(abstarget)):
1162 repo.dirstate.normalize(abstarget)):
1163 if not rename:
1163 if not rename:
1164 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1164 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1165 return True # report a failure
1165 return True # report a failure
1166 exists = False
1166 exists = False
1167 samefile = True
1167 samefile = True
1168
1168
1169 if not after and exists or after and state in 'mn':
1169 if not after and exists or after and state in 'mn':
1170 if not opts['force']:
1170 if not opts['force']:
1171 if state in 'mn':
1171 if state in 'mn':
1172 msg = _('%s: not overwriting - file already committed\n')
1172 msg = _('%s: not overwriting - file already committed\n')
1173 if after:
1173 if after:
1174 flags = '--after --force'
1174 flags = '--after --force'
1175 else:
1175 else:
1176 flags = '--force'
1176 flags = '--force'
1177 if rename:
1177 if rename:
1178 hint = _("('hg rename %s' to replace the file by "
1178 hint = _("('hg rename %s' to replace the file by "
1179 'recording a rename)\n') % flags
1179 'recording a rename)\n') % flags
1180 else:
1180 else:
1181 hint = _("('hg copy %s' to replace the file by "
1181 hint = _("('hg copy %s' to replace the file by "
1182 'recording a copy)\n') % flags
1182 'recording a copy)\n') % flags
1183 else:
1183 else:
1184 msg = _('%s: not overwriting - file exists\n')
1184 msg = _('%s: not overwriting - file exists\n')
1185 if rename:
1185 if rename:
1186 hint = _("('hg rename --after' to record the rename)\n")
1186 hint = _("('hg rename --after' to record the rename)\n")
1187 else:
1187 else:
1188 hint = _("('hg copy --after' to record the copy)\n")
1188 hint = _("('hg copy --after' to record the copy)\n")
1189 ui.warn(msg % reltarget)
1189 ui.warn(msg % reltarget)
1190 ui.warn(hint)
1190 ui.warn(hint)
1191 return True # report a failure
1191 return True # report a failure
1192
1192
1193 if after:
1193 if after:
1194 if not exists:
1194 if not exists:
1195 if rename:
1195 if rename:
1196 ui.warn(_('%s: not recording move - %s does not exist\n') %
1196 ui.warn(_('%s: not recording move - %s does not exist\n') %
1197 (relsrc, reltarget))
1197 (relsrc, reltarget))
1198 else:
1198 else:
1199 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1199 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1200 (relsrc, reltarget))
1200 (relsrc, reltarget))
1201 return True # report a failure
1201 return True # report a failure
1202 elif not dryrun:
1202 elif not dryrun:
1203 try:
1203 try:
1204 if exists:
1204 if exists:
1205 os.unlink(target)
1205 os.unlink(target)
1206 targetdir = os.path.dirname(target) or '.'
1206 targetdir = os.path.dirname(target) or '.'
1207 if not os.path.isdir(targetdir):
1207 if not os.path.isdir(targetdir):
1208 os.makedirs(targetdir)
1208 os.makedirs(targetdir)
1209 if samefile:
1209 if samefile:
1210 tmp = target + "~hgrename"
1210 tmp = target + "~hgrename"
1211 os.rename(src, tmp)
1211 os.rename(src, tmp)
1212 os.rename(tmp, target)
1212 os.rename(tmp, target)
1213 else:
1213 else:
1214 # Preserve stat info on renames, not on copies; this matches
1214 # Preserve stat info on renames, not on copies; this matches
1215 # Linux CLI behavior.
1215 # Linux CLI behavior.
1216 util.copyfile(src, target, copystat=rename)
1216 util.copyfile(src, target, copystat=rename)
1217 srcexists = True
1217 srcexists = True
1218 except IOError as inst:
1218 except IOError as inst:
1219 if inst.errno == errno.ENOENT:
1219 if inst.errno == errno.ENOENT:
1220 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1220 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1221 srcexists = False
1221 srcexists = False
1222 else:
1222 else:
1223 ui.warn(_('%s: cannot copy - %s\n') %
1223 ui.warn(_('%s: cannot copy - %s\n') %
1224 (relsrc, encoding.strtolocal(inst.strerror)))
1224 (relsrc, encoding.strtolocal(inst.strerror)))
1225 return True # report a failure
1225 return True # report a failure
1226
1226
1227 if ui.verbose or not exact:
1227 if ui.verbose or not exact:
1228 if rename:
1228 if rename:
1229 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1229 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1230 else:
1230 else:
1231 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1231 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1232
1232
1233 targets[abstarget] = abssrc
1233 targets[abstarget] = abssrc
1234
1234
1235 # fix up dirstate
1235 # fix up dirstate
1236 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1236 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1237 dryrun=dryrun, cwd=cwd)
1237 dryrun=dryrun, cwd=cwd)
1238 if rename and not dryrun:
1238 if rename and not dryrun:
1239 if not after and srcexists and not samefile:
1239 if not after and srcexists and not samefile:
1240 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1240 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1241 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1241 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1242 wctx.forget([abssrc])
1242 wctx.forget([abssrc])
1243
1243
1244 # pat: ossep
1244 # pat: ossep
1245 # dest ossep
1245 # dest ossep
1246 # srcs: list of (hgsep, hgsep, ossep, bool)
1246 # srcs: list of (hgsep, hgsep, ossep, bool)
1247 # return: function that takes hgsep and returns ossep
1247 # return: function that takes hgsep and returns ossep
1248 def targetpathfn(pat, dest, srcs):
1248 def targetpathfn(pat, dest, srcs):
1249 if os.path.isdir(pat):
1249 if os.path.isdir(pat):
1250 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1250 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1251 abspfx = util.localpath(abspfx)
1251 abspfx = util.localpath(abspfx)
1252 if destdirexists:
1252 if destdirexists:
1253 striplen = len(os.path.split(abspfx)[0])
1253 striplen = len(os.path.split(abspfx)[0])
1254 else:
1254 else:
1255 striplen = len(abspfx)
1255 striplen = len(abspfx)
1256 if striplen:
1256 if striplen:
1257 striplen += len(pycompat.ossep)
1257 striplen += len(pycompat.ossep)
1258 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1258 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1259 elif destdirexists:
1259 elif destdirexists:
1260 res = lambda p: os.path.join(dest,
1260 res = lambda p: os.path.join(dest,
1261 os.path.basename(util.localpath(p)))
1261 os.path.basename(util.localpath(p)))
1262 else:
1262 else:
1263 res = lambda p: dest
1263 res = lambda p: dest
1264 return res
1264 return res
1265
1265
1266 # pat: ossep
1266 # pat: ossep
1267 # dest ossep
1267 # dest ossep
1268 # srcs: list of (hgsep, hgsep, ossep, bool)
1268 # srcs: list of (hgsep, hgsep, ossep, bool)
1269 # return: function that takes hgsep and returns ossep
1269 # return: function that takes hgsep and returns ossep
1270 def targetpathafterfn(pat, dest, srcs):
1270 def targetpathafterfn(pat, dest, srcs):
1271 if matchmod.patkind(pat):
1271 if matchmod.patkind(pat):
1272 # a mercurial pattern
1272 # a mercurial pattern
1273 res = lambda p: os.path.join(dest,
1273 res = lambda p: os.path.join(dest,
1274 os.path.basename(util.localpath(p)))
1274 os.path.basename(util.localpath(p)))
1275 else:
1275 else:
1276 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1276 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1277 if len(abspfx) < len(srcs[0][0]):
1277 if len(abspfx) < len(srcs[0][0]):
1278 # A directory. Either the target path contains the last
1278 # A directory. Either the target path contains the last
1279 # component of the source path or it does not.
1279 # component of the source path or it does not.
1280 def evalpath(striplen):
1280 def evalpath(striplen):
1281 score = 0
1281 score = 0
1282 for s in srcs:
1282 for s in srcs:
1283 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1283 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1284 if os.path.lexists(t):
1284 if os.path.lexists(t):
1285 score += 1
1285 score += 1
1286 return score
1286 return score
1287
1287
1288 abspfx = util.localpath(abspfx)
1288 abspfx = util.localpath(abspfx)
1289 striplen = len(abspfx)
1289 striplen = len(abspfx)
1290 if striplen:
1290 if striplen:
1291 striplen += len(pycompat.ossep)
1291 striplen += len(pycompat.ossep)
1292 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1292 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1293 score = evalpath(striplen)
1293 score = evalpath(striplen)
1294 striplen1 = len(os.path.split(abspfx)[0])
1294 striplen1 = len(os.path.split(abspfx)[0])
1295 if striplen1:
1295 if striplen1:
1296 striplen1 += len(pycompat.ossep)
1296 striplen1 += len(pycompat.ossep)
1297 if evalpath(striplen1) > score:
1297 if evalpath(striplen1) > score:
1298 striplen = striplen1
1298 striplen = striplen1
1299 res = lambda p: os.path.join(dest,
1299 res = lambda p: os.path.join(dest,
1300 util.localpath(p)[striplen:])
1300 util.localpath(p)[striplen:])
1301 else:
1301 else:
1302 # a file
1302 # a file
1303 if destdirexists:
1303 if destdirexists:
1304 res = lambda p: os.path.join(dest,
1304 res = lambda p: os.path.join(dest,
1305 os.path.basename(util.localpath(p)))
1305 os.path.basename(util.localpath(p)))
1306 else:
1306 else:
1307 res = lambda p: dest
1307 res = lambda p: dest
1308 return res
1308 return res
1309
1309
1310 pats = scmutil.expandpats(pats)
1310 pats = scmutil.expandpats(pats)
1311 if not pats:
1311 if not pats:
1312 raise error.Abort(_('no source or destination specified'))
1312 raise error.Abort(_('no source or destination specified'))
1313 if len(pats) == 1:
1313 if len(pats) == 1:
1314 raise error.Abort(_('no destination specified'))
1314 raise error.Abort(_('no destination specified'))
1315 dest = pats.pop()
1315 dest = pats.pop()
1316 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1316 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1317 if not destdirexists:
1317 if not destdirexists:
1318 if len(pats) > 1 or matchmod.patkind(pats[0]):
1318 if len(pats) > 1 or matchmod.patkind(pats[0]):
1319 raise error.Abort(_('with multiple sources, destination must be an '
1319 raise error.Abort(_('with multiple sources, destination must be an '
1320 'existing directory'))
1320 'existing directory'))
1321 if util.endswithsep(dest):
1321 if util.endswithsep(dest):
1322 raise error.Abort(_('destination %s is not a directory') % dest)
1322 raise error.Abort(_('destination %s is not a directory') % dest)
1323
1323
1324 tfn = targetpathfn
1324 tfn = targetpathfn
1325 if after:
1325 if after:
1326 tfn = targetpathafterfn
1326 tfn = targetpathafterfn
1327 copylist = []
1327 copylist = []
1328 for pat in pats:
1328 for pat in pats:
1329 srcs = walkpat(pat)
1329 srcs = walkpat(pat)
1330 if not srcs:
1330 if not srcs:
1331 continue
1331 continue
1332 copylist.append((tfn(pat, dest, srcs), srcs))
1332 copylist.append((tfn(pat, dest, srcs), srcs))
1333 if not copylist:
1333 if not copylist:
1334 raise error.Abort(_('no files to copy'))
1334 raise error.Abort(_('no files to copy'))
1335
1335
1336 errors = 0
1336 errors = 0
1337 for targetpath, srcs in copylist:
1337 for targetpath, srcs in copylist:
1338 for abssrc, relsrc, exact in srcs:
1338 for abssrc, relsrc, exact in srcs:
1339 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1339 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1340 errors += 1
1340 errors += 1
1341
1341
1342 return errors != 0
1342 return errors != 0
1343
1343
1344 ## facility to let extension process additional data into an import patch
1344 ## facility to let extension process additional data into an import patch
1345 # list of identifier to be executed in order
1345 # list of identifier to be executed in order
1346 extrapreimport = [] # run before commit
1346 extrapreimport = [] # run before commit
1347 extrapostimport = [] # run after commit
1347 extrapostimport = [] # run after commit
1348 # mapping from identifier to actual import function
1348 # mapping from identifier to actual import function
1349 #
1349 #
1350 # 'preimport' are run before the commit is made and are provided the following
1350 # 'preimport' are run before the commit is made and are provided the following
1351 # arguments:
1351 # arguments:
1352 # - repo: the localrepository instance,
1352 # - repo: the localrepository instance,
1353 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1353 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1354 # - extra: the future extra dictionary of the changeset, please mutate it,
1354 # - extra: the future extra dictionary of the changeset, please mutate it,
1355 # - opts: the import options.
1355 # - opts: the import options.
1356 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1356 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1357 # mutation of in memory commit and more. Feel free to rework the code to get
1357 # mutation of in memory commit and more. Feel free to rework the code to get
1358 # there.
1358 # there.
1359 extrapreimportmap = {}
1359 extrapreimportmap = {}
1360 # 'postimport' are run after the commit is made and are provided the following
1360 # 'postimport' are run after the commit is made and are provided the following
1361 # argument:
1361 # argument:
1362 # - ctx: the changectx created by import.
1362 # - ctx: the changectx created by import.
1363 extrapostimportmap = {}
1363 extrapostimportmap = {}
1364
1364
1365 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1365 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1366 """Utility function used by commands.import to import a single patch
1366 """Utility function used by commands.import to import a single patch
1367
1367
1368 This function is explicitly defined here to help the evolve extension to
1368 This function is explicitly defined here to help the evolve extension to
1369 wrap this part of the import logic.
1369 wrap this part of the import logic.
1370
1370
1371 The API is currently a bit ugly because it a simple code translation from
1371 The API is currently a bit ugly because it a simple code translation from
1372 the import command. Feel free to make it better.
1372 the import command. Feel free to make it better.
1373
1373
1374 :patchdata: a dictionary containing parsed patch data (such as from
1374 :patchdata: a dictionary containing parsed patch data (such as from
1375 ``patch.extract()``)
1375 ``patch.extract()``)
1376 :parents: nodes that will be parent of the created commit
1376 :parents: nodes that will be parent of the created commit
1377 :opts: the full dict of option passed to the import command
1377 :opts: the full dict of option passed to the import command
1378 :msgs: list to save commit message to.
1378 :msgs: list to save commit message to.
1379 (used in case we need to save it when failing)
1379 (used in case we need to save it when failing)
1380 :updatefunc: a function that update a repo to a given node
1380 :updatefunc: a function that update a repo to a given node
1381 updatefunc(<repo>, <node>)
1381 updatefunc(<repo>, <node>)
1382 """
1382 """
1383 # avoid cycle context -> subrepo -> cmdutil
1383 # avoid cycle context -> subrepo -> cmdutil
1384 from . import context
1384 from . import context
1385
1385
1386 tmpname = patchdata.get('filename')
1386 tmpname = patchdata.get('filename')
1387 message = patchdata.get('message')
1387 message = patchdata.get('message')
1388 user = opts.get('user') or patchdata.get('user')
1388 user = opts.get('user') or patchdata.get('user')
1389 date = opts.get('date') or patchdata.get('date')
1389 date = opts.get('date') or patchdata.get('date')
1390 branch = patchdata.get('branch')
1390 branch = patchdata.get('branch')
1391 nodeid = patchdata.get('nodeid')
1391 nodeid = patchdata.get('nodeid')
1392 p1 = patchdata.get('p1')
1392 p1 = patchdata.get('p1')
1393 p2 = patchdata.get('p2')
1393 p2 = patchdata.get('p2')
1394
1394
1395 nocommit = opts.get('no_commit')
1395 nocommit = opts.get('no_commit')
1396 importbranch = opts.get('import_branch')
1396 importbranch = opts.get('import_branch')
1397 update = not opts.get('bypass')
1397 update = not opts.get('bypass')
1398 strip = opts["strip"]
1398 strip = opts["strip"]
1399 prefix = opts["prefix"]
1399 prefix = opts["prefix"]
1400 sim = float(opts.get('similarity') or 0)
1400 sim = float(opts.get('similarity') or 0)
1401
1401
1402 if not tmpname:
1402 if not tmpname:
1403 return None, None, False
1403 return None, None, False
1404
1404
1405 rejects = False
1405 rejects = False
1406
1406
1407 cmdline_message = logmessage(ui, opts)
1407 cmdline_message = logmessage(ui, opts)
1408 if cmdline_message:
1408 if cmdline_message:
1409 # pickup the cmdline msg
1409 # pickup the cmdline msg
1410 message = cmdline_message
1410 message = cmdline_message
1411 elif message:
1411 elif message:
1412 # pickup the patch msg
1412 # pickup the patch msg
1413 message = message.strip()
1413 message = message.strip()
1414 else:
1414 else:
1415 # launch the editor
1415 # launch the editor
1416 message = None
1416 message = None
1417 ui.debug('message:\n%s\n' % (message or ''))
1417 ui.debug('message:\n%s\n' % (message or ''))
1418
1418
1419 if len(parents) == 1:
1419 if len(parents) == 1:
1420 parents.append(repo[nullid])
1420 parents.append(repo[nullid])
1421 if opts.get('exact'):
1421 if opts.get('exact'):
1422 if not nodeid or not p1:
1422 if not nodeid or not p1:
1423 raise error.Abort(_('not a Mercurial patch'))
1423 raise error.Abort(_('not a Mercurial patch'))
1424 p1 = repo[p1]
1424 p1 = repo[p1]
1425 p2 = repo[p2 or nullid]
1425 p2 = repo[p2 or nullid]
1426 elif p2:
1426 elif p2:
1427 try:
1427 try:
1428 p1 = repo[p1]
1428 p1 = repo[p1]
1429 p2 = repo[p2]
1429 p2 = repo[p2]
1430 # Without any options, consider p2 only if the
1430 # Without any options, consider p2 only if the
1431 # patch is being applied on top of the recorded
1431 # patch is being applied on top of the recorded
1432 # first parent.
1432 # first parent.
1433 if p1 != parents[0]:
1433 if p1 != parents[0]:
1434 p1 = parents[0]
1434 p1 = parents[0]
1435 p2 = repo[nullid]
1435 p2 = repo[nullid]
1436 except error.RepoError:
1436 except error.RepoError:
1437 p1, p2 = parents
1437 p1, p2 = parents
1438 if p2.node() == nullid:
1438 if p2.node() == nullid:
1439 ui.warn(_("warning: import the patch as a normal revision\n"
1439 ui.warn(_("warning: import the patch as a normal revision\n"
1440 "(use --exact to import the patch as a merge)\n"))
1440 "(use --exact to import the patch as a merge)\n"))
1441 else:
1441 else:
1442 p1, p2 = parents
1442 p1, p2 = parents
1443
1443
1444 n = None
1444 n = None
1445 if update:
1445 if update:
1446 if p1 != parents[0]:
1446 if p1 != parents[0]:
1447 updatefunc(repo, p1.node())
1447 updatefunc(repo, p1.node())
1448 if p2 != parents[1]:
1448 if p2 != parents[1]:
1449 repo.setparents(p1.node(), p2.node())
1449 repo.setparents(p1.node(), p2.node())
1450
1450
1451 if opts.get('exact') or importbranch:
1451 if opts.get('exact') or importbranch:
1452 repo.dirstate.setbranch(branch or 'default')
1452 repo.dirstate.setbranch(branch or 'default')
1453
1453
1454 partial = opts.get('partial', False)
1454 partial = opts.get('partial', False)
1455 files = set()
1455 files = set()
1456 try:
1456 try:
1457 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1457 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1458 files=files, eolmode=None, similarity=sim / 100.0)
1458 files=files, eolmode=None, similarity=sim / 100.0)
1459 except error.PatchError as e:
1459 except error.PatchError as e:
1460 if not partial:
1460 if not partial:
1461 raise error.Abort(pycompat.bytestr(e))
1461 raise error.Abort(pycompat.bytestr(e))
1462 if partial:
1462 if partial:
1463 rejects = True
1463 rejects = True
1464
1464
1465 files = list(files)
1465 files = list(files)
1466 if nocommit:
1466 if nocommit:
1467 if message:
1467 if message:
1468 msgs.append(message)
1468 msgs.append(message)
1469 else:
1469 else:
1470 if opts.get('exact') or p2:
1470 if opts.get('exact') or p2:
1471 # If you got here, you either use --force and know what
1471 # If you got here, you either use --force and know what
1472 # you are doing or used --exact or a merge patch while
1472 # you are doing or used --exact or a merge patch while
1473 # being updated to its first parent.
1473 # being updated to its first parent.
1474 m = None
1474 m = None
1475 else:
1475 else:
1476 m = scmutil.matchfiles(repo, files or [])
1476 m = scmutil.matchfiles(repo, files or [])
1477 editform = mergeeditform(repo[None], 'import.normal')
1477 editform = mergeeditform(repo[None], 'import.normal')
1478 if opts.get('exact'):
1478 if opts.get('exact'):
1479 editor = None
1479 editor = None
1480 else:
1480 else:
1481 editor = getcommiteditor(editform=editform,
1481 editor = getcommiteditor(editform=editform,
1482 **pycompat.strkwargs(opts))
1482 **pycompat.strkwargs(opts))
1483 extra = {}
1483 extra = {}
1484 for idfunc in extrapreimport:
1484 for idfunc in extrapreimport:
1485 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1485 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1486 overrides = {}
1486 overrides = {}
1487 if partial:
1487 if partial:
1488 overrides[('ui', 'allowemptycommit')] = True
1488 overrides[('ui', 'allowemptycommit')] = True
1489 with repo.ui.configoverride(overrides, 'import'):
1489 with repo.ui.configoverride(overrides, 'import'):
1490 n = repo.commit(message, user,
1490 n = repo.commit(message, user,
1491 date, match=m,
1491 date, match=m,
1492 editor=editor, extra=extra)
1492 editor=editor, extra=extra)
1493 for idfunc in extrapostimport:
1493 for idfunc in extrapostimport:
1494 extrapostimportmap[idfunc](repo[n])
1494 extrapostimportmap[idfunc](repo[n])
1495 else:
1495 else:
1496 if opts.get('exact') or importbranch:
1496 if opts.get('exact') or importbranch:
1497 branch = branch or 'default'
1497 branch = branch or 'default'
1498 else:
1498 else:
1499 branch = p1.branch()
1499 branch = p1.branch()
1500 store = patch.filestore()
1500 store = patch.filestore()
1501 try:
1501 try:
1502 files = set()
1502 files = set()
1503 try:
1503 try:
1504 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1504 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1505 files, eolmode=None)
1505 files, eolmode=None)
1506 except error.PatchError as e:
1506 except error.PatchError as e:
1507 raise error.Abort(stringutil.forcebytestr(e))
1507 raise error.Abort(stringutil.forcebytestr(e))
1508 if opts.get('exact'):
1508 if opts.get('exact'):
1509 editor = None
1509 editor = None
1510 else:
1510 else:
1511 editor = getcommiteditor(editform='import.bypass')
1511 editor = getcommiteditor(editform='import.bypass')
1512 memctx = context.memctx(repo, (p1.node(), p2.node()),
1512 memctx = context.memctx(repo, (p1.node(), p2.node()),
1513 message,
1513 message,
1514 files=files,
1514 files=files,
1515 filectxfn=store,
1515 filectxfn=store,
1516 user=user,
1516 user=user,
1517 date=date,
1517 date=date,
1518 branch=branch,
1518 branch=branch,
1519 editor=editor)
1519 editor=editor)
1520 n = memctx.commit()
1520 n = memctx.commit()
1521 finally:
1521 finally:
1522 store.close()
1522 store.close()
1523 if opts.get('exact') and nocommit:
1523 if opts.get('exact') and nocommit:
1524 # --exact with --no-commit is still useful in that it does merge
1524 # --exact with --no-commit is still useful in that it does merge
1525 # and branch bits
1525 # and branch bits
1526 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1526 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1527 elif opts.get('exact') and (not n or hex(n) != nodeid):
1527 elif opts.get('exact') and (not n or hex(n) != nodeid):
1528 raise error.Abort(_('patch is damaged or loses information'))
1528 raise error.Abort(_('patch is damaged or loses information'))
1529 msg = _('applied to working directory')
1529 msg = _('applied to working directory')
1530 if n:
1530 if n:
1531 # i18n: refers to a short changeset id
1531 # i18n: refers to a short changeset id
1532 msg = _('created %s') % short(n)
1532 msg = _('created %s') % short(n)
1533 return msg, n, rejects
1533 return msg, n, rejects
1534
1534
1535 # facility to let extensions include additional data in an exported patch
1535 # facility to let extensions include additional data in an exported patch
1536 # list of identifiers to be executed in order
1536 # list of identifiers to be executed in order
1537 extraexport = []
1537 extraexport = []
1538 # mapping from identifier to actual export function
1538 # mapping from identifier to actual export function
1539 # function as to return a string to be added to the header or None
1539 # function as to return a string to be added to the header or None
1540 # it is given two arguments (sequencenumber, changectx)
1540 # it is given two arguments (sequencenumber, changectx)
1541 extraexportmap = {}
1541 extraexportmap = {}
1542
1542
1543 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1543 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1544 node = scmutil.binnode(ctx)
1544 node = scmutil.binnode(ctx)
1545 parents = [p.node() for p in ctx.parents() if p]
1545 parents = [p.node() for p in ctx.parents() if p]
1546 branch = ctx.branch()
1546 branch = ctx.branch()
1547 if switch_parent:
1547 if switch_parent:
1548 parents.reverse()
1548 parents.reverse()
1549
1549
1550 if parents:
1550 if parents:
1551 prev = parents[0]
1551 prev = parents[0]
1552 else:
1552 else:
1553 prev = nullid
1553 prev = nullid
1554
1554
1555 fm.context(ctx=ctx)
1555 fm.context(ctx=ctx)
1556 fm.plain('# HG changeset patch\n')
1556 fm.plain('# HG changeset patch\n')
1557 fm.write('user', '# User %s\n', ctx.user())
1557 fm.write('user', '# User %s\n', ctx.user())
1558 fm.plain('# Date %d %d\n' % ctx.date())
1558 fm.plain('# Date %d %d\n' % ctx.date())
1559 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1559 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1560 fm.condwrite(branch and branch != 'default',
1560 fm.condwrite(branch and branch != 'default',
1561 'branch', '# Branch %s\n', branch)
1561 'branch', '# Branch %s\n', branch)
1562 fm.write('node', '# Node ID %s\n', hex(node))
1562 fm.write('node', '# Node ID %s\n', hex(node))
1563 fm.plain('# Parent %s\n' % hex(prev))
1563 fm.plain('# Parent %s\n' % hex(prev))
1564 if len(parents) > 1:
1564 if len(parents) > 1:
1565 fm.plain('# Parent %s\n' % hex(parents[1]))
1565 fm.plain('# Parent %s\n' % hex(parents[1]))
1566 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1566 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1567
1567
1568 # TODO: redesign extraexportmap function to support formatter
1568 # TODO: redesign extraexportmap function to support formatter
1569 for headerid in extraexport:
1569 for headerid in extraexport:
1570 header = extraexportmap[headerid](seqno, ctx)
1570 header = extraexportmap[headerid](seqno, ctx)
1571 if header is not None:
1571 if header is not None:
1572 fm.plain('# %s\n' % header)
1572 fm.plain('# %s\n' % header)
1573
1573
1574 fm.write('desc', '%s\n', ctx.description().rstrip())
1574 fm.write('desc', '%s\n', ctx.description().rstrip())
1575 fm.plain('\n')
1575 fm.plain('\n')
1576
1576
1577 if fm.isplain():
1577 if fm.isplain():
1578 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1578 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1579 for chunk, label in chunkiter:
1579 for chunk, label in chunkiter:
1580 fm.plain(chunk, label=label)
1580 fm.plain(chunk, label=label)
1581 else:
1581 else:
1582 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1582 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1583 # TODO: make it structured?
1583 # TODO: make it structured?
1584 fm.data(diff=b''.join(chunkiter))
1584 fm.data(diff=b''.join(chunkiter))
1585
1585
1586 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1586 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1587 """Export changesets to stdout or a single file"""
1587 """Export changesets to stdout or a single file"""
1588 for seqno, rev in enumerate(revs, 1):
1588 for seqno, rev in enumerate(revs, 1):
1589 ctx = repo[rev]
1589 ctx = repo[rev]
1590 if not dest.startswith('<'):
1590 if not dest.startswith('<'):
1591 repo.ui.note("%s\n" % dest)
1591 repo.ui.note("%s\n" % dest)
1592 fm.startitem()
1592 fm.startitem()
1593 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1593 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1594
1594
1595 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1595 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1596 match):
1596 match):
1597 """Export changesets to possibly multiple files"""
1597 """Export changesets to possibly multiple files"""
1598 total = len(revs)
1598 total = len(revs)
1599 revwidth = max(len(str(rev)) for rev in revs)
1599 revwidth = max(len(str(rev)) for rev in revs)
1600 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1600 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1601
1601
1602 for seqno, rev in enumerate(revs, 1):
1602 for seqno, rev in enumerate(revs, 1):
1603 ctx = repo[rev]
1603 ctx = repo[rev]
1604 dest = makefilename(ctx, fntemplate,
1604 dest = makefilename(ctx, fntemplate,
1605 total=total, seqno=seqno, revwidth=revwidth)
1605 total=total, seqno=seqno, revwidth=revwidth)
1606 filemap.setdefault(dest, []).append((seqno, rev))
1606 filemap.setdefault(dest, []).append((seqno, rev))
1607
1607
1608 for dest in filemap:
1608 for dest in filemap:
1609 with formatter.maybereopen(basefm, dest) as fm:
1609 with formatter.maybereopen(basefm, dest) as fm:
1610 repo.ui.note("%s\n" % dest)
1610 repo.ui.note("%s\n" % dest)
1611 for seqno, rev in filemap[dest]:
1611 for seqno, rev in filemap[dest]:
1612 fm.startitem()
1612 fm.startitem()
1613 ctx = repo[rev]
1613 ctx = repo[rev]
1614 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1614 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1615 diffopts)
1615 diffopts)
1616
1616
1617 def _prefetchchangedfiles(repo, revs, match):
1617 def _prefetchchangedfiles(repo, revs, match):
1618 allfiles = set()
1618 allfiles = set()
1619 for rev in revs:
1619 for rev in revs:
1620 for file in repo[rev].files():
1620 for file in repo[rev].files():
1621 if not match or match(file):
1621 if not match or match(file):
1622 allfiles.add(file)
1622 allfiles.add(file)
1623 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1623 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1624
1624
1625 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1625 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1626 opts=None, match=None):
1626 opts=None, match=None):
1627 '''export changesets as hg patches
1627 '''export changesets as hg patches
1628
1628
1629 Args:
1629 Args:
1630 repo: The repository from which we're exporting revisions.
1630 repo: The repository from which we're exporting revisions.
1631 revs: A list of revisions to export as revision numbers.
1631 revs: A list of revisions to export as revision numbers.
1632 basefm: A formatter to which patches should be written.
1632 basefm: A formatter to which patches should be written.
1633 fntemplate: An optional string to use for generating patch file names.
1633 fntemplate: An optional string to use for generating patch file names.
1634 switch_parent: If True, show diffs against second parent when not nullid.
1634 switch_parent: If True, show diffs against second parent when not nullid.
1635 Default is false, which always shows diff against p1.
1635 Default is false, which always shows diff against p1.
1636 opts: diff options to use for generating the patch.
1636 opts: diff options to use for generating the patch.
1637 match: If specified, only export changes to files matching this matcher.
1637 match: If specified, only export changes to files matching this matcher.
1638
1638
1639 Returns:
1639 Returns:
1640 Nothing.
1640 Nothing.
1641
1641
1642 Side Effect:
1642 Side Effect:
1643 "HG Changeset Patch" data is emitted to one of the following
1643 "HG Changeset Patch" data is emitted to one of the following
1644 destinations:
1644 destinations:
1645 fntemplate specified: Each rev is written to a unique file named using
1645 fntemplate specified: Each rev is written to a unique file named using
1646 the given template.
1646 the given template.
1647 Otherwise: All revs will be written to basefm.
1647 Otherwise: All revs will be written to basefm.
1648 '''
1648 '''
1649 _prefetchchangedfiles(repo, revs, match)
1649 _prefetchchangedfiles(repo, revs, match)
1650
1650
1651 if not fntemplate:
1651 if not fntemplate:
1652 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1652 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1653 else:
1653 else:
1654 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1654 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1655 match)
1655 match)
1656
1656
1657 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1657 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1658 """Export changesets to the given file stream"""
1658 """Export changesets to the given file stream"""
1659 _prefetchchangedfiles(repo, revs, match)
1659 _prefetchchangedfiles(repo, revs, match)
1660
1660
1661 dest = getattr(fp, 'name', '<unnamed>')
1661 dest = getattr(fp, 'name', '<unnamed>')
1662 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1662 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1663 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1663 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1664
1664
1665 def showmarker(fm, marker, index=None):
1665 def showmarker(fm, marker, index=None):
1666 """utility function to display obsolescence marker in a readable way
1666 """utility function to display obsolescence marker in a readable way
1667
1667
1668 To be used by debug function."""
1668 To be used by debug function."""
1669 if index is not None:
1669 if index is not None:
1670 fm.write('index', '%i ', index)
1670 fm.write('index', '%i ', index)
1671 fm.write('prednode', '%s ', hex(marker.prednode()))
1671 fm.write('prednode', '%s ', hex(marker.prednode()))
1672 succs = marker.succnodes()
1672 succs = marker.succnodes()
1673 fm.condwrite(succs, 'succnodes', '%s ',
1673 fm.condwrite(succs, 'succnodes', '%s ',
1674 fm.formatlist(map(hex, succs), name='node'))
1674 fm.formatlist(map(hex, succs), name='node'))
1675 fm.write('flag', '%X ', marker.flags())
1675 fm.write('flag', '%X ', marker.flags())
1676 parents = marker.parentnodes()
1676 parents = marker.parentnodes()
1677 if parents is not None:
1677 if parents is not None:
1678 fm.write('parentnodes', '{%s} ',
1678 fm.write('parentnodes', '{%s} ',
1679 fm.formatlist(map(hex, parents), name='node', sep=', '))
1679 fm.formatlist(map(hex, parents), name='node', sep=', '))
1680 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1680 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1681 meta = marker.metadata().copy()
1681 meta = marker.metadata().copy()
1682 meta.pop('date', None)
1682 meta.pop('date', None)
1683 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1683 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1684 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1684 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1685 fm.plain('\n')
1685 fm.plain('\n')
1686
1686
1687 def finddate(ui, repo, date):
1687 def finddate(ui, repo, date):
1688 """Find the tipmost changeset that matches the given date spec"""
1688 """Find the tipmost changeset that matches the given date spec"""
1689
1689
1690 df = dateutil.matchdate(date)
1690 df = dateutil.matchdate(date)
1691 m = scmutil.matchall(repo)
1691 m = scmutil.matchall(repo)
1692 results = {}
1692 results = {}
1693
1693
1694 def prep(ctx, fns):
1694 def prep(ctx, fns):
1695 d = ctx.date()
1695 d = ctx.date()
1696 if df(d[0]):
1696 if df(d[0]):
1697 results[ctx.rev()] = d
1697 results[ctx.rev()] = d
1698
1698
1699 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1699 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1700 rev = ctx.rev()
1700 rev = ctx.rev()
1701 if rev in results:
1701 if rev in results:
1702 ui.status(_("found revision %s from %s\n") %
1702 ui.status(_("found revision %s from %s\n") %
1703 (rev, dateutil.datestr(results[rev])))
1703 (rev, dateutil.datestr(results[rev])))
1704 return '%d' % rev
1704 return '%d' % rev
1705
1705
1706 raise error.Abort(_("revision matching date not found"))
1706 raise error.Abort(_("revision matching date not found"))
1707
1707
1708 def increasingwindows(windowsize=8, sizelimit=512):
1708 def increasingwindows(windowsize=8, sizelimit=512):
1709 while True:
1709 while True:
1710 yield windowsize
1710 yield windowsize
1711 if windowsize < sizelimit:
1711 if windowsize < sizelimit:
1712 windowsize *= 2
1712 windowsize *= 2
1713
1713
1714 def _walkrevs(repo, opts):
1714 def _walkrevs(repo, opts):
1715 # Default --rev value depends on --follow but --follow behavior
1715 # Default --rev value depends on --follow but --follow behavior
1716 # depends on revisions resolved from --rev...
1716 # depends on revisions resolved from --rev...
1717 follow = opts.get('follow') or opts.get('follow_first')
1717 follow = opts.get('follow') or opts.get('follow_first')
1718 if opts.get('rev'):
1718 if opts.get('rev'):
1719 revs = scmutil.revrange(repo, opts['rev'])
1719 revs = scmutil.revrange(repo, opts['rev'])
1720 elif follow and repo.dirstate.p1() == nullid:
1720 elif follow and repo.dirstate.p1() == nullid:
1721 revs = smartset.baseset()
1721 revs = smartset.baseset()
1722 elif follow:
1722 elif follow:
1723 revs = repo.revs('reverse(:.)')
1723 revs = repo.revs('reverse(:.)')
1724 else:
1724 else:
1725 revs = smartset.spanset(repo)
1725 revs = smartset.spanset(repo)
1726 revs.reverse()
1726 revs.reverse()
1727 return revs
1727 return revs
1728
1728
1729 class FileWalkError(Exception):
1729 class FileWalkError(Exception):
1730 pass
1730 pass
1731
1731
1732 def walkfilerevs(repo, match, follow, revs, fncache):
1732 def walkfilerevs(repo, match, follow, revs, fncache):
1733 '''Walks the file history for the matched files.
1733 '''Walks the file history for the matched files.
1734
1734
1735 Returns the changeset revs that are involved in the file history.
1735 Returns the changeset revs that are involved in the file history.
1736
1736
1737 Throws FileWalkError if the file history can't be walked using
1737 Throws FileWalkError if the file history can't be walked using
1738 filelogs alone.
1738 filelogs alone.
1739 '''
1739 '''
1740 wanted = set()
1740 wanted = set()
1741 copies = []
1741 copies = []
1742 minrev, maxrev = min(revs), max(revs)
1742 minrev, maxrev = min(revs), max(revs)
1743 def filerevs(filelog, last):
1743 def filerevs(filelog, last):
1744 """
1744 """
1745 Only files, no patterns. Check the history of each file.
1745 Only files, no patterns. Check the history of each file.
1746
1746
1747 Examines filelog entries within minrev, maxrev linkrev range
1747 Examines filelog entries within minrev, maxrev linkrev range
1748 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1748 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1749 tuples in backwards order
1749 tuples in backwards order
1750 """
1750 """
1751 cl_count = len(repo)
1751 cl_count = len(repo)
1752 revs = []
1752 revs = []
1753 for j in pycompat.xrange(0, last + 1):
1753 for j in pycompat.xrange(0, last + 1):
1754 linkrev = filelog.linkrev(j)
1754 linkrev = filelog.linkrev(j)
1755 if linkrev < minrev:
1755 if linkrev < minrev:
1756 continue
1756 continue
1757 # only yield rev for which we have the changelog, it can
1757 # only yield rev for which we have the changelog, it can
1758 # happen while doing "hg log" during a pull or commit
1758 # happen while doing "hg log" during a pull or commit
1759 if linkrev >= cl_count:
1759 if linkrev >= cl_count:
1760 break
1760 break
1761
1761
1762 parentlinkrevs = []
1762 parentlinkrevs = []
1763 for p in filelog.parentrevs(j):
1763 for p in filelog.parentrevs(j):
1764 if p != nullrev:
1764 if p != nullrev:
1765 parentlinkrevs.append(filelog.linkrev(p))
1765 parentlinkrevs.append(filelog.linkrev(p))
1766 n = filelog.node(j)
1766 n = filelog.node(j)
1767 revs.append((linkrev, parentlinkrevs,
1767 revs.append((linkrev, parentlinkrevs,
1768 follow and filelog.renamed(n)))
1768 follow and filelog.renamed(n)))
1769
1769
1770 return reversed(revs)
1770 return reversed(revs)
1771 def iterfiles():
1771 def iterfiles():
1772 pctx = repo['.']
1772 pctx = repo['.']
1773 for filename in match.files():
1773 for filename in match.files():
1774 if follow:
1774 if follow:
1775 if filename not in pctx:
1775 if filename not in pctx:
1776 raise error.Abort(_('cannot follow file not in parent '
1776 raise error.Abort(_('cannot follow file not in parent '
1777 'revision: "%s"') % filename)
1777 'revision: "%s"') % filename)
1778 yield filename, pctx[filename].filenode()
1778 yield filename, pctx[filename].filenode()
1779 else:
1779 else:
1780 yield filename, None
1780 yield filename, None
1781 for filename_node in copies:
1781 for filename_node in copies:
1782 yield filename_node
1782 yield filename_node
1783
1783
1784 for file_, node in iterfiles():
1784 for file_, node in iterfiles():
1785 filelog = repo.file(file_)
1785 filelog = repo.file(file_)
1786 if not len(filelog):
1786 if not len(filelog):
1787 if node is None:
1787 if node is None:
1788 # A zero count may be a directory or deleted file, so
1788 # A zero count may be a directory or deleted file, so
1789 # try to find matching entries on the slow path.
1789 # try to find matching entries on the slow path.
1790 if follow:
1790 if follow:
1791 raise error.Abort(
1791 raise error.Abort(
1792 _('cannot follow nonexistent file: "%s"') % file_)
1792 _('cannot follow nonexistent file: "%s"') % file_)
1793 raise FileWalkError("Cannot walk via filelog")
1793 raise FileWalkError("Cannot walk via filelog")
1794 else:
1794 else:
1795 continue
1795 continue
1796
1796
1797 if node is None:
1797 if node is None:
1798 last = len(filelog) - 1
1798 last = len(filelog) - 1
1799 else:
1799 else:
1800 last = filelog.rev(node)
1800 last = filelog.rev(node)
1801
1801
1802 # keep track of all ancestors of the file
1802 # keep track of all ancestors of the file
1803 ancestors = {filelog.linkrev(last)}
1803 ancestors = {filelog.linkrev(last)}
1804
1804
1805 # iterate from latest to oldest revision
1805 # iterate from latest to oldest revision
1806 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
1806 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
1807 if not follow:
1807 if not follow:
1808 if rev > maxrev:
1808 if rev > maxrev:
1809 continue
1809 continue
1810 else:
1810 else:
1811 # Note that last might not be the first interesting
1811 # Note that last might not be the first interesting
1812 # rev to us:
1812 # rev to us:
1813 # if the file has been changed after maxrev, we'll
1813 # if the file has been changed after maxrev, we'll
1814 # have linkrev(last) > maxrev, and we still need
1814 # have linkrev(last) > maxrev, and we still need
1815 # to explore the file graph
1815 # to explore the file graph
1816 if rev not in ancestors:
1816 if rev not in ancestors:
1817 continue
1817 continue
1818 # XXX insert 1327 fix here
1818 # XXX insert 1327 fix here
1819 if flparentlinkrevs:
1819 if flparentlinkrevs:
1820 ancestors.update(flparentlinkrevs)
1820 ancestors.update(flparentlinkrevs)
1821
1821
1822 fncache.setdefault(rev, []).append(file_)
1822 fncache.setdefault(rev, []).append(file_)
1823 wanted.add(rev)
1823 wanted.add(rev)
1824 if copied:
1824 if copied:
1825 copies.append(copied)
1825 copies.append(copied)
1826
1826
1827 return wanted
1827 return wanted
1828
1828
1829 class _followfilter(object):
1829 class _followfilter(object):
1830 def __init__(self, repo, onlyfirst=False):
1830 def __init__(self, repo, onlyfirst=False):
1831 self.repo = repo
1831 self.repo = repo
1832 self.startrev = nullrev
1832 self.startrev = nullrev
1833 self.roots = set()
1833 self.roots = set()
1834 self.onlyfirst = onlyfirst
1834 self.onlyfirst = onlyfirst
1835
1835
1836 def match(self, rev):
1836 def match(self, rev):
1837 def realparents(rev):
1837 def realparents(rev):
1838 if self.onlyfirst:
1838 if self.onlyfirst:
1839 return self.repo.changelog.parentrevs(rev)[0:1]
1839 return self.repo.changelog.parentrevs(rev)[0:1]
1840 else:
1840 else:
1841 return filter(lambda x: x != nullrev,
1841 return filter(lambda x: x != nullrev,
1842 self.repo.changelog.parentrevs(rev))
1842 self.repo.changelog.parentrevs(rev))
1843
1843
1844 if self.startrev == nullrev:
1844 if self.startrev == nullrev:
1845 self.startrev = rev
1845 self.startrev = rev
1846 return True
1846 return True
1847
1847
1848 if rev > self.startrev:
1848 if rev > self.startrev:
1849 # forward: all descendants
1849 # forward: all descendants
1850 if not self.roots:
1850 if not self.roots:
1851 self.roots.add(self.startrev)
1851 self.roots.add(self.startrev)
1852 for parent in realparents(rev):
1852 for parent in realparents(rev):
1853 if parent in self.roots:
1853 if parent in self.roots:
1854 self.roots.add(rev)
1854 self.roots.add(rev)
1855 return True
1855 return True
1856 else:
1856 else:
1857 # backwards: all parents
1857 # backwards: all parents
1858 if not self.roots:
1858 if not self.roots:
1859 self.roots.update(realparents(self.startrev))
1859 self.roots.update(realparents(self.startrev))
1860 if rev in self.roots:
1860 if rev in self.roots:
1861 self.roots.remove(rev)
1861 self.roots.remove(rev)
1862 self.roots.update(realparents(rev))
1862 self.roots.update(realparents(rev))
1863 return True
1863 return True
1864
1864
1865 return False
1865 return False
1866
1866
1867 def walkchangerevs(repo, match, opts, prepare):
1867 def walkchangerevs(repo, match, opts, prepare):
1868 '''Iterate over files and the revs in which they changed.
1868 '''Iterate over files and the revs in which they changed.
1869
1869
1870 Callers most commonly need to iterate backwards over the history
1870 Callers most commonly need to iterate backwards over the history
1871 in which they are interested. Doing so has awful (quadratic-looking)
1871 in which they are interested. Doing so has awful (quadratic-looking)
1872 performance, so we use iterators in a "windowed" way.
1872 performance, so we use iterators in a "windowed" way.
1873
1873
1874 We walk a window of revisions in the desired order. Within the
1874 We walk a window of revisions in the desired order. Within the
1875 window, we first walk forwards to gather data, then in the desired
1875 window, we first walk forwards to gather data, then in the desired
1876 order (usually backwards) to display it.
1876 order (usually backwards) to display it.
1877
1877
1878 This function returns an iterator yielding contexts. Before
1878 This function returns an iterator yielding contexts. Before
1879 yielding each context, the iterator will first call the prepare
1879 yielding each context, the iterator will first call the prepare
1880 function on each context in the window in forward order.'''
1880 function on each context in the window in forward order.'''
1881
1881
1882 allfiles = opts.get('all_files')
1882 allfiles = opts.get('all_files')
1883 follow = opts.get('follow') or opts.get('follow_first')
1883 follow = opts.get('follow') or opts.get('follow_first')
1884 revs = _walkrevs(repo, opts)
1884 revs = _walkrevs(repo, opts)
1885 if not revs:
1885 if not revs:
1886 return []
1886 return []
1887 wanted = set()
1887 wanted = set()
1888 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1888 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1889 fncache = {}
1889 fncache = {}
1890 change = repo.__getitem__
1890 change = repo.__getitem__
1891
1891
1892 # First step is to fill wanted, the set of revisions that we want to yield.
1892 # First step is to fill wanted, the set of revisions that we want to yield.
1893 # When it does not induce extra cost, we also fill fncache for revisions in
1893 # When it does not induce extra cost, we also fill fncache for revisions in
1894 # wanted: a cache of filenames that were changed (ctx.files()) and that
1894 # wanted: a cache of filenames that were changed (ctx.files()) and that
1895 # match the file filtering conditions.
1895 # match the file filtering conditions.
1896
1896
1897 if match.always() or allfiles:
1897 if match.always() or allfiles:
1898 # No files, no patterns. Display all revs.
1898 # No files, no patterns. Display all revs.
1899 wanted = revs
1899 wanted = revs
1900 elif not slowpath:
1900 elif not slowpath:
1901 # We only have to read through the filelog to find wanted revisions
1901 # We only have to read through the filelog to find wanted revisions
1902
1902
1903 try:
1903 try:
1904 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1904 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1905 except FileWalkError:
1905 except FileWalkError:
1906 slowpath = True
1906 slowpath = True
1907
1907
1908 # We decided to fall back to the slowpath because at least one
1908 # We decided to fall back to the slowpath because at least one
1909 # of the paths was not a file. Check to see if at least one of them
1909 # of the paths was not a file. Check to see if at least one of them
1910 # existed in history, otherwise simply return
1910 # existed in history, otherwise simply return
1911 for path in match.files():
1911 for path in match.files():
1912 if path == '.' or path in repo.store:
1912 if path == '.' or path in repo.store:
1913 break
1913 break
1914 else:
1914 else:
1915 return []
1915 return []
1916
1916
1917 if slowpath:
1917 if slowpath:
1918 # We have to read the changelog to match filenames against
1918 # We have to read the changelog to match filenames against
1919 # changed files
1919 # changed files
1920
1920
1921 if follow:
1921 if follow:
1922 raise error.Abort(_('can only follow copies/renames for explicit '
1922 raise error.Abort(_('can only follow copies/renames for explicit '
1923 'filenames'))
1923 'filenames'))
1924
1924
1925 # The slow path checks files modified in every changeset.
1925 # The slow path checks files modified in every changeset.
1926 # This is really slow on large repos, so compute the set lazily.
1926 # This is really slow on large repos, so compute the set lazily.
1927 class lazywantedset(object):
1927 class lazywantedset(object):
1928 def __init__(self):
1928 def __init__(self):
1929 self.set = set()
1929 self.set = set()
1930 self.revs = set(revs)
1930 self.revs = set(revs)
1931
1931
1932 # No need to worry about locality here because it will be accessed
1932 # No need to worry about locality here because it will be accessed
1933 # in the same order as the increasing window below.
1933 # in the same order as the increasing window below.
1934 def __contains__(self, value):
1934 def __contains__(self, value):
1935 if value in self.set:
1935 if value in self.set:
1936 return True
1936 return True
1937 elif not value in self.revs:
1937 elif not value in self.revs:
1938 return False
1938 return False
1939 else:
1939 else:
1940 self.revs.discard(value)
1940 self.revs.discard(value)
1941 ctx = change(value)
1941 ctx = change(value)
1942 if allfiles:
1942 if allfiles:
1943 matches = list(ctx.manifest().walk(match))
1943 matches = list(ctx.manifest().walk(match))
1944 else:
1944 else:
1945 matches = [f for f in ctx.files() if match(f)]
1945 matches = [f for f in ctx.files() if match(f)]
1946 if matches:
1946 if matches:
1947 fncache[value] = matches
1947 fncache[value] = matches
1948 self.set.add(value)
1948 self.set.add(value)
1949 return True
1949 return True
1950 return False
1950 return False
1951
1951
1952 def discard(self, value):
1952 def discard(self, value):
1953 self.revs.discard(value)
1953 self.revs.discard(value)
1954 self.set.discard(value)
1954 self.set.discard(value)
1955
1955
1956 wanted = lazywantedset()
1956 wanted = lazywantedset()
1957
1957
1958 # it might be worthwhile to do this in the iterator if the rev range
1958 # it might be worthwhile to do this in the iterator if the rev range
1959 # is descending and the prune args are all within that range
1959 # is descending and the prune args are all within that range
1960 for rev in opts.get('prune', ()):
1960 for rev in opts.get('prune', ()):
1961 rev = repo[rev].rev()
1961 rev = repo[rev].rev()
1962 ff = _followfilter(repo)
1962 ff = _followfilter(repo)
1963 stop = min(revs[0], revs[-1])
1963 stop = min(revs[0], revs[-1])
1964 for x in pycompat.xrange(rev, stop - 1, -1):
1964 for x in pycompat.xrange(rev, stop - 1, -1):
1965 if ff.match(x):
1965 if ff.match(x):
1966 wanted = wanted - [x]
1966 wanted = wanted - [x]
1967
1967
1968 # Now that wanted is correctly initialized, we can iterate over the
1968 # Now that wanted is correctly initialized, we can iterate over the
1969 # revision range, yielding only revisions in wanted.
1969 # revision range, yielding only revisions in wanted.
1970 def iterate():
1970 def iterate():
1971 if follow and match.always():
1971 if follow and match.always():
1972 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1972 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1973 def want(rev):
1973 def want(rev):
1974 return ff.match(rev) and rev in wanted
1974 return ff.match(rev) and rev in wanted
1975 else:
1975 else:
1976 def want(rev):
1976 def want(rev):
1977 return rev in wanted
1977 return rev in wanted
1978
1978
1979 it = iter(revs)
1979 it = iter(revs)
1980 stopiteration = False
1980 stopiteration = False
1981 for windowsize in increasingwindows():
1981 for windowsize in increasingwindows():
1982 nrevs = []
1982 nrevs = []
1983 for i in pycompat.xrange(windowsize):
1983 for i in pycompat.xrange(windowsize):
1984 rev = next(it, None)
1984 rev = next(it, None)
1985 if rev is None:
1985 if rev is None:
1986 stopiteration = True
1986 stopiteration = True
1987 break
1987 break
1988 elif want(rev):
1988 elif want(rev):
1989 nrevs.append(rev)
1989 nrevs.append(rev)
1990 for rev in sorted(nrevs):
1990 for rev in sorted(nrevs):
1991 fns = fncache.get(rev)
1991 fns = fncache.get(rev)
1992 ctx = change(rev)
1992 ctx = change(rev)
1993 if not fns:
1993 if not fns:
1994 def fns_generator():
1994 def fns_generator():
1995 if allfiles:
1995 if allfiles:
1996 fiter = iter(ctx)
1996 fiter = iter(ctx)
1997 else:
1997 else:
1998 fiter = ctx.files()
1998 fiter = ctx.files()
1999 for f in fiter:
1999 for f in fiter:
2000 if match(f):
2000 if match(f):
2001 yield f
2001 yield f
2002 fns = fns_generator()
2002 fns = fns_generator()
2003 prepare(ctx, fns)
2003 prepare(ctx, fns)
2004 for rev in nrevs:
2004 for rev in nrevs:
2005 yield change(rev)
2005 yield change(rev)
2006
2006
2007 if stopiteration:
2007 if stopiteration:
2008 break
2008 break
2009
2009
2010 return iterate()
2010 return iterate()
2011
2011
2012 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2012 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2013 bad = []
2013 bad = []
2014
2014
2015 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2015 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2016 names = []
2016 names = []
2017 wctx = repo[None]
2017 wctx = repo[None]
2018 cca = None
2018 cca = None
2019 abort, warn = scmutil.checkportabilityalert(ui)
2019 abort, warn = scmutil.checkportabilityalert(ui)
2020 if abort or warn:
2020 if abort or warn:
2021 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2021 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2022
2022
2023 match = repo.narrowmatch(match, includeexact=True)
2023 match = repo.narrowmatch(match, includeexact=True)
2024 badmatch = matchmod.badmatch(match, badfn)
2024 badmatch = matchmod.badmatch(match, badfn)
2025 dirstate = repo.dirstate
2025 dirstate = repo.dirstate
2026 # We don't want to just call wctx.walk here, since it would return a lot of
2026 # We don't want to just call wctx.walk here, since it would return a lot of
2027 # clean files, which we aren't interested in and takes time.
2027 # clean files, which we aren't interested in and takes time.
2028 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2028 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2029 unknown=True, ignored=False, full=False)):
2029 unknown=True, ignored=False, full=False)):
2030 exact = match.exact(f)
2030 exact = match.exact(f)
2031 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2031 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2032 if cca:
2032 if cca:
2033 cca(f)
2033 cca(f)
2034 names.append(f)
2034 names.append(f)
2035 if ui.verbose or not exact:
2035 if ui.verbose or not exact:
2036 ui.status(_('adding %s\n') % uipathfn(f),
2036 ui.status(_('adding %s\n') % uipathfn(f),
2037 label='ui.addremove.added')
2037 label='ui.addremove.added')
2038
2038
2039 for subpath in sorted(wctx.substate):
2039 for subpath in sorted(wctx.substate):
2040 sub = wctx.sub(subpath)
2040 sub = wctx.sub(subpath)
2041 try:
2041 try:
2042 submatch = matchmod.subdirmatcher(subpath, match)
2042 submatch = matchmod.subdirmatcher(subpath, match)
2043 subprefix = repo.wvfs.reljoin(prefix, subpath)
2043 subprefix = repo.wvfs.reljoin(prefix, subpath)
2044 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2044 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2045 if opts.get(r'subrepos'):
2045 if opts.get(r'subrepos'):
2046 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2046 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2047 **opts))
2047 **opts))
2048 else:
2048 else:
2049 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2049 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2050 **opts))
2050 **opts))
2051 except error.LookupError:
2051 except error.LookupError:
2052 ui.status(_("skipping missing subrepository: %s\n")
2052 ui.status(_("skipping missing subrepository: %s\n")
2053 % uipathfn(subpath))
2053 % uipathfn(subpath))
2054
2054
2055 if not opts.get(r'dry_run'):
2055 if not opts.get(r'dry_run'):
2056 rejected = wctx.add(names, prefix)
2056 rejected = wctx.add(names, prefix)
2057 bad.extend(f for f in rejected if f in match.files())
2057 bad.extend(f for f in rejected if f in match.files())
2058 return bad
2058 return bad
2059
2059
2060 def addwebdirpath(repo, serverpath, webconf):
2060 def addwebdirpath(repo, serverpath, webconf):
2061 webconf[serverpath] = repo.root
2061 webconf[serverpath] = repo.root
2062 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2062 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2063
2063
2064 for r in repo.revs('filelog("path:.hgsub")'):
2064 for r in repo.revs('filelog("path:.hgsub")'):
2065 ctx = repo[r]
2065 ctx = repo[r]
2066 for subpath in ctx.substate:
2066 for subpath in ctx.substate:
2067 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2067 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2068
2068
2069 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2069 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2070 interactive):
2070 interactive):
2071 if dryrun and interactive:
2071 if dryrun and interactive:
2072 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2072 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2073 bad = []
2073 bad = []
2074 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2074 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2075 wctx = repo[None]
2075 wctx = repo[None]
2076 forgot = []
2076 forgot = []
2077
2077
2078 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2078 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2079 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2079 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2080 if explicitonly:
2080 if explicitonly:
2081 forget = [f for f in forget if match.exact(f)]
2081 forget = [f for f in forget if match.exact(f)]
2082
2082
2083 for subpath in sorted(wctx.substate):
2083 for subpath in sorted(wctx.substate):
2084 sub = wctx.sub(subpath)
2084 sub = wctx.sub(subpath)
2085 submatch = matchmod.subdirmatcher(subpath, match)
2085 submatch = matchmod.subdirmatcher(subpath, match)
2086 subprefix = repo.wvfs.reljoin(prefix, subpath)
2086 subprefix = repo.wvfs.reljoin(prefix, subpath)
2087 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2087 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2088 try:
2088 try:
2089 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2089 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2090 dryrun=dryrun,
2090 dryrun=dryrun,
2091 interactive=interactive)
2091 interactive=interactive)
2092 bad.extend([subpath + '/' + f for f in subbad])
2092 bad.extend([subpath + '/' + f for f in subbad])
2093 forgot.extend([subpath + '/' + f for f in subforgot])
2093 forgot.extend([subpath + '/' + f for f in subforgot])
2094 except error.LookupError:
2094 except error.LookupError:
2095 ui.status(_("skipping missing subrepository: %s\n")
2095 ui.status(_("skipping missing subrepository: %s\n")
2096 % uipathfn(subpath))
2096 % uipathfn(subpath))
2097
2097
2098 if not explicitonly:
2098 if not explicitonly:
2099 for f in match.files():
2099 for f in match.files():
2100 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2100 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2101 if f not in forgot:
2101 if f not in forgot:
2102 if repo.wvfs.exists(f):
2102 if repo.wvfs.exists(f):
2103 # Don't complain if the exact case match wasn't given.
2103 # Don't complain if the exact case match wasn't given.
2104 # But don't do this until after checking 'forgot', so
2104 # But don't do this until after checking 'forgot', so
2105 # that subrepo files aren't normalized, and this op is
2105 # that subrepo files aren't normalized, and this op is
2106 # purely from data cached by the status walk above.
2106 # purely from data cached by the status walk above.
2107 if repo.dirstate.normalize(f) in repo.dirstate:
2107 if repo.dirstate.normalize(f) in repo.dirstate:
2108 continue
2108 continue
2109 ui.warn(_('not removing %s: '
2109 ui.warn(_('not removing %s: '
2110 'file is already untracked\n')
2110 'file is already untracked\n')
2111 % uipathfn(f))
2111 % uipathfn(f))
2112 bad.append(f)
2112 bad.append(f)
2113
2113
2114 if interactive:
2114 if interactive:
2115 responses = _('[Ynsa?]'
2115 responses = _('[Ynsa?]'
2116 '$$ &Yes, forget this file'
2116 '$$ &Yes, forget this file'
2117 '$$ &No, skip this file'
2117 '$$ &No, skip this file'
2118 '$$ &Skip remaining files'
2118 '$$ &Skip remaining files'
2119 '$$ Include &all remaining files'
2119 '$$ Include &all remaining files'
2120 '$$ &? (display help)')
2120 '$$ &? (display help)')
2121 for filename in forget[:]:
2121 for filename in forget[:]:
2122 r = ui.promptchoice(_('forget %s %s') %
2122 r = ui.promptchoice(_('forget %s %s') %
2123 (uipathfn(filename), responses))
2123 (uipathfn(filename), responses))
2124 if r == 4: # ?
2124 if r == 4: # ?
2125 while r == 4:
2125 while r == 4:
2126 for c, t in ui.extractchoices(responses)[1]:
2126 for c, t in ui.extractchoices(responses)[1]:
2127 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2127 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2128 r = ui.promptchoice(_('forget %s %s') %
2128 r = ui.promptchoice(_('forget %s %s') %
2129 (uipathfn(filename), responses))
2129 (uipathfn(filename), responses))
2130 if r == 0: # yes
2130 if r == 0: # yes
2131 continue
2131 continue
2132 elif r == 1: # no
2132 elif r == 1: # no
2133 forget.remove(filename)
2133 forget.remove(filename)
2134 elif r == 2: # Skip
2134 elif r == 2: # Skip
2135 fnindex = forget.index(filename)
2135 fnindex = forget.index(filename)
2136 del forget[fnindex:]
2136 del forget[fnindex:]
2137 break
2137 break
2138 elif r == 3: # All
2138 elif r == 3: # All
2139 break
2139 break
2140
2140
2141 for f in forget:
2141 for f in forget:
2142 if ui.verbose or not match.exact(f) or interactive:
2142 if ui.verbose or not match.exact(f) or interactive:
2143 ui.status(_('removing %s\n') % uipathfn(f),
2143 ui.status(_('removing %s\n') % uipathfn(f),
2144 label='ui.addremove.removed')
2144 label='ui.addremove.removed')
2145
2145
2146 if not dryrun:
2146 if not dryrun:
2147 rejected = wctx.forget(forget, prefix)
2147 rejected = wctx.forget(forget, prefix)
2148 bad.extend(f for f in rejected if f in match.files())
2148 bad.extend(f for f in rejected if f in match.files())
2149 forgot.extend(f for f in forget if f not in rejected)
2149 forgot.extend(f for f in forget if f not in rejected)
2150 return bad, forgot
2150 return bad, forgot
2151
2151
2152 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2152 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2153 ret = 1
2153 ret = 1
2154
2154
2155 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2155 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2156 for f in ctx.matches(m):
2156 for f in ctx.matches(m):
2157 fm.startitem()
2157 fm.startitem()
2158 fm.context(ctx=ctx)
2158 fm.context(ctx=ctx)
2159 if needsfctx:
2159 if needsfctx:
2160 fc = ctx[f]
2160 fc = ctx[f]
2161 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2161 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2162 fm.data(path=f)
2162 fm.data(path=f)
2163 fm.plain(fmt % uipathfn(f))
2163 fm.plain(fmt % uipathfn(f))
2164 ret = 0
2164 ret = 0
2165
2165
2166 for subpath in sorted(ctx.substate):
2166 for subpath in sorted(ctx.substate):
2167 submatch = matchmod.subdirmatcher(subpath, m)
2167 submatch = matchmod.subdirmatcher(subpath, m)
2168 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2168 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2169 if (subrepos or m.exact(subpath) or any(submatch.files())):
2169 if (subrepos or m.exact(subpath) or any(submatch.files())):
2170 sub = ctx.sub(subpath)
2170 sub = ctx.sub(subpath)
2171 try:
2171 try:
2172 recurse = m.exact(subpath) or subrepos
2172 recurse = m.exact(subpath) or subrepos
2173 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2173 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2174 recurse) == 0:
2174 recurse) == 0:
2175 ret = 0
2175 ret = 0
2176 except error.LookupError:
2176 except error.LookupError:
2177 ui.status(_("skipping missing subrepository: %s\n")
2177 ui.status(_("skipping missing subrepository: %s\n")
2178 % uipathfn(subpath))
2178 % uipathfn(subpath))
2179
2179
2180 return ret
2180 return ret
2181
2181
2182 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2182 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2183 warnings=None):
2183 warnings=None):
2184 ret = 0
2184 ret = 0
2185 s = repo.status(match=m, clean=True)
2185 s = repo.status(match=m, clean=True)
2186 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2186 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2187
2187
2188 wctx = repo[None]
2188 wctx = repo[None]
2189
2189
2190 if warnings is None:
2190 if warnings is None:
2191 warnings = []
2191 warnings = []
2192 warn = True
2192 warn = True
2193 else:
2193 else:
2194 warn = False
2194 warn = False
2195
2195
2196 subs = sorted(wctx.substate)
2196 subs = sorted(wctx.substate)
2197 progress = ui.makeprogress(_('searching'), total=len(subs),
2197 progress = ui.makeprogress(_('searching'), total=len(subs),
2198 unit=_('subrepos'))
2198 unit=_('subrepos'))
2199 for subpath in subs:
2199 for subpath in subs:
2200 submatch = matchmod.subdirmatcher(subpath, m)
2200 submatch = matchmod.subdirmatcher(subpath, m)
2201 subprefix = repo.wvfs.reljoin(prefix, subpath)
2201 subprefix = repo.wvfs.reljoin(prefix, subpath)
2202 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2202 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2203 if subrepos or m.exact(subpath) or any(submatch.files()):
2203 if subrepos or m.exact(subpath) or any(submatch.files()):
2204 progress.increment()
2204 progress.increment()
2205 sub = wctx.sub(subpath)
2205 sub = wctx.sub(subpath)
2206 try:
2206 try:
2207 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2207 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2208 force, subrepos, dryrun, warnings):
2208 force, subrepos, dryrun, warnings):
2209 ret = 1
2209 ret = 1
2210 except error.LookupError:
2210 except error.LookupError:
2211 warnings.append(_("skipping missing subrepository: %s\n")
2211 warnings.append(_("skipping missing subrepository: %s\n")
2212 % uipathfn(subpath))
2212 % uipathfn(subpath))
2213 progress.complete()
2213 progress.complete()
2214
2214
2215 # warn about failure to delete explicit files/dirs
2215 # warn about failure to delete explicit files/dirs
2216 deleteddirs = util.dirs(deleted)
2216 deleteddirs = util.dirs(deleted)
2217 files = m.files()
2217 files = m.files()
2218 progress = ui.makeprogress(_('deleting'), total=len(files),
2218 progress = ui.makeprogress(_('deleting'), total=len(files),
2219 unit=_('files'))
2219 unit=_('files'))
2220 for f in files:
2220 for f in files:
2221 def insubrepo():
2221 def insubrepo():
2222 for subpath in wctx.substate:
2222 for subpath in wctx.substate:
2223 if f.startswith(subpath + '/'):
2223 if f.startswith(subpath + '/'):
2224 return True
2224 return True
2225 return False
2225 return False
2226
2226
2227 progress.increment()
2227 progress.increment()
2228 isdir = f in deleteddirs or wctx.hasdir(f)
2228 isdir = f in deleteddirs or wctx.hasdir(f)
2229 if (f in repo.dirstate or isdir or f == '.'
2229 if (f in repo.dirstate or isdir or f == '.'
2230 or insubrepo() or f in subs):
2230 or insubrepo() or f in subs):
2231 continue
2231 continue
2232
2232
2233 if repo.wvfs.exists(f):
2233 if repo.wvfs.exists(f):
2234 if repo.wvfs.isdir(f):
2234 if repo.wvfs.isdir(f):
2235 warnings.append(_('not removing %s: no tracked files\n')
2235 warnings.append(_('not removing %s: no tracked files\n')
2236 % uipathfn(f))
2236 % uipathfn(f))
2237 else:
2237 else:
2238 warnings.append(_('not removing %s: file is untracked\n')
2238 warnings.append(_('not removing %s: file is untracked\n')
2239 % uipathfn(f))
2239 % uipathfn(f))
2240 # missing files will generate a warning elsewhere
2240 # missing files will generate a warning elsewhere
2241 ret = 1
2241 ret = 1
2242 progress.complete()
2242 progress.complete()
2243
2243
2244 if force:
2244 if force:
2245 list = modified + deleted + clean + added
2245 list = modified + deleted + clean + added
2246 elif after:
2246 elif after:
2247 list = deleted
2247 list = deleted
2248 remaining = modified + added + clean
2248 remaining = modified + added + clean
2249 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2249 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2250 unit=_('files'))
2250 unit=_('files'))
2251 for f in remaining:
2251 for f in remaining:
2252 progress.increment()
2252 progress.increment()
2253 if ui.verbose or (f in files):
2253 if ui.verbose or (f in files):
2254 warnings.append(_('not removing %s: file still exists\n')
2254 warnings.append(_('not removing %s: file still exists\n')
2255 % uipathfn(f))
2255 % uipathfn(f))
2256 ret = 1
2256 ret = 1
2257 progress.complete()
2257 progress.complete()
2258 else:
2258 else:
2259 list = deleted + clean
2259 list = deleted + clean
2260 progress = ui.makeprogress(_('skipping'),
2260 progress = ui.makeprogress(_('skipping'),
2261 total=(len(modified) + len(added)),
2261 total=(len(modified) + len(added)),
2262 unit=_('files'))
2262 unit=_('files'))
2263 for f in modified:
2263 for f in modified:
2264 progress.increment()
2264 progress.increment()
2265 warnings.append(_('not removing %s: file is modified (use -f'
2265 warnings.append(_('not removing %s: file is modified (use -f'
2266 ' to force removal)\n') % uipathfn(f))
2266 ' to force removal)\n') % uipathfn(f))
2267 ret = 1
2267 ret = 1
2268 for f in added:
2268 for f in added:
2269 progress.increment()
2269 progress.increment()
2270 warnings.append(_("not removing %s: file has been marked for add"
2270 warnings.append(_("not removing %s: file has been marked for add"
2271 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2271 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2272 ret = 1
2272 ret = 1
2273 progress.complete()
2273 progress.complete()
2274
2274
2275 list = sorted(list)
2275 list = sorted(list)
2276 progress = ui.makeprogress(_('deleting'), total=len(list),
2276 progress = ui.makeprogress(_('deleting'), total=len(list),
2277 unit=_('files'))
2277 unit=_('files'))
2278 for f in list:
2278 for f in list:
2279 if ui.verbose or not m.exact(f):
2279 if ui.verbose or not m.exact(f):
2280 progress.increment()
2280 progress.increment()
2281 ui.status(_('removing %s\n') % uipathfn(f),
2281 ui.status(_('removing %s\n') % uipathfn(f),
2282 label='ui.addremove.removed')
2282 label='ui.addremove.removed')
2283 progress.complete()
2283 progress.complete()
2284
2284
2285 if not dryrun:
2285 if not dryrun:
2286 with repo.wlock():
2286 with repo.wlock():
2287 if not after:
2287 if not after:
2288 for f in list:
2288 for f in list:
2289 if f in added:
2289 if f in added:
2290 continue # we never unlink added files on remove
2290 continue # we never unlink added files on remove
2291 rmdir = repo.ui.configbool('experimental',
2291 rmdir = repo.ui.configbool('experimental',
2292 'removeemptydirs')
2292 'removeemptydirs')
2293 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2293 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2294 repo[None].forget(list)
2294 repo[None].forget(list)
2295
2295
2296 if warn:
2296 if warn:
2297 for warning in warnings:
2297 for warning in warnings:
2298 ui.warn(warning)
2298 ui.warn(warning)
2299
2299
2300 return ret
2300 return ret
2301
2301
2302 def _catfmtneedsdata(fm):
2302 def _catfmtneedsdata(fm):
2303 return not fm.datahint() or 'data' in fm.datahint()
2303 return not fm.datahint() or 'data' in fm.datahint()
2304
2304
2305 def _updatecatformatter(fm, ctx, matcher, path, decode):
2305 def _updatecatformatter(fm, ctx, matcher, path, decode):
2306 """Hook for adding data to the formatter used by ``hg cat``.
2306 """Hook for adding data to the formatter used by ``hg cat``.
2307
2307
2308 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2308 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2309 this method first."""
2309 this method first."""
2310
2310
2311 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2311 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2312 # wasn't requested.
2312 # wasn't requested.
2313 data = b''
2313 data = b''
2314 if _catfmtneedsdata(fm):
2314 if _catfmtneedsdata(fm):
2315 data = ctx[path].data()
2315 data = ctx[path].data()
2316 if decode:
2316 if decode:
2317 data = ctx.repo().wwritedata(path, data)
2317 data = ctx.repo().wwritedata(path, data)
2318 fm.startitem()
2318 fm.startitem()
2319 fm.context(ctx=ctx)
2319 fm.context(ctx=ctx)
2320 fm.write('data', '%s', data)
2320 fm.write('data', '%s', data)
2321 fm.data(path=path)
2321 fm.data(path=path)
2322
2322
2323 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2323 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2324 err = 1
2324 err = 1
2325 opts = pycompat.byteskwargs(opts)
2325 opts = pycompat.byteskwargs(opts)
2326
2326
2327 def write(path):
2327 def write(path):
2328 filename = None
2328 filename = None
2329 if fntemplate:
2329 if fntemplate:
2330 filename = makefilename(ctx, fntemplate,
2330 filename = makefilename(ctx, fntemplate,
2331 pathname=os.path.join(prefix, path))
2331 pathname=os.path.join(prefix, path))
2332 # attempt to create the directory if it does not already exist
2332 # attempt to create the directory if it does not already exist
2333 try:
2333 try:
2334 os.makedirs(os.path.dirname(filename))
2334 os.makedirs(os.path.dirname(filename))
2335 except OSError:
2335 except OSError:
2336 pass
2336 pass
2337 with formatter.maybereopen(basefm, filename) as fm:
2337 with formatter.maybereopen(basefm, filename) as fm:
2338 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2338 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2339
2339
2340 # Automation often uses hg cat on single files, so special case it
2340 # Automation often uses hg cat on single files, so special case it
2341 # for performance to avoid the cost of parsing the manifest.
2341 # for performance to avoid the cost of parsing the manifest.
2342 if len(matcher.files()) == 1 and not matcher.anypats():
2342 if len(matcher.files()) == 1 and not matcher.anypats():
2343 file = matcher.files()[0]
2343 file = matcher.files()[0]
2344 mfl = repo.manifestlog
2344 mfl = repo.manifestlog
2345 mfnode = ctx.manifestnode()
2345 mfnode = ctx.manifestnode()
2346 try:
2346 try:
2347 if mfnode and mfl[mfnode].find(file)[0]:
2347 if mfnode and mfl[mfnode].find(file)[0]:
2348 if _catfmtneedsdata(basefm):
2348 if _catfmtneedsdata(basefm):
2349 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2349 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2350 write(file)
2350 write(file)
2351 return 0
2351 return 0
2352 except KeyError:
2352 except KeyError:
2353 pass
2353 pass
2354
2354
2355 if _catfmtneedsdata(basefm):
2355 if _catfmtneedsdata(basefm):
2356 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2356 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2357
2357
2358 for abs in ctx.walk(matcher):
2358 for abs in ctx.walk(matcher):
2359 write(abs)
2359 write(abs)
2360 err = 0
2360 err = 0
2361
2361
2362 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2362 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2363 for subpath in sorted(ctx.substate):
2363 for subpath in sorted(ctx.substate):
2364 sub = ctx.sub(subpath)
2364 sub = ctx.sub(subpath)
2365 try:
2365 try:
2366 submatch = matchmod.subdirmatcher(subpath, matcher)
2366 submatch = matchmod.subdirmatcher(subpath, matcher)
2367 subprefix = os.path.join(prefix, subpath)
2367 subprefix = os.path.join(prefix, subpath)
2368 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2368 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2369 **pycompat.strkwargs(opts)):
2369 **pycompat.strkwargs(opts)):
2370 err = 0
2370 err = 0
2371 except error.RepoLookupError:
2371 except error.RepoLookupError:
2372 ui.status(_("skipping missing subrepository: %s\n") %
2372 ui.status(_("skipping missing subrepository: %s\n") %
2373 uipathfn(subpath))
2373 uipathfn(subpath))
2374
2374
2375 return err
2375 return err
2376
2376
2377 def commit(ui, repo, commitfunc, pats, opts):
2377 def commit(ui, repo, commitfunc, pats, opts):
2378 '''commit the specified files or all outstanding changes'''
2378 '''commit the specified files or all outstanding changes'''
2379 date = opts.get('date')
2379 date = opts.get('date')
2380 if date:
2380 if date:
2381 opts['date'] = dateutil.parsedate(date)
2381 opts['date'] = dateutil.parsedate(date)
2382 message = logmessage(ui, opts)
2382 message = logmessage(ui, opts)
2383 matcher = scmutil.match(repo[None], pats, opts)
2383 matcher = scmutil.match(repo[None], pats, opts)
2384
2384
2385 dsguard = None
2385 dsguard = None
2386 # extract addremove carefully -- this function can be called from a command
2386 # extract addremove carefully -- this function can be called from a command
2387 # that doesn't support addremove
2387 # that doesn't support addremove
2388 if opts.get('addremove'):
2388 if opts.get('addremove'):
2389 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2389 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2390 with dsguard or util.nullcontextmanager():
2390 with dsguard or util.nullcontextmanager():
2391 if dsguard:
2391 if dsguard:
2392 relative = scmutil.anypats(pats, opts)
2392 relative = scmutil.anypats(pats, opts)
2393 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2393 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2394 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2394 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2395 raise error.Abort(
2395 raise error.Abort(
2396 _("failed to mark all new/missing files as added/removed"))
2396 _("failed to mark all new/missing files as added/removed"))
2397
2397
2398 return commitfunc(ui, repo, message, matcher, opts)
2398 return commitfunc(ui, repo, message, matcher, opts)
2399
2399
2400 def samefile(f, ctx1, ctx2):
2400 def samefile(f, ctx1, ctx2):
2401 if f in ctx1.manifest():
2401 if f in ctx1.manifest():
2402 a = ctx1.filectx(f)
2402 a = ctx1.filectx(f)
2403 if f in ctx2.manifest():
2403 if f in ctx2.manifest():
2404 b = ctx2.filectx(f)
2404 b = ctx2.filectx(f)
2405 return (not a.cmp(b)
2405 return (not a.cmp(b)
2406 and a.flags() == b.flags())
2406 and a.flags() == b.flags())
2407 else:
2407 else:
2408 return False
2408 return False
2409 else:
2409 else:
2410 return f not in ctx2.manifest()
2410 return f not in ctx2.manifest()
2411
2411
2412 def amend(ui, repo, old, extra, pats, opts):
2412 def amend(ui, repo, old, extra, pats, opts):
2413 # avoid cycle context -> subrepo -> cmdutil
2413 # avoid cycle context -> subrepo -> cmdutil
2414 from . import context
2414 from . import context
2415
2415
2416 # amend will reuse the existing user if not specified, but the obsolete
2416 # amend will reuse the existing user if not specified, but the obsolete
2417 # marker creation requires that the current user's name is specified.
2417 # marker creation requires that the current user's name is specified.
2418 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2418 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2419 ui.username() # raise exception if username not set
2419 ui.username() # raise exception if username not set
2420
2420
2421 ui.note(_('amending changeset %s\n') % old)
2421 ui.note(_('amending changeset %s\n') % old)
2422 base = old.p1()
2422 base = old.p1()
2423
2423
2424 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2424 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2425 # Participating changesets:
2425 # Participating changesets:
2426 #
2426 #
2427 # wctx o - workingctx that contains changes from working copy
2427 # wctx o - workingctx that contains changes from working copy
2428 # | to go into amending commit
2428 # | to go into amending commit
2429 # |
2429 # |
2430 # old o - changeset to amend
2430 # old o - changeset to amend
2431 # |
2431 # |
2432 # base o - first parent of the changeset to amend
2432 # base o - first parent of the changeset to amend
2433 wctx = repo[None]
2433 wctx = repo[None]
2434
2434
2435 # Copy to avoid mutating input
2435 # Copy to avoid mutating input
2436 extra = extra.copy()
2436 extra = extra.copy()
2437 # Update extra dict from amended commit (e.g. to preserve graft
2437 # Update extra dict from amended commit (e.g. to preserve graft
2438 # source)
2438 # source)
2439 extra.update(old.extra())
2439 extra.update(old.extra())
2440
2440
2441 # Also update it from the from the wctx
2441 # Also update it from the from the wctx
2442 extra.update(wctx.extra())
2442 extra.update(wctx.extra())
2443
2443
2444 user = opts.get('user') or old.user()
2444 user = opts.get('user') or old.user()
2445
2445
2446 datemaydiffer = False # date-only change should be ignored?
2446 datemaydiffer = False # date-only change should be ignored?
2447 if opts.get('date') and opts.get('currentdate'):
2447 if opts.get('date') and opts.get('currentdate'):
2448 raise error.Abort(_('--date and --currentdate are mutually '
2448 raise error.Abort(_('--date and --currentdate are mutually '
2449 'exclusive'))
2449 'exclusive'))
2450 if opts.get('date'):
2450 if opts.get('date'):
2451 date = dateutil.parsedate(opts.get('date'))
2451 date = dateutil.parsedate(opts.get('date'))
2452 elif opts.get('currentdate'):
2452 elif opts.get('currentdate'):
2453 date = dateutil.makedate()
2453 date = dateutil.makedate()
2454 elif (ui.configbool('rewrite', 'update-timestamp')
2454 elif (ui.configbool('rewrite', 'update-timestamp')
2455 and opts.get('currentdate') is None):
2455 and opts.get('currentdate') is None):
2456 date = dateutil.makedate()
2456 date = dateutil.makedate()
2457 datemaydiffer = True
2457 datemaydiffer = True
2458 else:
2458 else:
2459 date = old.date()
2459 date = old.date()
2460
2460
2461 if len(old.parents()) > 1:
2461 if len(old.parents()) > 1:
2462 # ctx.files() isn't reliable for merges, so fall back to the
2462 # ctx.files() isn't reliable for merges, so fall back to the
2463 # slower repo.status() method
2463 # slower repo.status() method
2464 files = {fn for st in base.status(old)[:3] for fn in st}
2464 files = {fn for st in base.status(old)[:3] for fn in st}
2465 else:
2465 else:
2466 files = set(old.files())
2466 files = set(old.files())
2467
2467
2468 # add/remove the files to the working copy if the "addremove" option
2468 # add/remove the files to the working copy if the "addremove" option
2469 # was specified.
2469 # was specified.
2470 matcher = scmutil.match(wctx, pats, opts)
2470 matcher = scmutil.match(wctx, pats, opts)
2471 relative = scmutil.anypats(pats, opts)
2471 relative = scmutil.anypats(pats, opts)
2472 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2472 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2473 if (opts.get('addremove')
2473 if (opts.get('addremove')
2474 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2474 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2475 raise error.Abort(
2475 raise error.Abort(
2476 _("failed to mark all new/missing files as added/removed"))
2476 _("failed to mark all new/missing files as added/removed"))
2477
2477
2478 # Check subrepos. This depends on in-place wctx._status update in
2478 # Check subrepos. This depends on in-place wctx._status update in
2479 # subrepo.precommit(). To minimize the risk of this hack, we do
2479 # subrepo.precommit(). To minimize the risk of this hack, we do
2480 # nothing if .hgsub does not exist.
2480 # nothing if .hgsub does not exist.
2481 if '.hgsub' in wctx or '.hgsub' in old:
2481 if '.hgsub' in wctx or '.hgsub' in old:
2482 subs, commitsubs, newsubstate = subrepoutil.precommit(
2482 subs, commitsubs, newsubstate = subrepoutil.precommit(
2483 ui, wctx, wctx._status, matcher)
2483 ui, wctx, wctx._status, matcher)
2484 # amend should abort if commitsubrepos is enabled
2484 # amend should abort if commitsubrepos is enabled
2485 assert not commitsubs
2485 assert not commitsubs
2486 if subs:
2486 if subs:
2487 subrepoutil.writestate(repo, newsubstate)
2487 subrepoutil.writestate(repo, newsubstate)
2488
2488
2489 ms = mergemod.mergestate.read(repo)
2489 ms = mergemod.mergestate.read(repo)
2490 mergeutil.checkunresolved(ms)
2490 mergeutil.checkunresolved(ms)
2491
2491
2492 filestoamend = set(f for f in wctx.files() if matcher(f))
2492 filestoamend = set(f for f in wctx.files() if matcher(f))
2493
2493
2494 changes = (len(filestoamend) > 0)
2494 changes = (len(filestoamend) > 0)
2495 if changes:
2495 if changes:
2496 # Recompute copies (avoid recording a -> b -> a)
2496 # Recompute copies (avoid recording a -> b -> a)
2497 copied = copies.pathcopies(base, wctx, matcher)
2497 copied = copies.pathcopies(base, wctx, matcher)
2498 if old.p2:
2498 if old.p2:
2499 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2499 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2500
2500
2501 # Prune files which were reverted by the updates: if old
2501 # Prune files which were reverted by the updates: if old
2502 # introduced file X and the file was renamed in the working
2502 # introduced file X and the file was renamed in the working
2503 # copy, then those two files are the same and
2503 # copy, then those two files are the same and
2504 # we can discard X from our list of files. Likewise if X
2504 # we can discard X from our list of files. Likewise if X
2505 # was removed, it's no longer relevant. If X is missing (aka
2505 # was removed, it's no longer relevant. If X is missing (aka
2506 # deleted), old X must be preserved.
2506 # deleted), old X must be preserved.
2507 files.update(filestoamend)
2507 files.update(filestoamend)
2508 files = [f for f in files if (not samefile(f, wctx, base)
2508 files = [f for f in files if (not samefile(f, wctx, base)
2509 or f in wctx.deleted())]
2509 or f in wctx.deleted())]
2510
2510
2511 def filectxfn(repo, ctx_, path):
2511 def filectxfn(repo, ctx_, path):
2512 try:
2512 try:
2513 # If the file being considered is not amongst the files
2513 # If the file being considered is not amongst the files
2514 # to be amended, we should return the file context from the
2514 # to be amended, we should return the file context from the
2515 # old changeset. This avoids issues when only some files in
2515 # old changeset. This avoids issues when only some files in
2516 # the working copy are being amended but there are also
2516 # the working copy are being amended but there are also
2517 # changes to other files from the old changeset.
2517 # changes to other files from the old changeset.
2518 if path not in filestoamend:
2518 if path not in filestoamend:
2519 return old.filectx(path)
2519 return old.filectx(path)
2520
2520
2521 # Return None for removed files.
2521 # Return None for removed files.
2522 if path in wctx.removed():
2522 if path in wctx.removed():
2523 return None
2523 return None
2524
2524
2525 fctx = wctx[path]
2525 fctx = wctx[path]
2526 flags = fctx.flags()
2526 flags = fctx.flags()
2527 mctx = context.memfilectx(repo, ctx_,
2527 mctx = context.memfilectx(repo, ctx_,
2528 fctx.path(), fctx.data(),
2528 fctx.path(), fctx.data(),
2529 islink='l' in flags,
2529 islink='l' in flags,
2530 isexec='x' in flags,
2530 isexec='x' in flags,
2531 copysource=copied.get(path))
2531 copysource=copied.get(path))
2532 return mctx
2532 return mctx
2533 except KeyError:
2533 except KeyError:
2534 return None
2534 return None
2535 else:
2535 else:
2536 ui.note(_('copying changeset %s to %s\n') % (old, base))
2536 ui.note(_('copying changeset %s to %s\n') % (old, base))
2537
2537
2538 # Use version of files as in the old cset
2538 # Use version of files as in the old cset
2539 def filectxfn(repo, ctx_, path):
2539 def filectxfn(repo, ctx_, path):
2540 try:
2540 try:
2541 return old.filectx(path)
2541 return old.filectx(path)
2542 except KeyError:
2542 except KeyError:
2543 return None
2543 return None
2544
2544
2545 # See if we got a message from -m or -l, if not, open the editor with
2545 # See if we got a message from -m or -l, if not, open the editor with
2546 # the message of the changeset to amend.
2546 # the message of the changeset to amend.
2547 message = logmessage(ui, opts)
2547 message = logmessage(ui, opts)
2548
2548
2549 editform = mergeeditform(old, 'commit.amend')
2549 editform = mergeeditform(old, 'commit.amend')
2550
2550
2551 if not message:
2551 if not message:
2552 message = old.description()
2552 message = old.description()
2553 # Default if message isn't provided and --edit is not passed is to
2553 # Default if message isn't provided and --edit is not passed is to
2554 # invoke editor, but allow --no-edit. If somehow we don't have any
2554 # invoke editor, but allow --no-edit. If somehow we don't have any
2555 # description, let's always start the editor.
2555 # description, let's always start the editor.
2556 doedit = not message or opts.get('edit') in [True, None]
2556 doedit = not message or opts.get('edit') in [True, None]
2557 else:
2557 else:
2558 # Default if message is provided is to not invoke editor, but allow
2558 # Default if message is provided is to not invoke editor, but allow
2559 # --edit.
2559 # --edit.
2560 doedit = opts.get('edit') is True
2560 doedit = opts.get('edit') is True
2561 editor = getcommiteditor(edit=doedit, editform=editform)
2561 editor = getcommiteditor(edit=doedit, editform=editform)
2562
2562
2563 pureextra = extra.copy()
2563 pureextra = extra.copy()
2564 extra['amend_source'] = old.hex()
2564 extra['amend_source'] = old.hex()
2565
2565
2566 new = context.memctx(repo,
2566 new = context.memctx(repo,
2567 parents=[base.node(), old.p2().node()],
2567 parents=[base.node(), old.p2().node()],
2568 text=message,
2568 text=message,
2569 files=files,
2569 files=files,
2570 filectxfn=filectxfn,
2570 filectxfn=filectxfn,
2571 user=user,
2571 user=user,
2572 date=date,
2572 date=date,
2573 extra=extra,
2573 extra=extra,
2574 editor=editor)
2574 editor=editor)
2575
2575
2576 newdesc = changelog.stripdesc(new.description())
2576 newdesc = changelog.stripdesc(new.description())
2577 if ((not changes)
2577 if ((not changes)
2578 and newdesc == old.description()
2578 and newdesc == old.description()
2579 and user == old.user()
2579 and user == old.user()
2580 and (date == old.date() or datemaydiffer)
2580 and (date == old.date() or datemaydiffer)
2581 and pureextra == old.extra()):
2581 and pureextra == old.extra()):
2582 # nothing changed. continuing here would create a new node
2582 # nothing changed. continuing here would create a new node
2583 # anyway because of the amend_source noise.
2583 # anyway because of the amend_source noise.
2584 #
2584 #
2585 # This not what we expect from amend.
2585 # This not what we expect from amend.
2586 return old.node()
2586 return old.node()
2587
2587
2588 commitphase = None
2588 commitphase = None
2589 if opts.get('secret'):
2589 if opts.get('secret'):
2590 commitphase = phases.secret
2590 commitphase = phases.secret
2591 newid = repo.commitctx(new)
2591 newid = repo.commitctx(new)
2592
2592
2593 # Reroute the working copy parent to the new changeset
2593 # Reroute the working copy parent to the new changeset
2594 repo.setparents(newid, nullid)
2594 repo.setparents(newid, nullid)
2595 mapping = {old.node(): (newid,)}
2595 mapping = {old.node(): (newid,)}
2596 obsmetadata = None
2596 obsmetadata = None
2597 if opts.get('note'):
2597 if opts.get('note'):
2598 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2598 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2599 backup = ui.configbool('rewrite', 'backup-bundle')
2599 backup = ui.configbool('rewrite', 'backup-bundle')
2600 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2600 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2601 fixphase=True, targetphase=commitphase,
2601 fixphase=True, targetphase=commitphase,
2602 backup=backup)
2602 backup=backup)
2603
2603
2604 # Fixing the dirstate because localrepo.commitctx does not update
2604 # Fixing the dirstate because localrepo.commitctx does not update
2605 # it. This is rather convenient because we did not need to update
2605 # it. This is rather convenient because we did not need to update
2606 # the dirstate for all the files in the new commit which commitctx
2606 # the dirstate for all the files in the new commit which commitctx
2607 # could have done if it updated the dirstate. Now, we can
2607 # could have done if it updated the dirstate. Now, we can
2608 # selectively update the dirstate only for the amended files.
2608 # selectively update the dirstate only for the amended files.
2609 dirstate = repo.dirstate
2609 dirstate = repo.dirstate
2610
2610
2611 # Update the state of the files which were added and
2611 # Update the state of the files which were added and
2612 # and modified in the amend to "normal" in the dirstate.
2612 # and modified in the amend to "normal" in the dirstate.
2613 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2613 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2614 for f in normalfiles:
2614 for f in normalfiles:
2615 dirstate.normal(f)
2615 dirstate.normal(f)
2616
2616
2617 # Update the state of files which were removed in the amend
2617 # Update the state of files which were removed in the amend
2618 # to "removed" in the dirstate.
2618 # to "removed" in the dirstate.
2619 removedfiles = set(wctx.removed()) & filestoamend
2619 removedfiles = set(wctx.removed()) & filestoamend
2620 for f in removedfiles:
2620 for f in removedfiles:
2621 dirstate.drop(f)
2621 dirstate.drop(f)
2622
2622
2623 return newid
2623 return newid
2624
2624
2625 def commiteditor(repo, ctx, subs, editform=''):
2625 def commiteditor(repo, ctx, subs, editform=''):
2626 if ctx.description():
2626 if ctx.description():
2627 return ctx.description()
2627 return ctx.description()
2628 return commitforceeditor(repo, ctx, subs, editform=editform,
2628 return commitforceeditor(repo, ctx, subs, editform=editform,
2629 unchangedmessagedetection=True)
2629 unchangedmessagedetection=True)
2630
2630
2631 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2631 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2632 editform='', unchangedmessagedetection=False):
2632 editform='', unchangedmessagedetection=False):
2633 if not extramsg:
2633 if not extramsg:
2634 extramsg = _("Leave message empty to abort commit.")
2634 extramsg = _("Leave message empty to abort commit.")
2635
2635
2636 forms = [e for e in editform.split('.') if e]
2636 forms = [e for e in editform.split('.') if e]
2637 forms.insert(0, 'changeset')
2637 forms.insert(0, 'changeset')
2638 templatetext = None
2638 templatetext = None
2639 while forms:
2639 while forms:
2640 ref = '.'.join(forms)
2640 ref = '.'.join(forms)
2641 if repo.ui.config('committemplate', ref):
2641 if repo.ui.config('committemplate', ref):
2642 templatetext = committext = buildcommittemplate(
2642 templatetext = committext = buildcommittemplate(
2643 repo, ctx, subs, extramsg, ref)
2643 repo, ctx, subs, extramsg, ref)
2644 break
2644 break
2645 forms.pop()
2645 forms.pop()
2646 else:
2646 else:
2647 committext = buildcommittext(repo, ctx, subs, extramsg)
2647 committext = buildcommittext(repo, ctx, subs, extramsg)
2648
2648
2649 # run editor in the repository root
2649 # run editor in the repository root
2650 olddir = encoding.getcwd()
2650 olddir = encoding.getcwd()
2651 os.chdir(repo.root)
2651 os.chdir(repo.root)
2652
2652
2653 # make in-memory changes visible to external process
2653 # make in-memory changes visible to external process
2654 tr = repo.currenttransaction()
2654 tr = repo.currenttransaction()
2655 repo.dirstate.write(tr)
2655 repo.dirstate.write(tr)
2656 pending = tr and tr.writepending() and repo.root
2656 pending = tr and tr.writepending() and repo.root
2657
2657
2658 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2658 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2659 editform=editform, pending=pending,
2659 editform=editform, pending=pending,
2660 repopath=repo.path, action='commit')
2660 repopath=repo.path, action='commit')
2661 text = editortext
2661 text = editortext
2662
2662
2663 # strip away anything below this special string (used for editors that want
2663 # strip away anything below this special string (used for editors that want
2664 # to display the diff)
2664 # to display the diff)
2665 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2665 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2666 if stripbelow:
2666 if stripbelow:
2667 text = text[:stripbelow.start()]
2667 text = text[:stripbelow.start()]
2668
2668
2669 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2669 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2670 os.chdir(olddir)
2670 os.chdir(olddir)
2671
2671
2672 if finishdesc:
2672 if finishdesc:
2673 text = finishdesc(text)
2673 text = finishdesc(text)
2674 if not text.strip():
2674 if not text.strip():
2675 raise error.Abort(_("empty commit message"))
2675 raise error.Abort(_("empty commit message"))
2676 if unchangedmessagedetection and editortext == templatetext:
2676 if unchangedmessagedetection and editortext == templatetext:
2677 raise error.Abort(_("commit message unchanged"))
2677 raise error.Abort(_("commit message unchanged"))
2678
2678
2679 return text
2679 return text
2680
2680
2681 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2681 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2682 ui = repo.ui
2682 ui = repo.ui
2683 spec = formatter.templatespec(ref, None, None)
2683 spec = formatter.templatespec(ref, None, None)
2684 t = logcmdutil.changesettemplater(ui, repo, spec)
2684 t = logcmdutil.changesettemplater(ui, repo, spec)
2685 t.t.cache.update((k, templater.unquotestring(v))
2685 t.t.cache.update((k, templater.unquotestring(v))
2686 for k, v in repo.ui.configitems('committemplate'))
2686 for k, v in repo.ui.configitems('committemplate'))
2687
2687
2688 if not extramsg:
2688 if not extramsg:
2689 extramsg = '' # ensure that extramsg is string
2689 extramsg = '' # ensure that extramsg is string
2690
2690
2691 ui.pushbuffer()
2691 ui.pushbuffer()
2692 t.show(ctx, extramsg=extramsg)
2692 t.show(ctx, extramsg=extramsg)
2693 return ui.popbuffer()
2693 return ui.popbuffer()
2694
2694
2695 def hgprefix(msg):
2695 def hgprefix(msg):
2696 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2696 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2697
2697
2698 def buildcommittext(repo, ctx, subs, extramsg):
2698 def buildcommittext(repo, ctx, subs, extramsg):
2699 edittext = []
2699 edittext = []
2700 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2700 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2701 if ctx.description():
2701 if ctx.description():
2702 edittext.append(ctx.description())
2702 edittext.append(ctx.description())
2703 edittext.append("")
2703 edittext.append("")
2704 edittext.append("") # Empty line between message and comments.
2704 edittext.append("") # Empty line between message and comments.
2705 edittext.append(hgprefix(_("Enter commit message."
2705 edittext.append(hgprefix(_("Enter commit message."
2706 " Lines beginning with 'HG:' are removed.")))
2706 " Lines beginning with 'HG:' are removed.")))
2707 edittext.append(hgprefix(extramsg))
2707 edittext.append(hgprefix(extramsg))
2708 edittext.append("HG: --")
2708 edittext.append("HG: --")
2709 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2709 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2710 if ctx.p2():
2710 if ctx.p2():
2711 edittext.append(hgprefix(_("branch merge")))
2711 edittext.append(hgprefix(_("branch merge")))
2712 if ctx.branch():
2712 if ctx.branch():
2713 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2713 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2714 if bookmarks.isactivewdirparent(repo):
2714 if bookmarks.isactivewdirparent(repo):
2715 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2715 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2716 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2716 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2717 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2717 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2718 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2718 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2719 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2719 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2720 if not added and not modified and not removed:
2720 if not added and not modified and not removed:
2721 edittext.append(hgprefix(_("no files changed")))
2721 edittext.append(hgprefix(_("no files changed")))
2722 edittext.append("")
2722 edittext.append("")
2723
2723
2724 return "\n".join(edittext)
2724 return "\n".join(edittext)
2725
2725
2726 def commitstatus(repo, node, branch, bheads=None, opts=None):
2726 def commitstatus(repo, node, branch, bheads=None, opts=None):
2727 if opts is None:
2727 if opts is None:
2728 opts = {}
2728 opts = {}
2729 ctx = repo[node]
2729 ctx = repo[node]
2730 parents = ctx.parents()
2730 parents = ctx.parents()
2731
2731
2732 if (not opts.get('amend') and bheads and node not in bheads and not
2732 if (not opts.get('amend') and bheads and node not in bheads and not
2733 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2733 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2734 repo.ui.status(_('created new head\n'))
2734 repo.ui.status(_('created new head\n'))
2735 # The message is not printed for initial roots. For the other
2735 # The message is not printed for initial roots. For the other
2736 # changesets, it is printed in the following situations:
2736 # changesets, it is printed in the following situations:
2737 #
2737 #
2738 # Par column: for the 2 parents with ...
2738 # Par column: for the 2 parents with ...
2739 # N: null or no parent
2739 # N: null or no parent
2740 # B: parent is on another named branch
2740 # B: parent is on another named branch
2741 # C: parent is a regular non head changeset
2741 # C: parent is a regular non head changeset
2742 # H: parent was a branch head of the current branch
2742 # H: parent was a branch head of the current branch
2743 # Msg column: whether we print "created new head" message
2743 # Msg column: whether we print "created new head" message
2744 # In the following, it is assumed that there already exists some
2744 # In the following, it is assumed that there already exists some
2745 # initial branch heads of the current branch, otherwise nothing is
2745 # initial branch heads of the current branch, otherwise nothing is
2746 # printed anyway.
2746 # printed anyway.
2747 #
2747 #
2748 # Par Msg Comment
2748 # Par Msg Comment
2749 # N N y additional topo root
2749 # N N y additional topo root
2750 #
2750 #
2751 # B N y additional branch root
2751 # B N y additional branch root
2752 # C N y additional topo head
2752 # C N y additional topo head
2753 # H N n usual case
2753 # H N n usual case
2754 #
2754 #
2755 # B B y weird additional branch root
2755 # B B y weird additional branch root
2756 # C B y branch merge
2756 # C B y branch merge
2757 # H B n merge with named branch
2757 # H B n merge with named branch
2758 #
2758 #
2759 # C C y additional head from merge
2759 # C C y additional head from merge
2760 # C H n merge with a head
2760 # C H n merge with a head
2761 #
2761 #
2762 # H H n head merge: head count decreases
2762 # H H n head merge: head count decreases
2763
2763
2764 if not opts.get('close_branch'):
2764 if not opts.get('close_branch'):
2765 for r in parents:
2765 for r in parents:
2766 if r.closesbranch() and r.branch() == branch:
2766 if r.closesbranch() and r.branch() == branch:
2767 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2767 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2768
2768
2769 if repo.ui.debugflag:
2769 if repo.ui.debugflag:
2770 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2770 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2771 elif repo.ui.verbose:
2771 elif repo.ui.verbose:
2772 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2772 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2773
2773
2774 def postcommitstatus(repo, pats, opts):
2774 def postcommitstatus(repo, pats, opts):
2775 return repo.status(match=scmutil.match(repo[None], pats, opts))
2775 return repo.status(match=scmutil.match(repo[None], pats, opts))
2776
2776
2777 def revert(ui, repo, ctx, parents, *pats, **opts):
2777 def revert(ui, repo, ctx, parents, *pats, **opts):
2778 opts = pycompat.byteskwargs(opts)
2778 opts = pycompat.byteskwargs(opts)
2779 parent, p2 = parents
2779 parent, p2 = parents
2780 node = ctx.node()
2780 node = ctx.node()
2781
2781
2782 mf = ctx.manifest()
2782 mf = ctx.manifest()
2783 if node == p2:
2783 if node == p2:
2784 parent = p2
2784 parent = p2
2785
2785
2786 # need all matching names in dirstate and manifest of target rev,
2786 # need all matching names in dirstate and manifest of target rev,
2787 # so have to walk both. do not print errors if files exist in one
2787 # so have to walk both. do not print errors if files exist in one
2788 # but not other. in both cases, filesets should be evaluated against
2788 # but not other. in both cases, filesets should be evaluated against
2789 # workingctx to get consistent result (issue4497). this means 'set:**'
2789 # workingctx to get consistent result (issue4497). this means 'set:**'
2790 # cannot be used to select missing files from target rev.
2790 # cannot be used to select missing files from target rev.
2791
2791
2792 # `names` is a mapping for all elements in working copy and target revision
2792 # `names` is a mapping for all elements in working copy and target revision
2793 # The mapping is in the form:
2793 # The mapping is in the form:
2794 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2794 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2795 names = {}
2795 names = {}
2796 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2796 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2797
2797
2798 with repo.wlock():
2798 with repo.wlock():
2799 ## filling of the `names` mapping
2799 ## filling of the `names` mapping
2800 # walk dirstate to fill `names`
2800 # walk dirstate to fill `names`
2801
2801
2802 interactive = opts.get('interactive', False)
2802 interactive = opts.get('interactive', False)
2803 wctx = repo[None]
2803 wctx = repo[None]
2804 m = scmutil.match(wctx, pats, opts)
2804 m = scmutil.match(wctx, pats, opts)
2805
2805
2806 # we'll need this later
2806 # we'll need this later
2807 targetsubs = sorted(s for s in wctx.substate if m(s))
2807 targetsubs = sorted(s for s in wctx.substate if m(s))
2808
2808
2809 if not m.always():
2809 if not m.always():
2810 matcher = matchmod.badmatch(m, lambda x, y: False)
2810 matcher = matchmod.badmatch(m, lambda x, y: False)
2811 for abs in wctx.walk(matcher):
2811 for abs in wctx.walk(matcher):
2812 names[abs] = m.exact(abs)
2812 names[abs] = m.exact(abs)
2813
2813
2814 # walk target manifest to fill `names`
2814 # walk target manifest to fill `names`
2815
2815
2816 def badfn(path, msg):
2816 def badfn(path, msg):
2817 if path in names:
2817 if path in names:
2818 return
2818 return
2819 if path in ctx.substate:
2819 if path in ctx.substate:
2820 return
2820 return
2821 path_ = path + '/'
2821 path_ = path + '/'
2822 for f in names:
2822 for f in names:
2823 if f.startswith(path_):
2823 if f.startswith(path_):
2824 return
2824 return
2825 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2825 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2826
2826
2827 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2827 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2828 if abs not in names:
2828 if abs not in names:
2829 names[abs] = m.exact(abs)
2829 names[abs] = m.exact(abs)
2830
2830
2831 # Find status of all file in `names`.
2831 # Find status of all file in `names`.
2832 m = scmutil.matchfiles(repo, names)
2832 m = scmutil.matchfiles(repo, names)
2833
2833
2834 changes = repo.status(node1=node, match=m,
2834 changes = repo.status(node1=node, match=m,
2835 unknown=True, ignored=True, clean=True)
2835 unknown=True, ignored=True, clean=True)
2836 else:
2836 else:
2837 changes = repo.status(node1=node, match=m)
2837 changes = repo.status(node1=node, match=m)
2838 for kind in changes:
2838 for kind in changes:
2839 for abs in kind:
2839 for abs in kind:
2840 names[abs] = m.exact(abs)
2840 names[abs] = m.exact(abs)
2841
2841
2842 m = scmutil.matchfiles(repo, names)
2842 m = scmutil.matchfiles(repo, names)
2843
2843
2844 modified = set(changes.modified)
2844 modified = set(changes.modified)
2845 added = set(changes.added)
2845 added = set(changes.added)
2846 removed = set(changes.removed)
2846 removed = set(changes.removed)
2847 _deleted = set(changes.deleted)
2847 _deleted = set(changes.deleted)
2848 unknown = set(changes.unknown)
2848 unknown = set(changes.unknown)
2849 unknown.update(changes.ignored)
2849 unknown.update(changes.ignored)
2850 clean = set(changes.clean)
2850 clean = set(changes.clean)
2851 modadded = set()
2851 modadded = set()
2852
2852
2853 # We need to account for the state of the file in the dirstate,
2853 # We need to account for the state of the file in the dirstate,
2854 # even when we revert against something else than parent. This will
2854 # even when we revert against something else than parent. This will
2855 # slightly alter the behavior of revert (doing back up or not, delete
2855 # slightly alter the behavior of revert (doing back up or not, delete
2856 # or just forget etc).
2856 # or just forget etc).
2857 if parent == node:
2857 if parent == node:
2858 dsmodified = modified
2858 dsmodified = modified
2859 dsadded = added
2859 dsadded = added
2860 dsremoved = removed
2860 dsremoved = removed
2861 # store all local modifications, useful later for rename detection
2861 # store all local modifications, useful later for rename detection
2862 localchanges = dsmodified | dsadded
2862 localchanges = dsmodified | dsadded
2863 modified, added, removed = set(), set(), set()
2863 modified, added, removed = set(), set(), set()
2864 else:
2864 else:
2865 changes = repo.status(node1=parent, match=m)
2865 changes = repo.status(node1=parent, match=m)
2866 dsmodified = set(changes.modified)
2866 dsmodified = set(changes.modified)
2867 dsadded = set(changes.added)
2867 dsadded = set(changes.added)
2868 dsremoved = set(changes.removed)
2868 dsremoved = set(changes.removed)
2869 # store all local modifications, useful later for rename detection
2869 # store all local modifications, useful later for rename detection
2870 localchanges = dsmodified | dsadded
2870 localchanges = dsmodified | dsadded
2871
2871
2872 # only take into account for removes between wc and target
2872 # only take into account for removes between wc and target
2873 clean |= dsremoved - removed
2873 clean |= dsremoved - removed
2874 dsremoved &= removed
2874 dsremoved &= removed
2875 # distinct between dirstate remove and other
2875 # distinct between dirstate remove and other
2876 removed -= dsremoved
2876 removed -= dsremoved
2877
2877
2878 modadded = added & dsmodified
2878 modadded = added & dsmodified
2879 added -= modadded
2879 added -= modadded
2880
2880
2881 # tell newly modified apart.
2881 # tell newly modified apart.
2882 dsmodified &= modified
2882 dsmodified &= modified
2883 dsmodified |= modified & dsadded # dirstate added may need backup
2883 dsmodified |= modified & dsadded # dirstate added may need backup
2884 modified -= dsmodified
2884 modified -= dsmodified
2885
2885
2886 # We need to wait for some post-processing to update this set
2886 # We need to wait for some post-processing to update this set
2887 # before making the distinction. The dirstate will be used for
2887 # before making the distinction. The dirstate will be used for
2888 # that purpose.
2888 # that purpose.
2889 dsadded = added
2889 dsadded = added
2890
2890
2891 # in case of merge, files that are actually added can be reported as
2891 # in case of merge, files that are actually added can be reported as
2892 # modified, we need to post process the result
2892 # modified, we need to post process the result
2893 if p2 != nullid:
2893 if p2 != nullid:
2894 mergeadd = set(dsmodified)
2894 mergeadd = set(dsmodified)
2895 for path in dsmodified:
2895 for path in dsmodified:
2896 if path in mf:
2896 if path in mf:
2897 mergeadd.remove(path)
2897 mergeadd.remove(path)
2898 dsadded |= mergeadd
2898 dsadded |= mergeadd
2899 dsmodified -= mergeadd
2899 dsmodified -= mergeadd
2900
2900
2901 # if f is a rename, update `names` to also revert the source
2901 # if f is a rename, update `names` to also revert the source
2902 for f in localchanges:
2902 for f in localchanges:
2903 src = repo.dirstate.copied(f)
2903 src = repo.dirstate.copied(f)
2904 # XXX should we check for rename down to target node?
2904 # XXX should we check for rename down to target node?
2905 if src and src not in names and repo.dirstate[src] == 'r':
2905 if src and src not in names and repo.dirstate[src] == 'r':
2906 dsremoved.add(src)
2906 dsremoved.add(src)
2907 names[src] = True
2907 names[src] = True
2908
2908
2909 # determine the exact nature of the deleted changesets
2909 # determine the exact nature of the deleted changesets
2910 deladded = set(_deleted)
2910 deladded = set(_deleted)
2911 for path in _deleted:
2911 for path in _deleted:
2912 if path in mf:
2912 if path in mf:
2913 deladded.remove(path)
2913 deladded.remove(path)
2914 deleted = _deleted - deladded
2914 deleted = _deleted - deladded
2915
2915
2916 # distinguish between file to forget and the other
2916 # distinguish between file to forget and the other
2917 added = set()
2917 added = set()
2918 for abs in dsadded:
2918 for abs in dsadded:
2919 if repo.dirstate[abs] != 'a':
2919 if repo.dirstate[abs] != 'a':
2920 added.add(abs)
2920 added.add(abs)
2921 dsadded -= added
2921 dsadded -= added
2922
2922
2923 for abs in deladded:
2923 for abs in deladded:
2924 if repo.dirstate[abs] == 'a':
2924 if repo.dirstate[abs] == 'a':
2925 dsadded.add(abs)
2925 dsadded.add(abs)
2926 deladded -= dsadded
2926 deladded -= dsadded
2927
2927
2928 # For files marked as removed, we check if an unknown file is present at
2928 # For files marked as removed, we check if an unknown file is present at
2929 # the same path. If a such file exists it may need to be backed up.
2929 # the same path. If a such file exists it may need to be backed up.
2930 # Making the distinction at this stage helps have simpler backup
2930 # Making the distinction at this stage helps have simpler backup
2931 # logic.
2931 # logic.
2932 removunk = set()
2932 removunk = set()
2933 for abs in removed:
2933 for abs in removed:
2934 target = repo.wjoin(abs)
2934 target = repo.wjoin(abs)
2935 if os.path.lexists(target):
2935 if os.path.lexists(target):
2936 removunk.add(abs)
2936 removunk.add(abs)
2937 removed -= removunk
2937 removed -= removunk
2938
2938
2939 dsremovunk = set()
2939 dsremovunk = set()
2940 for abs in dsremoved:
2940 for abs in dsremoved:
2941 target = repo.wjoin(abs)
2941 target = repo.wjoin(abs)
2942 if os.path.lexists(target):
2942 if os.path.lexists(target):
2943 dsremovunk.add(abs)
2943 dsremovunk.add(abs)
2944 dsremoved -= dsremovunk
2944 dsremoved -= dsremovunk
2945
2945
2946 # action to be actually performed by revert
2946 # action to be actually performed by revert
2947 # (<list of file>, message>) tuple
2947 # (<list of file>, message>) tuple
2948 actions = {'revert': ([], _('reverting %s\n')),
2948 actions = {'revert': ([], _('reverting %s\n')),
2949 'add': ([], _('adding %s\n')),
2949 'add': ([], _('adding %s\n')),
2950 'remove': ([], _('removing %s\n')),
2950 'remove': ([], _('removing %s\n')),
2951 'drop': ([], _('removing %s\n')),
2951 'drop': ([], _('removing %s\n')),
2952 'forget': ([], _('forgetting %s\n')),
2952 'forget': ([], _('forgetting %s\n')),
2953 'undelete': ([], _('undeleting %s\n')),
2953 'undelete': ([], _('undeleting %s\n')),
2954 'noop': (None, _('no changes needed to %s\n')),
2954 'noop': (None, _('no changes needed to %s\n')),
2955 'unknown': (None, _('file not managed: %s\n')),
2955 'unknown': (None, _('file not managed: %s\n')),
2956 }
2956 }
2957
2957
2958 # "constant" that convey the backup strategy.
2958 # "constant" that convey the backup strategy.
2959 # All set to `discard` if `no-backup` is set do avoid checking
2959 # All set to `discard` if `no-backup` is set do avoid checking
2960 # no_backup lower in the code.
2960 # no_backup lower in the code.
2961 # These values are ordered for comparison purposes
2961 # These values are ordered for comparison purposes
2962 backupinteractive = 3 # do backup if interactively modified
2962 backupinteractive = 3 # do backup if interactively modified
2963 backup = 2 # unconditionally do backup
2963 backup = 2 # unconditionally do backup
2964 check = 1 # check if the existing file differs from target
2964 check = 1 # check if the existing file differs from target
2965 discard = 0 # never do backup
2965 discard = 0 # never do backup
2966 if opts.get('no_backup'):
2966 if opts.get('no_backup'):
2967 backupinteractive = backup = check = discard
2967 backupinteractive = backup = check = discard
2968 if interactive:
2968 if interactive:
2969 dsmodifiedbackup = backupinteractive
2969 dsmodifiedbackup = backupinteractive
2970 else:
2970 else:
2971 dsmodifiedbackup = backup
2971 dsmodifiedbackup = backup
2972 tobackup = set()
2972 tobackup = set()
2973
2973
2974 backupanddel = actions['remove']
2974 backupanddel = actions['remove']
2975 if not opts.get('no_backup'):
2975 if not opts.get('no_backup'):
2976 backupanddel = actions['drop']
2976 backupanddel = actions['drop']
2977
2977
2978 disptable = (
2978 disptable = (
2979 # dispatch table:
2979 # dispatch table:
2980 # file state
2980 # file state
2981 # action
2981 # action
2982 # make backup
2982 # make backup
2983
2983
2984 ## Sets that results that will change file on disk
2984 ## Sets that results that will change file on disk
2985 # Modified compared to target, no local change
2985 # Modified compared to target, no local change
2986 (modified, actions['revert'], discard),
2986 (modified, actions['revert'], discard),
2987 # Modified compared to target, but local file is deleted
2987 # Modified compared to target, but local file is deleted
2988 (deleted, actions['revert'], discard),
2988 (deleted, actions['revert'], discard),
2989 # Modified compared to target, local change
2989 # Modified compared to target, local change
2990 (dsmodified, actions['revert'], dsmodifiedbackup),
2990 (dsmodified, actions['revert'], dsmodifiedbackup),
2991 # Added since target
2991 # Added since target
2992 (added, actions['remove'], discard),
2992 (added, actions['remove'], discard),
2993 # Added in working directory
2993 # Added in working directory
2994 (dsadded, actions['forget'], discard),
2994 (dsadded, actions['forget'], discard),
2995 # Added since target, have local modification
2995 # Added since target, have local modification
2996 (modadded, backupanddel, backup),
2996 (modadded, backupanddel, backup),
2997 # Added since target but file is missing in working directory
2997 # Added since target but file is missing in working directory
2998 (deladded, actions['drop'], discard),
2998 (deladded, actions['drop'], discard),
2999 # Removed since target, before working copy parent
2999 # Removed since target, before working copy parent
3000 (removed, actions['add'], discard),
3000 (removed, actions['add'], discard),
3001 # Same as `removed` but an unknown file exists at the same path
3001 # Same as `removed` but an unknown file exists at the same path
3002 (removunk, actions['add'], check),
3002 (removunk, actions['add'], check),
3003 # Removed since targe, marked as such in working copy parent
3003 # Removed since targe, marked as such in working copy parent
3004 (dsremoved, actions['undelete'], discard),
3004 (dsremoved, actions['undelete'], discard),
3005 # Same as `dsremoved` but an unknown file exists at the same path
3005 # Same as `dsremoved` but an unknown file exists at the same path
3006 (dsremovunk, actions['undelete'], check),
3006 (dsremovunk, actions['undelete'], check),
3007 ## the following sets does not result in any file changes
3007 ## the following sets does not result in any file changes
3008 # File with no modification
3008 # File with no modification
3009 (clean, actions['noop'], discard),
3009 (clean, actions['noop'], discard),
3010 # Existing file, not tracked anywhere
3010 # Existing file, not tracked anywhere
3011 (unknown, actions['unknown'], discard),
3011 (unknown, actions['unknown'], discard),
3012 )
3012 )
3013
3013
3014 for abs, exact in sorted(names.items()):
3014 for abs, exact in sorted(names.items()):
3015 # target file to be touch on disk (relative to cwd)
3015 # target file to be touch on disk (relative to cwd)
3016 target = repo.wjoin(abs)
3016 target = repo.wjoin(abs)
3017 # search the entry in the dispatch table.
3017 # search the entry in the dispatch table.
3018 # if the file is in any of these sets, it was touched in the working
3018 # if the file is in any of these sets, it was touched in the working
3019 # directory parent and we are sure it needs to be reverted.
3019 # directory parent and we are sure it needs to be reverted.
3020 for table, (xlist, msg), dobackup in disptable:
3020 for table, (xlist, msg), dobackup in disptable:
3021 if abs not in table:
3021 if abs not in table:
3022 continue
3022 continue
3023 if xlist is not None:
3023 if xlist is not None:
3024 xlist.append(abs)
3024 xlist.append(abs)
3025 if dobackup:
3025 if dobackup:
3026 # If in interactive mode, don't automatically create
3026 # If in interactive mode, don't automatically create
3027 # .orig files (issue4793)
3027 # .orig files (issue4793)
3028 if dobackup == backupinteractive:
3028 if dobackup == backupinteractive:
3029 tobackup.add(abs)
3029 tobackup.add(abs)
3030 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3030 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3031 absbakname = scmutil.backuppath(ui, repo, abs)
3031 absbakname = scmutil.backuppath(ui, repo, abs)
3032 bakname = os.path.relpath(absbakname,
3032 bakname = os.path.relpath(absbakname,
3033 start=repo.root)
3033 start=repo.root)
3034 ui.note(_('saving current version of %s as %s\n') %
3034 ui.note(_('saving current version of %s as %s\n') %
3035 (uipathfn(abs), uipathfn(bakname)))
3035 (uipathfn(abs), uipathfn(bakname)))
3036 if not opts.get('dry_run'):
3036 if not opts.get('dry_run'):
3037 if interactive:
3037 if interactive:
3038 util.copyfile(target, absbakname)
3038 util.copyfile(target, absbakname)
3039 else:
3039 else:
3040 util.rename(target, absbakname)
3040 util.rename(target, absbakname)
3041 if opts.get('dry_run'):
3041 if opts.get('dry_run'):
3042 if ui.verbose or not exact:
3042 if ui.verbose or not exact:
3043 ui.status(msg % uipathfn(abs))
3043 ui.status(msg % uipathfn(abs))
3044 elif exact:
3044 elif exact:
3045 ui.warn(msg % uipathfn(abs))
3045 ui.warn(msg % uipathfn(abs))
3046 break
3046 break
3047
3047
3048 if not opts.get('dry_run'):
3048 if not opts.get('dry_run'):
3049 needdata = ('revert', 'add', 'undelete')
3049 needdata = ('revert', 'add', 'undelete')
3050 oplist = [actions[name][0] for name in needdata]
3050 oplist = [actions[name][0] for name in needdata]
3051 prefetch = scmutil.prefetchfiles
3051 prefetch = scmutil.prefetchfiles
3052 matchfiles = scmutil.matchfiles
3052 matchfiles = scmutil.matchfiles
3053 prefetch(repo, [ctx.rev()],
3053 prefetch(repo, [ctx.rev()],
3054 matchfiles(repo,
3054 matchfiles(repo,
3055 [f for sublist in oplist for f in sublist]))
3055 [f for sublist in oplist for f in sublist]))
3056 match = scmutil.match(repo[None], pats)
3056 match = scmutil.match(repo[None], pats)
3057 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3057 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3058 match, interactive, tobackup)
3058 match, interactive, tobackup)
3059
3059
3060 if targetsubs:
3060 if targetsubs:
3061 # Revert the subrepos on the revert list
3061 # Revert the subrepos on the revert list
3062 for sub in targetsubs:
3062 for sub in targetsubs:
3063 try:
3063 try:
3064 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3064 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3065 **pycompat.strkwargs(opts))
3065 **pycompat.strkwargs(opts))
3066 except KeyError:
3066 except KeyError:
3067 raise error.Abort("subrepository '%s' does not exist in %s!"
3067 raise error.Abort("subrepository '%s' does not exist in %s!"
3068 % (sub, short(ctx.node())))
3068 % (sub, short(ctx.node())))
3069
3069
3070 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3070 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3071 match, interactive=False, tobackup=None):
3071 match, interactive=False, tobackup=None):
3072 """function that actually perform all the actions computed for revert
3072 """function that actually perform all the actions computed for revert
3073
3073
3074 This is an independent function to let extension to plug in and react to
3074 This is an independent function to let extension to plug in and react to
3075 the imminent revert.
3075 the imminent revert.
3076
3076
3077 Make sure you have the working directory locked when calling this function.
3077 Make sure you have the working directory locked when calling this function.
3078 """
3078 """
3079 parent, p2 = parents
3079 parent, p2 = parents
3080 node = ctx.node()
3080 node = ctx.node()
3081 excluded_files = []
3081 excluded_files = []
3082
3082
3083 def checkout(f):
3083 def checkout(f):
3084 fc = ctx[f]
3084 fc = ctx[f]
3085 repo.wwrite(f, fc.data(), fc.flags())
3085 repo.wwrite(f, fc.data(), fc.flags())
3086
3086
3087 def doremove(f):
3087 def doremove(f):
3088 try:
3088 try:
3089 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3089 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3090 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3090 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3091 except OSError:
3091 except OSError:
3092 pass
3092 pass
3093 repo.dirstate.remove(f)
3093 repo.dirstate.remove(f)
3094
3094
3095 def prntstatusmsg(action, f):
3095 def prntstatusmsg(action, f):
3096 exact = names[f]
3096 exact = names[f]
3097 if repo.ui.verbose or not exact:
3097 if repo.ui.verbose or not exact:
3098 repo.ui.status(actions[action][1] % uipathfn(f))
3098 repo.ui.status(actions[action][1] % uipathfn(f))
3099
3099
3100 audit_path = pathutil.pathauditor(repo.root, cached=True)
3100 audit_path = pathutil.pathauditor(repo.root, cached=True)
3101 for f in actions['forget'][0]:
3101 for f in actions['forget'][0]:
3102 if interactive:
3102 if interactive:
3103 choice = repo.ui.promptchoice(
3103 choice = repo.ui.promptchoice(
3104 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3104 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3105 if choice == 0:
3105 if choice == 0:
3106 prntstatusmsg('forget', f)
3106 prntstatusmsg('forget', f)
3107 repo.dirstate.drop(f)
3107 repo.dirstate.drop(f)
3108 else:
3108 else:
3109 excluded_files.append(f)
3109 excluded_files.append(f)
3110 else:
3110 else:
3111 prntstatusmsg('forget', f)
3111 prntstatusmsg('forget', f)
3112 repo.dirstate.drop(f)
3112 repo.dirstate.drop(f)
3113 for f in actions['remove'][0]:
3113 for f in actions['remove'][0]:
3114 audit_path(f)
3114 audit_path(f)
3115 if interactive:
3115 if interactive:
3116 choice = repo.ui.promptchoice(
3116 choice = repo.ui.promptchoice(
3117 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3117 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3118 if choice == 0:
3118 if choice == 0:
3119 prntstatusmsg('remove', f)
3119 prntstatusmsg('remove', f)
3120 doremove(f)
3120 doremove(f)
3121 else:
3121 else:
3122 excluded_files.append(f)
3122 excluded_files.append(f)
3123 else:
3123 else:
3124 prntstatusmsg('remove', f)
3124 prntstatusmsg('remove', f)
3125 doremove(f)
3125 doremove(f)
3126 for f in actions['drop'][0]:
3126 for f in actions['drop'][0]:
3127 audit_path(f)
3127 audit_path(f)
3128 prntstatusmsg('drop', f)
3128 prntstatusmsg('drop', f)
3129 repo.dirstate.remove(f)
3129 repo.dirstate.remove(f)
3130
3130
3131 normal = None
3131 normal = None
3132 if node == parent:
3132 if node == parent:
3133 # We're reverting to our parent. If possible, we'd like status
3133 # We're reverting to our parent. If possible, we'd like status
3134 # to report the file as clean. We have to use normallookup for
3134 # to report the file as clean. We have to use normallookup for
3135 # merges to avoid losing information about merged/dirty files.
3135 # merges to avoid losing information about merged/dirty files.
3136 if p2 != nullid:
3136 if p2 != nullid:
3137 normal = repo.dirstate.normallookup
3137 normal = repo.dirstate.normallookup
3138 else:
3138 else:
3139 normal = repo.dirstate.normal
3139 normal = repo.dirstate.normal
3140
3140
3141 newlyaddedandmodifiedfiles = set()
3141 newlyaddedandmodifiedfiles = set()
3142 if interactive:
3142 if interactive:
3143 # Prompt the user for changes to revert
3143 # Prompt the user for changes to revert
3144 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3144 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3145 m = scmutil.matchfiles(repo, torevert)
3145 m = scmutil.matchfiles(repo, torevert)
3146 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3146 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3147 section='commands',
3147 section='commands',
3148 configprefix='revert.interactive.')
3148 configprefix='revert.interactive.')
3149 diffopts.nodates = True
3149 diffopts.nodates = True
3150 diffopts.git = True
3150 diffopts.git = True
3151 operation = 'apply'
3151 operation = 'apply'
3152 if node == parent:
3152 if node == parent:
3153 if repo.ui.configbool('experimental',
3153 if repo.ui.configbool('experimental',
3154 'revert.interactive.select-to-keep'):
3154 'revert.interactive.select-to-keep'):
3155 operation = 'keep'
3155 operation = 'keep'
3156 else:
3156 else:
3157 operation = 'discard'
3157 operation = 'discard'
3158
3158
3159 if operation == 'apply':
3159 if operation == 'apply':
3160 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3160 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3161 else:
3161 else:
3162 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3162 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3163 originalchunks = patch.parsepatch(diff)
3163 originalchunks = patch.parsepatch(diff)
3164
3164
3165 try:
3165 try:
3166
3166
3167 chunks, opts = recordfilter(repo.ui, originalchunks, match,
3167 chunks, opts = recordfilter(repo.ui, originalchunks, match,
3168 operation=operation)
3168 operation=operation)
3169 if operation == 'discard':
3169 if operation == 'discard':
3170 chunks = patch.reversehunks(chunks)
3170 chunks = patch.reversehunks(chunks)
3171
3171
3172 except error.PatchError as err:
3172 except error.PatchError as err:
3173 raise error.Abort(_('error parsing patch: %s') % err)
3173 raise error.Abort(_('error parsing patch: %s') % err)
3174
3174
3175 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3175 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3176 if tobackup is None:
3176 if tobackup is None:
3177 tobackup = set()
3177 tobackup = set()
3178 # Apply changes
3178 # Apply changes
3179 fp = stringio()
3179 fp = stringio()
3180 # chunks are serialized per file, but files aren't sorted
3180 # chunks are serialized per file, but files aren't sorted
3181 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3181 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3182 prntstatusmsg('revert', f)
3182 prntstatusmsg('revert', f)
3183 files = set()
3183 files = set()
3184 for c in chunks:
3184 for c in chunks:
3185 if ishunk(c):
3185 if ishunk(c):
3186 abs = c.header.filename()
3186 abs = c.header.filename()
3187 # Create a backup file only if this hunk should be backed up
3187 # Create a backup file only if this hunk should be backed up
3188 if c.header.filename() in tobackup:
3188 if c.header.filename() in tobackup:
3189 target = repo.wjoin(abs)
3189 target = repo.wjoin(abs)
3190 bakname = scmutil.backuppath(repo.ui, repo, abs)
3190 bakname = scmutil.backuppath(repo.ui, repo, abs)
3191 util.copyfile(target, bakname)
3191 util.copyfile(target, bakname)
3192 tobackup.remove(abs)
3192 tobackup.remove(abs)
3193 if abs not in files:
3193 if abs not in files:
3194 files.add(abs)
3194 files.add(abs)
3195 if operation == 'keep':
3195 if operation == 'keep':
3196 checkout(abs)
3196 checkout(abs)
3197 c.write(fp)
3197 c.write(fp)
3198 dopatch = fp.tell()
3198 dopatch = fp.tell()
3199 fp.seek(0)
3199 fp.seek(0)
3200 if dopatch:
3200 if dopatch:
3201 try:
3201 try:
3202 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3202 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3203 except error.PatchError as err:
3203 except error.PatchError as err:
3204 raise error.Abort(pycompat.bytestr(err))
3204 raise error.Abort(pycompat.bytestr(err))
3205 del fp
3205 del fp
3206 else:
3206 else:
3207 for f in actions['revert'][0]:
3207 for f in actions['revert'][0]:
3208 prntstatusmsg('revert', f)
3208 prntstatusmsg('revert', f)
3209 checkout(f)
3209 checkout(f)
3210 if normal:
3210 if normal:
3211 normal(f)
3211 normal(f)
3212
3212
3213 for f in actions['add'][0]:
3213 for f in actions['add'][0]:
3214 # Don't checkout modified files, they are already created by the diff
3214 # Don't checkout modified files, they are already created by the diff
3215 if f not in newlyaddedandmodifiedfiles:
3215 if f not in newlyaddedandmodifiedfiles:
3216 prntstatusmsg('add', f)
3216 prntstatusmsg('add', f)
3217 checkout(f)
3217 checkout(f)
3218 repo.dirstate.add(f)
3218 repo.dirstate.add(f)
3219
3219
3220 normal = repo.dirstate.normallookup
3220 normal = repo.dirstate.normallookup
3221 if node == parent and p2 == nullid:
3221 if node == parent and p2 == nullid:
3222 normal = repo.dirstate.normal
3222 normal = repo.dirstate.normal
3223 for f in actions['undelete'][0]:
3223 for f in actions['undelete'][0]:
3224 if interactive:
3224 if interactive:
3225 choice = repo.ui.promptchoice(
3225 choice = repo.ui.promptchoice(
3226 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3226 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3227 if choice == 0:
3227 if choice == 0:
3228 prntstatusmsg('undelete', f)
3228 prntstatusmsg('undelete', f)
3229 checkout(f)
3229 checkout(f)
3230 normal(f)
3230 normal(f)
3231 else:
3231 else:
3232 excluded_files.append(f)
3232 excluded_files.append(f)
3233 else:
3233 else:
3234 prntstatusmsg('undelete', f)
3234 prntstatusmsg('undelete', f)
3235 checkout(f)
3235 checkout(f)
3236 normal(f)
3236 normal(f)
3237
3237
3238 copied = copies.pathcopies(repo[parent], ctx)
3238 copied = copies.pathcopies(repo[parent], ctx)
3239
3239
3240 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3240 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3241 if f in copied:
3241 if f in copied:
3242 repo.dirstate.copy(copied[f], f)
3242 repo.dirstate.copy(copied[f], f)
3243
3243
3244 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3244 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3245 # commands.outgoing. "missing" is "missing" of the result of
3245 # commands.outgoing. "missing" is "missing" of the result of
3246 # "findcommonoutgoing()"
3246 # "findcommonoutgoing()"
3247 outgoinghooks = util.hooks()
3247 outgoinghooks = util.hooks()
3248
3248
3249 # a list of (ui, repo) functions called by commands.summary
3249 # a list of (ui, repo) functions called by commands.summary
3250 summaryhooks = util.hooks()
3250 summaryhooks = util.hooks()
3251
3251
3252 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3252 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3253 #
3253 #
3254 # functions should return tuple of booleans below, if 'changes' is None:
3254 # functions should return tuple of booleans below, if 'changes' is None:
3255 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3255 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3256 #
3256 #
3257 # otherwise, 'changes' is a tuple of tuples below:
3257 # otherwise, 'changes' is a tuple of tuples below:
3258 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3258 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3259 # - (desturl, destbranch, destpeer, outgoing)
3259 # - (desturl, destbranch, destpeer, outgoing)
3260 summaryremotehooks = util.hooks()
3260 summaryremotehooks = util.hooks()
3261
3261
3262
3262
3263 def checkunfinished(repo, commit=False, skipmerge=False):
3263 def checkunfinished(repo, commit=False, skipmerge=False):
3264 '''Look for an unfinished multistep operation, like graft, and abort
3264 '''Look for an unfinished multistep operation, like graft, and abort
3265 if found. It's probably good to check this right before
3265 if found. It's probably good to check this right before
3266 bailifchanged().
3266 bailifchanged().
3267 '''
3267 '''
3268 # Check for non-clearable states first, so things like rebase will take
3268 # Check for non-clearable states first, so things like rebase will take
3269 # precedence over update.
3269 # precedence over update.
3270 for state in statemod._unfinishedstates:
3270 for state in statemod._unfinishedstates:
3271 if (state._clearable or (commit and state._allowcommit) or
3271 if (state._clearable or (commit and state._allowcommit) or
3272 state._reportonly):
3272 state._reportonly):
3273 continue
3273 continue
3274 if state.isunfinished(repo):
3274 if state.isunfinished(repo):
3275 raise error.Abort(state.msg(), hint=state.hint())
3275 raise error.Abort(state.msg(), hint=state.hint())
3276
3276
3277 for s in statemod._unfinishedstates:
3277 for s in statemod._unfinishedstates:
3278 if (not s._clearable or (commit and s._allowcommit) or
3278 if (not s._clearable or (commit and s._allowcommit) or
3279 (s._opname == 'merge' and skipmerge) or s._reportonly):
3279 (s._opname == 'merge' and skipmerge) or s._reportonly):
3280 continue
3280 continue
3281 if s.isunfinished(repo):
3281 if s.isunfinished(repo):
3282 raise error.Abort(s.msg(), hint=s.hint())
3282 raise error.Abort(s.msg(), hint=s.hint())
3283
3283
3284 def clearunfinished(repo):
3284 def clearunfinished(repo):
3285 '''Check for unfinished operations (as above), and clear the ones
3285 '''Check for unfinished operations (as above), and clear the ones
3286 that are clearable.
3286 that are clearable.
3287 '''
3287 '''
3288 for state in statemod._unfinishedstates:
3288 for state in statemod._unfinishedstates:
3289 if state._reportonly:
3289 if state._reportonly:
3290 continue
3290 continue
3291 if not state._clearable and state.isunfinished(repo):
3291 if not state._clearable and state.isunfinished(repo):
3292 raise error.Abort(state.msg(), hint=state.hint())
3292 raise error.Abort(state.msg(), hint=state.hint())
3293
3293
3294 for s in statemod._unfinishedstates:
3294 for s in statemod._unfinishedstates:
3295 if s._opname == 'merge' or state._reportonly:
3295 if s._opname == 'merge' or state._reportonly:
3296 continue
3296 continue
3297 if s._clearable and s.isunfinished(repo):
3297 if s._clearable and s.isunfinished(repo):
3298 util.unlink(repo.vfs.join(s._fname))
3298 util.unlink(repo.vfs.join(s._fname))
3299
3299
3300 def getunfinishedstate(repo):
3301 ''' Checks for unfinished operations and returns statecheck object
3302 for it'''
3303 for state in statemod._unfinishedstates:
3304 if state.isunfinished(repo):
3305 return state
3306 return None
3307
3300 def howtocontinue(repo):
3308 def howtocontinue(repo):
3301 '''Check for an unfinished operation and return the command to finish
3309 '''Check for an unfinished operation and return the command to finish
3302 it.
3310 it.
3303
3311
3304 statemod._unfinishedstates list is checked for an unfinished operation
3312 statemod._unfinishedstates list is checked for an unfinished operation
3305 and the corresponding message to finish it is generated if a method to
3313 and the corresponding message to finish it is generated if a method to
3306 continue is supported by the operation.
3314 continue is supported by the operation.
3307
3315
3308 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3316 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3309 a boolean.
3317 a boolean.
3310 '''
3318 '''
3311 contmsg = _("continue: %s")
3319 contmsg = _("continue: %s")
3312 for state in statemod._unfinishedstates:
3320 for state in statemod._unfinishedstates:
3313 if not state._continueflag:
3321 if not state._continueflag:
3314 continue
3322 continue
3315 if state.isunfinished(repo):
3323 if state.isunfinished(repo):
3316 return contmsg % state.continuemsg(), True
3324 return contmsg % state.continuemsg(), True
3317 if repo[None].dirty(missing=True, merge=False, branch=False):
3325 if repo[None].dirty(missing=True, merge=False, branch=False):
3318 return contmsg % _("hg commit"), False
3326 return contmsg % _("hg commit"), False
3319 return None, None
3327 return None, None
3320
3328
3321 def checkafterresolved(repo):
3329 def checkafterresolved(repo):
3322 '''Inform the user about the next action after completing hg resolve
3330 '''Inform the user about the next action after completing hg resolve
3323
3331
3324 If there's a an unfinished operation that supports continue flag,
3332 If there's a an unfinished operation that supports continue flag,
3325 howtocontinue will yield repo.ui.warn as the reporter.
3333 howtocontinue will yield repo.ui.warn as the reporter.
3326
3334
3327 Otherwise, it will yield repo.ui.note.
3335 Otherwise, it will yield repo.ui.note.
3328 '''
3336 '''
3329 msg, warning = howtocontinue(repo)
3337 msg, warning = howtocontinue(repo)
3330 if msg is not None:
3338 if msg is not None:
3331 if warning:
3339 if warning:
3332 repo.ui.warn("%s\n" % msg)
3340 repo.ui.warn("%s\n" % msg)
3333 else:
3341 else:
3334 repo.ui.note("%s\n" % msg)
3342 repo.ui.note("%s\n" % msg)
3335
3343
3336 def wrongtooltocontinue(repo, task):
3344 def wrongtooltocontinue(repo, task):
3337 '''Raise an abort suggesting how to properly continue if there is an
3345 '''Raise an abort suggesting how to properly continue if there is an
3338 active task.
3346 active task.
3339
3347
3340 Uses howtocontinue() to find the active task.
3348 Uses howtocontinue() to find the active task.
3341
3349
3342 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3350 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3343 a hint.
3351 a hint.
3344 '''
3352 '''
3345 after = howtocontinue(repo)
3353 after = howtocontinue(repo)
3346 hint = None
3354 hint = None
3347 if after[1]:
3355 if after[1]:
3348 hint = after[0]
3356 hint = after[0]
3349 raise error.Abort(_('no %s in progress') % task, hint=hint)
3357 raise error.Abort(_('no %s in progress') % task, hint=hint)
3350
3358
3351 def abortgraft(ui, repo, graftstate):
3359 def abortgraft(ui, repo, graftstate):
3352 """abort the interrupted graft and rollbacks to the state before interrupted
3360 """abort the interrupted graft and rollbacks to the state before interrupted
3353 graft"""
3361 graft"""
3354 if not graftstate.exists():
3362 if not graftstate.exists():
3355 raise error.Abort(_("no interrupted graft to abort"))
3363 raise error.Abort(_("no interrupted graft to abort"))
3356 statedata = readgraftstate(repo, graftstate)
3364 statedata = readgraftstate(repo, graftstate)
3357 newnodes = statedata.get('newnodes')
3365 newnodes = statedata.get('newnodes')
3358 if newnodes is None:
3366 if newnodes is None:
3359 # and old graft state which does not have all the data required to abort
3367 # and old graft state which does not have all the data required to abort
3360 # the graft
3368 # the graft
3361 raise error.Abort(_("cannot abort using an old graftstate"))
3369 raise error.Abort(_("cannot abort using an old graftstate"))
3362
3370
3363 # changeset from which graft operation was started
3371 # changeset from which graft operation was started
3364 if len(newnodes) > 0:
3372 if len(newnodes) > 0:
3365 startctx = repo[newnodes[0]].p1()
3373 startctx = repo[newnodes[0]].p1()
3366 else:
3374 else:
3367 startctx = repo['.']
3375 startctx = repo['.']
3368 # whether to strip or not
3376 # whether to strip or not
3369 cleanup = False
3377 cleanup = False
3370 from . import hg
3378 from . import hg
3371 if newnodes:
3379 if newnodes:
3372 newnodes = [repo[r].rev() for r in newnodes]
3380 newnodes = [repo[r].rev() for r in newnodes]
3373 cleanup = True
3381 cleanup = True
3374 # checking that none of the newnodes turned public or is public
3382 # checking that none of the newnodes turned public or is public
3375 immutable = [c for c in newnodes if not repo[c].mutable()]
3383 immutable = [c for c in newnodes if not repo[c].mutable()]
3376 if immutable:
3384 if immutable:
3377 repo.ui.warn(_("cannot clean up public changesets %s\n")
3385 repo.ui.warn(_("cannot clean up public changesets %s\n")
3378 % ', '.join(bytes(repo[r]) for r in immutable),
3386 % ', '.join(bytes(repo[r]) for r in immutable),
3379 hint=_("see 'hg help phases' for details"))
3387 hint=_("see 'hg help phases' for details"))
3380 cleanup = False
3388 cleanup = False
3381
3389
3382 # checking that no new nodes are created on top of grafted revs
3390 # checking that no new nodes are created on top of grafted revs
3383 desc = set(repo.changelog.descendants(newnodes))
3391 desc = set(repo.changelog.descendants(newnodes))
3384 if desc - set(newnodes):
3392 if desc - set(newnodes):
3385 repo.ui.warn(_("new changesets detected on destination "
3393 repo.ui.warn(_("new changesets detected on destination "
3386 "branch, can't strip\n"))
3394 "branch, can't strip\n"))
3387 cleanup = False
3395 cleanup = False
3388
3396
3389 if cleanup:
3397 if cleanup:
3390 with repo.wlock(), repo.lock():
3398 with repo.wlock(), repo.lock():
3391 hg.updaterepo(repo, startctx.node(), overwrite=True)
3399 hg.updaterepo(repo, startctx.node(), overwrite=True)
3392 # stripping the new nodes created
3400 # stripping the new nodes created
3393 strippoints = [c.node() for c in repo.set("roots(%ld)",
3401 strippoints = [c.node() for c in repo.set("roots(%ld)",
3394 newnodes)]
3402 newnodes)]
3395 repair.strip(repo.ui, repo, strippoints, backup=False)
3403 repair.strip(repo.ui, repo, strippoints, backup=False)
3396
3404
3397 if not cleanup:
3405 if not cleanup:
3398 # we don't update to the startnode if we can't strip
3406 # we don't update to the startnode if we can't strip
3399 startctx = repo['.']
3407 startctx = repo['.']
3400 hg.updaterepo(repo, startctx.node(), overwrite=True)
3408 hg.updaterepo(repo, startctx.node(), overwrite=True)
3401
3409
3402 ui.status(_("graft aborted\n"))
3410 ui.status(_("graft aborted\n"))
3403 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
3411 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
3404 graftstate.delete()
3412 graftstate.delete()
3405 return 0
3413 return 0
3406
3414
3407 def readgraftstate(repo, graftstate):
3415 def readgraftstate(repo, graftstate):
3408 """read the graft state file and return a dict of the data stored in it"""
3416 """read the graft state file and return a dict of the data stored in it"""
3409 try:
3417 try:
3410 return graftstate.read()
3418 return graftstate.read()
3411 except error.CorruptedState:
3419 except error.CorruptedState:
3412 nodes = repo.vfs.read('graftstate').splitlines()
3420 nodes = repo.vfs.read('graftstate').splitlines()
3413 return {'nodes': nodes}
3421 return {'nodes': nodes}
@@ -1,6366 +1,6370 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 formatteropts = cmdutil.formatteropts
121 formatteropts = cmdutil.formatteropts
122 templateopts = cmdutil.templateopts
122 templateopts = cmdutil.templateopts
123 logopts = cmdutil.logopts
123 logopts = cmdutil.logopts
124 diffopts = cmdutil.diffopts
124 diffopts = cmdutil.diffopts
125 diffwsopts = cmdutil.diffwsopts
125 diffwsopts = cmdutil.diffwsopts
126 diffopts2 = cmdutil.diffopts2
126 diffopts2 = cmdutil.diffopts2
127 mergetoolopts = cmdutil.mergetoolopts
127 mergetoolopts = cmdutil.mergetoolopts
128 similarityopts = cmdutil.similarityopts
128 similarityopts = cmdutil.similarityopts
129 subrepoopts = cmdutil.subrepoopts
129 subrepoopts = cmdutil.subrepoopts
130 debugrevlogopts = cmdutil.debugrevlogopts
130 debugrevlogopts = cmdutil.debugrevlogopts
131
131
132 # Commands start here, listed alphabetically
132 # Commands start here, listed alphabetically
133
133
134 @command('add',
134 @command('add',
135 walkopts + subrepoopts + dryrunopts,
135 walkopts + subrepoopts + dryrunopts,
136 _('[OPTION]... [FILE]...'),
136 _('[OPTION]... [FILE]...'),
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 helpbasic=True, inferrepo=True)
138 helpbasic=True, inferrepo=True)
139 def add(ui, repo, *pats, **opts):
139 def add(ui, repo, *pats, **opts):
140 """add the specified files on the next commit
140 """add the specified files on the next commit
141
141
142 Schedule files to be version controlled and added to the
142 Schedule files to be version controlled and added to the
143 repository.
143 repository.
144
144
145 The files will be added to the repository at the next commit. To
145 The files will be added to the repository at the next commit. To
146 undo an add before that, see :hg:`forget`.
146 undo an add before that, see :hg:`forget`.
147
147
148 If no names are given, add all files to the repository (except
148 If no names are given, add all files to the repository (except
149 files matching ``.hgignore``).
149 files matching ``.hgignore``).
150
150
151 .. container:: verbose
151 .. container:: verbose
152
152
153 Examples:
153 Examples:
154
154
155 - New (unknown) files are added
155 - New (unknown) files are added
156 automatically by :hg:`add`::
156 automatically by :hg:`add`::
157
157
158 $ ls
158 $ ls
159 foo.c
159 foo.c
160 $ hg status
160 $ hg status
161 ? foo.c
161 ? foo.c
162 $ hg add
162 $ hg add
163 adding foo.c
163 adding foo.c
164 $ hg status
164 $ hg status
165 A foo.c
165 A foo.c
166
166
167 - Specific files to be added can be specified::
167 - Specific files to be added can be specified::
168
168
169 $ ls
169 $ ls
170 bar.c foo.c
170 bar.c foo.c
171 $ hg status
171 $ hg status
172 ? bar.c
172 ? bar.c
173 ? foo.c
173 ? foo.c
174 $ hg add bar.c
174 $ hg add bar.c
175 $ hg status
175 $ hg status
176 A bar.c
176 A bar.c
177 ? foo.c
177 ? foo.c
178
178
179 Returns 0 if all files are successfully added.
179 Returns 0 if all files are successfully added.
180 """
180 """
181
181
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
185 return rejected and 1 or 0
185 return rejected and 1 or 0
186
186
187 @command('addremove',
187 @command('addremove',
188 similarityopts + subrepoopts + walkopts + dryrunopts,
188 similarityopts + subrepoopts + walkopts + dryrunopts,
189 _('[OPTION]... [FILE]...'),
189 _('[OPTION]... [FILE]...'),
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
191 inferrepo=True)
191 inferrepo=True)
192 def addremove(ui, repo, *pats, **opts):
192 def addremove(ui, repo, *pats, **opts):
193 """add all new files, delete all missing files
193 """add all new files, delete all missing files
194
194
195 Add all new files and remove all missing files from the
195 Add all new files and remove all missing files from the
196 repository.
196 repository.
197
197
198 Unless names are given, new files are ignored if they match any of
198 Unless names are given, new files are ignored if they match any of
199 the patterns in ``.hgignore``. As with add, these changes take
199 the patterns in ``.hgignore``. As with add, these changes take
200 effect at the next commit.
200 effect at the next commit.
201
201
202 Use the -s/--similarity option to detect renamed files. This
202 Use the -s/--similarity option to detect renamed files. This
203 option takes a percentage between 0 (disabled) and 100 (files must
203 option takes a percentage between 0 (disabled) and 100 (files must
204 be identical) as its parameter. With a parameter greater than 0,
204 be identical) as its parameter. With a parameter greater than 0,
205 this compares every removed file with every added file and records
205 this compares every removed file with every added file and records
206 those similar enough as renames. Detecting renamed files this way
206 those similar enough as renames. Detecting renamed files this way
207 can be expensive. After using this option, :hg:`status -C` can be
207 can be expensive. After using this option, :hg:`status -C` can be
208 used to check which files were identified as moved or renamed. If
208 used to check which files were identified as moved or renamed. If
209 not specified, -s/--similarity defaults to 100 and only renames of
209 not specified, -s/--similarity defaults to 100 and only renames of
210 identical files are detected.
210 identical files are detected.
211
211
212 .. container:: verbose
212 .. container:: verbose
213
213
214 Examples:
214 Examples:
215
215
216 - A number of files (bar.c and foo.c) are new,
216 - A number of files (bar.c and foo.c) are new,
217 while foobar.c has been removed (without using :hg:`remove`)
217 while foobar.c has been removed (without using :hg:`remove`)
218 from the repository::
218 from the repository::
219
219
220 $ ls
220 $ ls
221 bar.c foo.c
221 bar.c foo.c
222 $ hg status
222 $ hg status
223 ! foobar.c
223 ! foobar.c
224 ? bar.c
224 ? bar.c
225 ? foo.c
225 ? foo.c
226 $ hg addremove
226 $ hg addremove
227 adding bar.c
227 adding bar.c
228 adding foo.c
228 adding foo.c
229 removing foobar.c
229 removing foobar.c
230 $ hg status
230 $ hg status
231 A bar.c
231 A bar.c
232 A foo.c
232 A foo.c
233 R foobar.c
233 R foobar.c
234
234
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
236 Afterwards, it was edited slightly::
236 Afterwards, it was edited slightly::
237
237
238 $ ls
238 $ ls
239 foo.c
239 foo.c
240 $ hg status
240 $ hg status
241 ! foobar.c
241 ! foobar.c
242 ? foo.c
242 ? foo.c
243 $ hg addremove --similarity 90
243 $ hg addremove --similarity 90
244 removing foobar.c
244 removing foobar.c
245 adding foo.c
245 adding foo.c
246 recording removal of foobar.c as rename to foo.c (94% similar)
246 recording removal of foobar.c as rename to foo.c (94% similar)
247 $ hg status -C
247 $ hg status -C
248 A foo.c
248 A foo.c
249 foobar.c
249 foobar.c
250 R foobar.c
250 R foobar.c
251
251
252 Returns 0 if all files are successfully added.
252 Returns 0 if all files are successfully added.
253 """
253 """
254 opts = pycompat.byteskwargs(opts)
254 opts = pycompat.byteskwargs(opts)
255 if not opts.get('similarity'):
255 if not opts.get('similarity'):
256 opts['similarity'] = '100'
256 opts['similarity'] = '100'
257 matcher = scmutil.match(repo[None], pats, opts)
257 matcher = scmutil.match(repo[None], pats, opts)
258 relative = scmutil.anypats(pats, opts)
258 relative = scmutil.anypats(pats, opts)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
261
261
262 @command('annotate|blame',
262 @command('annotate|blame',
263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
264 ('', 'follow', None,
264 ('', 'follow', None,
265 _('follow copies/renames and list the filename (DEPRECATED)')),
265 _('follow copies/renames and list the filename (DEPRECATED)')),
266 ('', 'no-follow', None, _("don't follow copies and renames")),
266 ('', 'no-follow', None, _("don't follow copies and renames")),
267 ('a', 'text', None, _('treat all files as text')),
267 ('a', 'text', None, _('treat all files as text')),
268 ('u', 'user', None, _('list the author (long with -v)')),
268 ('u', 'user', None, _('list the author (long with -v)')),
269 ('f', 'file', None, _('list the filename')),
269 ('f', 'file', None, _('list the filename')),
270 ('d', 'date', None, _('list the date (short with -q)')),
270 ('d', 'date', None, _('list the date (short with -q)')),
271 ('n', 'number', None, _('list the revision number (default)')),
271 ('n', 'number', None, _('list the revision number (default)')),
272 ('c', 'changeset', None, _('list the changeset')),
272 ('c', 'changeset', None, _('list the changeset')),
273 ('l', 'line-number', None, _('show line number at the first appearance')),
273 ('l', 'line-number', None, _('show line number at the first appearance')),
274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
275 ] + diffwsopts + walkopts + formatteropts,
275 ] + diffwsopts + walkopts + formatteropts,
276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
277 helpcategory=command.CATEGORY_FILE_CONTENTS,
277 helpcategory=command.CATEGORY_FILE_CONTENTS,
278 helpbasic=True, inferrepo=True)
278 helpbasic=True, inferrepo=True)
279 def annotate(ui, repo, *pats, **opts):
279 def annotate(ui, repo, *pats, **opts):
280 """show changeset information by line for each file
280 """show changeset information by line for each file
281
281
282 List changes in files, showing the revision id responsible for
282 List changes in files, showing the revision id responsible for
283 each line.
283 each line.
284
284
285 This command is useful for discovering when a change was made and
285 This command is useful for discovering when a change was made and
286 by whom.
286 by whom.
287
287
288 If you include --file, --user, or --date, the revision number is
288 If you include --file, --user, or --date, the revision number is
289 suppressed unless you also include --number.
289 suppressed unless you also include --number.
290
290
291 Without the -a/--text option, annotate will avoid processing files
291 Without the -a/--text option, annotate will avoid processing files
292 it detects as binary. With -a, annotate will annotate the file
292 it detects as binary. With -a, annotate will annotate the file
293 anyway, although the results will probably be neither useful
293 anyway, although the results will probably be neither useful
294 nor desirable.
294 nor desirable.
295
295
296 .. container:: verbose
296 .. container:: verbose
297
297
298 Template:
298 Template:
299
299
300 The following keywords are supported in addition to the common template
300 The following keywords are supported in addition to the common template
301 keywords and functions. See also :hg:`help templates`.
301 keywords and functions. See also :hg:`help templates`.
302
302
303 :lines: List of lines with annotation data.
303 :lines: List of lines with annotation data.
304 :path: String. Repository-absolute path of the specified file.
304 :path: String. Repository-absolute path of the specified file.
305
305
306 And each entry of ``{lines}`` provides the following sub-keywords in
306 And each entry of ``{lines}`` provides the following sub-keywords in
307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
308
308
309 :line: String. Line content.
309 :line: String. Line content.
310 :lineno: Integer. Line number at that revision.
310 :lineno: Integer. Line number at that revision.
311 :path: String. Repository-absolute path of the file at that revision.
311 :path: String. Repository-absolute path of the file at that revision.
312
312
313 See :hg:`help templates.operators` for the list expansion syntax.
313 See :hg:`help templates.operators` for the list expansion syntax.
314
314
315 Returns 0 on success.
315 Returns 0 on success.
316 """
316 """
317 opts = pycompat.byteskwargs(opts)
317 opts = pycompat.byteskwargs(opts)
318 if not pats:
318 if not pats:
319 raise error.Abort(_('at least one filename or pattern is required'))
319 raise error.Abort(_('at least one filename or pattern is required'))
320
320
321 if opts.get('follow'):
321 if opts.get('follow'):
322 # --follow is deprecated and now just an alias for -f/--file
322 # --follow is deprecated and now just an alias for -f/--file
323 # to mimic the behavior of Mercurial before version 1.5
323 # to mimic the behavior of Mercurial before version 1.5
324 opts['file'] = True
324 opts['file'] = True
325
325
326 if (not opts.get('user') and not opts.get('changeset')
326 if (not opts.get('user') and not opts.get('changeset')
327 and not opts.get('date') and not opts.get('file')):
327 and not opts.get('date') and not opts.get('file')):
328 opts['number'] = True
328 opts['number'] = True
329
329
330 linenumber = opts.get('line_number') is not None
330 linenumber = opts.get('line_number') is not None
331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
332 raise error.Abort(_('at least one of -n/-c is required for -l'))
332 raise error.Abort(_('at least one of -n/-c is required for -l'))
333
333
334 rev = opts.get('rev')
334 rev = opts.get('rev')
335 if rev:
335 if rev:
336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
337 ctx = scmutil.revsingle(repo, rev)
337 ctx = scmutil.revsingle(repo, rev)
338
338
339 ui.pager('annotate')
339 ui.pager('annotate')
340 rootfm = ui.formatter('annotate', opts)
340 rootfm = ui.formatter('annotate', opts)
341 if ui.debugflag:
341 if ui.debugflag:
342 shorthex = pycompat.identity
342 shorthex = pycompat.identity
343 else:
343 else:
344 def shorthex(h):
344 def shorthex(h):
345 return h[:12]
345 return h[:12]
346 if ui.quiet:
346 if ui.quiet:
347 datefunc = dateutil.shortdate
347 datefunc = dateutil.shortdate
348 else:
348 else:
349 datefunc = dateutil.datestr
349 datefunc = dateutil.datestr
350 if ctx.rev() is None:
350 if ctx.rev() is None:
351 if opts.get('changeset'):
351 if opts.get('changeset'):
352 # omit "+" suffix which is appended to node hex
352 # omit "+" suffix which is appended to node hex
353 def formatrev(rev):
353 def formatrev(rev):
354 if rev == wdirrev:
354 if rev == wdirrev:
355 return '%d' % ctx.p1().rev()
355 return '%d' % ctx.p1().rev()
356 else:
356 else:
357 return '%d' % rev
357 return '%d' % rev
358 else:
358 else:
359 def formatrev(rev):
359 def formatrev(rev):
360 if rev == wdirrev:
360 if rev == wdirrev:
361 return '%d+' % ctx.p1().rev()
361 return '%d+' % ctx.p1().rev()
362 else:
362 else:
363 return '%d ' % rev
363 return '%d ' % rev
364 def formathex(h):
364 def formathex(h):
365 if h == wdirhex:
365 if h == wdirhex:
366 return '%s+' % shorthex(hex(ctx.p1().node()))
366 return '%s+' % shorthex(hex(ctx.p1().node()))
367 else:
367 else:
368 return '%s ' % shorthex(h)
368 return '%s ' % shorthex(h)
369 else:
369 else:
370 formatrev = b'%d'.__mod__
370 formatrev = b'%d'.__mod__
371 formathex = shorthex
371 formathex = shorthex
372
372
373 opmap = [
373 opmap = [
374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
380 ]
380 ]
381 opnamemap = {
381 opnamemap = {
382 'rev': 'number',
382 'rev': 'number',
383 'node': 'changeset',
383 'node': 'changeset',
384 'path': 'file',
384 'path': 'file',
385 'lineno': 'line_number',
385 'lineno': 'line_number',
386 }
386 }
387
387
388 if rootfm.isplain():
388 if rootfm.isplain():
389 def makefunc(get, fmt):
389 def makefunc(get, fmt):
390 return lambda x: fmt(get(x))
390 return lambda x: fmt(get(x))
391 else:
391 else:
392 def makefunc(get, fmt):
392 def makefunc(get, fmt):
393 return get
393 return get
394 datahint = rootfm.datahint()
394 datahint = rootfm.datahint()
395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
400
400
401 def bad(x, y):
401 def bad(x, y):
402 raise error.Abort("%s: %s" % (x, y))
402 raise error.Abort("%s: %s" % (x, y))
403
403
404 m = scmutil.match(ctx, pats, opts, badfn=bad)
404 m = scmutil.match(ctx, pats, opts, badfn=bad)
405
405
406 follow = not opts.get('no_follow')
406 follow = not opts.get('no_follow')
407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
408 whitespace=True)
408 whitespace=True)
409 skiprevs = opts.get('skip')
409 skiprevs = opts.get('skip')
410 if skiprevs:
410 if skiprevs:
411 skiprevs = scmutil.revrange(repo, skiprevs)
411 skiprevs = scmutil.revrange(repo, skiprevs)
412
412
413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
414 for abs in ctx.walk(m):
414 for abs in ctx.walk(m):
415 fctx = ctx[abs]
415 fctx = ctx[abs]
416 rootfm.startitem()
416 rootfm.startitem()
417 rootfm.data(path=abs)
417 rootfm.data(path=abs)
418 if not opts.get('text') and fctx.isbinary():
418 if not opts.get('text') and fctx.isbinary():
419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
420 continue
420 continue
421
421
422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
424 diffopts=diffopts)
424 diffopts=diffopts)
425 if not lines:
425 if not lines:
426 fm.end()
426 fm.end()
427 continue
427 continue
428 formats = []
428 formats = []
429 pieces = []
429 pieces = []
430
430
431 for f, sep in funcmap:
431 for f, sep in funcmap:
432 l = [f(n) for n in lines]
432 l = [f(n) for n in lines]
433 if fm.isplain():
433 if fm.isplain():
434 sizes = [encoding.colwidth(x) for x in l]
434 sizes = [encoding.colwidth(x) for x in l]
435 ml = max(sizes)
435 ml = max(sizes)
436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
437 else:
437 else:
438 formats.append(['%s' for x in l])
438 formats.append(['%s' for x in l])
439 pieces.append(l)
439 pieces.append(l)
440
440
441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
442 fm.startitem()
442 fm.startitem()
443 fm.context(fctx=n.fctx)
443 fm.context(fctx=n.fctx)
444 fm.write(fields, "".join(f), *p)
444 fm.write(fields, "".join(f), *p)
445 if n.skip:
445 if n.skip:
446 fmt = "* %s"
446 fmt = "* %s"
447 else:
447 else:
448 fmt = ": %s"
448 fmt = ": %s"
449 fm.write('line', fmt, n.text)
449 fm.write('line', fmt, n.text)
450
450
451 if not lines[-1].text.endswith('\n'):
451 if not lines[-1].text.endswith('\n'):
452 fm.plain('\n')
452 fm.plain('\n')
453 fm.end()
453 fm.end()
454
454
455 rootfm.end()
455 rootfm.end()
456
456
457 @command('archive',
457 @command('archive',
458 [('', 'no-decode', None, _('do not pass files through decoders')),
458 [('', 'no-decode', None, _('do not pass files through decoders')),
459 ('p', 'prefix', '', _('directory prefix for files in archive'),
459 ('p', 'prefix', '', _('directory prefix for files in archive'),
460 _('PREFIX')),
460 _('PREFIX')),
461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
463 ] + subrepoopts + walkopts,
463 ] + subrepoopts + walkopts,
464 _('[OPTION]... DEST'),
464 _('[OPTION]... DEST'),
465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
466 def archive(ui, repo, dest, **opts):
466 def archive(ui, repo, dest, **opts):
467 '''create an unversioned archive of a repository revision
467 '''create an unversioned archive of a repository revision
468
468
469 By default, the revision used is the parent of the working
469 By default, the revision used is the parent of the working
470 directory; use -r/--rev to specify a different revision.
470 directory; use -r/--rev to specify a different revision.
471
471
472 The archive type is automatically detected based on file
472 The archive type is automatically detected based on file
473 extension (to override, use -t/--type).
473 extension (to override, use -t/--type).
474
474
475 .. container:: verbose
475 .. container:: verbose
476
476
477 Examples:
477 Examples:
478
478
479 - create a zip file containing the 1.0 release::
479 - create a zip file containing the 1.0 release::
480
480
481 hg archive -r 1.0 project-1.0.zip
481 hg archive -r 1.0 project-1.0.zip
482
482
483 - create a tarball excluding .hg files::
483 - create a tarball excluding .hg files::
484
484
485 hg archive project.tar.gz -X ".hg*"
485 hg archive project.tar.gz -X ".hg*"
486
486
487 Valid types are:
487 Valid types are:
488
488
489 :``files``: a directory full of files (default)
489 :``files``: a directory full of files (default)
490 :``tar``: tar archive, uncompressed
490 :``tar``: tar archive, uncompressed
491 :``tbz2``: tar archive, compressed using bzip2
491 :``tbz2``: tar archive, compressed using bzip2
492 :``tgz``: tar archive, compressed using gzip
492 :``tgz``: tar archive, compressed using gzip
493 :``uzip``: zip archive, uncompressed
493 :``uzip``: zip archive, uncompressed
494 :``zip``: zip archive, compressed using deflate
494 :``zip``: zip archive, compressed using deflate
495
495
496 The exact name of the destination archive or directory is given
496 The exact name of the destination archive or directory is given
497 using a format string; see :hg:`help export` for details.
497 using a format string; see :hg:`help export` for details.
498
498
499 Each member added to an archive file has a directory prefix
499 Each member added to an archive file has a directory prefix
500 prepended. Use -p/--prefix to specify a format string for the
500 prepended. Use -p/--prefix to specify a format string for the
501 prefix. The default is the basename of the archive, with suffixes
501 prefix. The default is the basename of the archive, with suffixes
502 removed.
502 removed.
503
503
504 Returns 0 on success.
504 Returns 0 on success.
505 '''
505 '''
506
506
507 opts = pycompat.byteskwargs(opts)
507 opts = pycompat.byteskwargs(opts)
508 rev = opts.get('rev')
508 rev = opts.get('rev')
509 if rev:
509 if rev:
510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
511 ctx = scmutil.revsingle(repo, rev)
511 ctx = scmutil.revsingle(repo, rev)
512 if not ctx:
512 if not ctx:
513 raise error.Abort(_('no working directory: please specify a revision'))
513 raise error.Abort(_('no working directory: please specify a revision'))
514 node = ctx.node()
514 node = ctx.node()
515 dest = cmdutil.makefilename(ctx, dest)
515 dest = cmdutil.makefilename(ctx, dest)
516 if os.path.realpath(dest) == repo.root:
516 if os.path.realpath(dest) == repo.root:
517 raise error.Abort(_('repository root cannot be destination'))
517 raise error.Abort(_('repository root cannot be destination'))
518
518
519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
520 prefix = opts.get('prefix')
520 prefix = opts.get('prefix')
521
521
522 if dest == '-':
522 if dest == '-':
523 if kind == 'files':
523 if kind == 'files':
524 raise error.Abort(_('cannot archive plain files to stdout'))
524 raise error.Abort(_('cannot archive plain files to stdout'))
525 dest = cmdutil.makefileobj(ctx, dest)
525 dest = cmdutil.makefileobj(ctx, dest)
526 if not prefix:
526 if not prefix:
527 prefix = os.path.basename(repo.root) + '-%h'
527 prefix = os.path.basename(repo.root) + '-%h'
528
528
529 prefix = cmdutil.makefilename(ctx, prefix)
529 prefix = cmdutil.makefilename(ctx, prefix)
530 match = scmutil.match(ctx, [], opts)
530 match = scmutil.match(ctx, [], opts)
531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
532 match, prefix, subrepos=opts.get('subrepos'))
532 match, prefix, subrepos=opts.get('subrepos'))
533
533
534 @command('backout',
534 @command('backout',
535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
536 ('', 'commit', None,
536 ('', 'commit', None,
537 _('commit if no conflicts were encountered (DEPRECATED)')),
537 _('commit if no conflicts were encountered (DEPRECATED)')),
538 ('', 'no-commit', None, _('do not commit')),
538 ('', 'no-commit', None, _('do not commit')),
539 ('', 'parent', '',
539 ('', 'parent', '',
540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
541 ('r', 'rev', '', _('revision to backout'), _('REV')),
541 ('r', 'rev', '', _('revision to backout'), _('REV')),
542 ('e', 'edit', False, _('invoke editor on commit messages')),
542 ('e', 'edit', False, _('invoke editor on commit messages')),
543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
544 _('[OPTION]... [-r] REV'),
544 _('[OPTION]... [-r] REV'),
545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
546 def backout(ui, repo, node=None, rev=None, **opts):
546 def backout(ui, repo, node=None, rev=None, **opts):
547 '''reverse effect of earlier changeset
547 '''reverse effect of earlier changeset
548
548
549 Prepare a new changeset with the effect of REV undone in the
549 Prepare a new changeset with the effect of REV undone in the
550 current working directory. If no conflicts were encountered,
550 current working directory. If no conflicts were encountered,
551 it will be committed immediately.
551 it will be committed immediately.
552
552
553 If REV is the parent of the working directory, then this new changeset
553 If REV is the parent of the working directory, then this new changeset
554 is committed automatically (unless --no-commit is specified).
554 is committed automatically (unless --no-commit is specified).
555
555
556 .. note::
556 .. note::
557
557
558 :hg:`backout` cannot be used to fix either an unwanted or
558 :hg:`backout` cannot be used to fix either an unwanted or
559 incorrect merge.
559 incorrect merge.
560
560
561 .. container:: verbose
561 .. container:: verbose
562
562
563 Examples:
563 Examples:
564
564
565 - Reverse the effect of the parent of the working directory.
565 - Reverse the effect of the parent of the working directory.
566 This backout will be committed immediately::
566 This backout will be committed immediately::
567
567
568 hg backout -r .
568 hg backout -r .
569
569
570 - Reverse the effect of previous bad revision 23::
570 - Reverse the effect of previous bad revision 23::
571
571
572 hg backout -r 23
572 hg backout -r 23
573
573
574 - Reverse the effect of previous bad revision 23 and
574 - Reverse the effect of previous bad revision 23 and
575 leave changes uncommitted::
575 leave changes uncommitted::
576
576
577 hg backout -r 23 --no-commit
577 hg backout -r 23 --no-commit
578 hg commit -m "Backout revision 23"
578 hg commit -m "Backout revision 23"
579
579
580 By default, the pending changeset will have one parent,
580 By default, the pending changeset will have one parent,
581 maintaining a linear history. With --merge, the pending
581 maintaining a linear history. With --merge, the pending
582 changeset will instead have two parents: the old parent of the
582 changeset will instead have two parents: the old parent of the
583 working directory and a new child of REV that simply undoes REV.
583 working directory and a new child of REV that simply undoes REV.
584
584
585 Before version 1.7, the behavior without --merge was equivalent
585 Before version 1.7, the behavior without --merge was equivalent
586 to specifying --merge followed by :hg:`update --clean .` to
586 to specifying --merge followed by :hg:`update --clean .` to
587 cancel the merge and leave the child of REV as a head to be
587 cancel the merge and leave the child of REV as a head to be
588 merged separately.
588 merged separately.
589
589
590 See :hg:`help dates` for a list of formats valid for -d/--date.
590 See :hg:`help dates` for a list of formats valid for -d/--date.
591
591
592 See :hg:`help revert` for a way to restore files to the state
592 See :hg:`help revert` for a way to restore files to the state
593 of another revision.
593 of another revision.
594
594
595 Returns 0 on success, 1 if nothing to backout or there are unresolved
595 Returns 0 on success, 1 if nothing to backout or there are unresolved
596 files.
596 files.
597 '''
597 '''
598 with repo.wlock(), repo.lock():
598 with repo.wlock(), repo.lock():
599 return _dobackout(ui, repo, node, rev, **opts)
599 return _dobackout(ui, repo, node, rev, **opts)
600
600
601 def _dobackout(ui, repo, node=None, rev=None, **opts):
601 def _dobackout(ui, repo, node=None, rev=None, **opts):
602 opts = pycompat.byteskwargs(opts)
602 opts = pycompat.byteskwargs(opts)
603 if opts.get('commit') and opts.get('no_commit'):
603 if opts.get('commit') and opts.get('no_commit'):
604 raise error.Abort(_("cannot use --commit with --no-commit"))
604 raise error.Abort(_("cannot use --commit with --no-commit"))
605 if opts.get('merge') and opts.get('no_commit'):
605 if opts.get('merge') and opts.get('no_commit'):
606 raise error.Abort(_("cannot use --merge with --no-commit"))
606 raise error.Abort(_("cannot use --merge with --no-commit"))
607
607
608 if rev and node:
608 if rev and node:
609 raise error.Abort(_("please specify just one revision"))
609 raise error.Abort(_("please specify just one revision"))
610
610
611 if not rev:
611 if not rev:
612 rev = node
612 rev = node
613
613
614 if not rev:
614 if not rev:
615 raise error.Abort(_("please specify a revision to backout"))
615 raise error.Abort(_("please specify a revision to backout"))
616
616
617 date = opts.get('date')
617 date = opts.get('date')
618 if date:
618 if date:
619 opts['date'] = dateutil.parsedate(date)
619 opts['date'] = dateutil.parsedate(date)
620
620
621 cmdutil.checkunfinished(repo)
621 cmdutil.checkunfinished(repo)
622 cmdutil.bailifchanged(repo)
622 cmdutil.bailifchanged(repo)
623 node = scmutil.revsingle(repo, rev).node()
623 node = scmutil.revsingle(repo, rev).node()
624
624
625 op1, op2 = repo.dirstate.parents()
625 op1, op2 = repo.dirstate.parents()
626 if not repo.changelog.isancestor(node, op1):
626 if not repo.changelog.isancestor(node, op1):
627 raise error.Abort(_('cannot backout change that is not an ancestor'))
627 raise error.Abort(_('cannot backout change that is not an ancestor'))
628
628
629 p1, p2 = repo.changelog.parents(node)
629 p1, p2 = repo.changelog.parents(node)
630 if p1 == nullid:
630 if p1 == nullid:
631 raise error.Abort(_('cannot backout a change with no parents'))
631 raise error.Abort(_('cannot backout a change with no parents'))
632 if p2 != nullid:
632 if p2 != nullid:
633 if not opts.get('parent'):
633 if not opts.get('parent'):
634 raise error.Abort(_('cannot backout a merge changeset'))
634 raise error.Abort(_('cannot backout a merge changeset'))
635 p = repo.lookup(opts['parent'])
635 p = repo.lookup(opts['parent'])
636 if p not in (p1, p2):
636 if p not in (p1, p2):
637 raise error.Abort(_('%s is not a parent of %s') %
637 raise error.Abort(_('%s is not a parent of %s') %
638 (short(p), short(node)))
638 (short(p), short(node)))
639 parent = p
639 parent = p
640 else:
640 else:
641 if opts.get('parent'):
641 if opts.get('parent'):
642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
643 parent = p1
643 parent = p1
644
644
645 # the backout should appear on the same branch
645 # the backout should appear on the same branch
646 branch = repo.dirstate.branch()
646 branch = repo.dirstate.branch()
647 bheads = repo.branchheads(branch)
647 bheads = repo.branchheads(branch)
648 rctx = scmutil.revsingle(repo, hex(parent))
648 rctx = scmutil.revsingle(repo, hex(parent))
649 if not opts.get('merge') and op1 != node:
649 if not opts.get('merge') and op1 != node:
650 with dirstateguard.dirstateguard(repo, 'backout'):
650 with dirstateguard.dirstateguard(repo, 'backout'):
651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
652 with ui.configoverride(overrides, 'backout'):
652 with ui.configoverride(overrides, 'backout'):
653 stats = mergemod.update(repo, parent, branchmerge=True,
653 stats = mergemod.update(repo, parent, branchmerge=True,
654 force=True, ancestor=node,
654 force=True, ancestor=node,
655 mergeancestor=False)
655 mergeancestor=False)
656 repo.setparents(op1, op2)
656 repo.setparents(op1, op2)
657 hg._showstats(repo, stats)
657 hg._showstats(repo, stats)
658 if stats.unresolvedcount:
658 if stats.unresolvedcount:
659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
660 "file merges\n"))
660 "file merges\n"))
661 return 1
661 return 1
662 else:
662 else:
663 hg.clean(repo, node, show_stats=False)
663 hg.clean(repo, node, show_stats=False)
664 repo.dirstate.setbranch(branch)
664 repo.dirstate.setbranch(branch)
665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
666
666
667 if opts.get('no_commit'):
667 if opts.get('no_commit'):
668 msg = _("changeset %s backed out, "
668 msg = _("changeset %s backed out, "
669 "don't forget to commit.\n")
669 "don't forget to commit.\n")
670 ui.status(msg % short(node))
670 ui.status(msg % short(node))
671 return 0
671 return 0
672
672
673 def commitfunc(ui, repo, message, match, opts):
673 def commitfunc(ui, repo, message, match, opts):
674 editform = 'backout'
674 editform = 'backout'
675 e = cmdutil.getcommiteditor(editform=editform,
675 e = cmdutil.getcommiteditor(editform=editform,
676 **pycompat.strkwargs(opts))
676 **pycompat.strkwargs(opts))
677 if not message:
677 if not message:
678 # we don't translate commit messages
678 # we don't translate commit messages
679 message = "Backed out changeset %s" % short(node)
679 message = "Backed out changeset %s" % short(node)
680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
681 return repo.commit(message, opts.get('user'), opts.get('date'),
681 return repo.commit(message, opts.get('user'), opts.get('date'),
682 match, editor=e)
682 match, editor=e)
683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
684 if not newnode:
684 if not newnode:
685 ui.status(_("nothing changed\n"))
685 ui.status(_("nothing changed\n"))
686 return 1
686 return 1
687 cmdutil.commitstatus(repo, newnode, branch, bheads)
687 cmdutil.commitstatus(repo, newnode, branch, bheads)
688
688
689 def nice(node):
689 def nice(node):
690 return '%d:%s' % (repo.changelog.rev(node), short(node))
690 return '%d:%s' % (repo.changelog.rev(node), short(node))
691 ui.status(_('changeset %s backs out changeset %s\n') %
691 ui.status(_('changeset %s backs out changeset %s\n') %
692 (nice(repo.changelog.tip()), nice(node)))
692 (nice(repo.changelog.tip()), nice(node)))
693 if opts.get('merge') and op1 != node:
693 if opts.get('merge') and op1 != node:
694 hg.clean(repo, op1, show_stats=False)
694 hg.clean(repo, op1, show_stats=False)
695 ui.status(_('merging with changeset %s\n')
695 ui.status(_('merging with changeset %s\n')
696 % nice(repo.changelog.tip()))
696 % nice(repo.changelog.tip()))
697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
698 with ui.configoverride(overrides, 'backout'):
698 with ui.configoverride(overrides, 'backout'):
699 return hg.merge(repo, hex(repo.changelog.tip()))
699 return hg.merge(repo, hex(repo.changelog.tip()))
700 return 0
700 return 0
701
701
702 @command('bisect',
702 @command('bisect',
703 [('r', 'reset', False, _('reset bisect state')),
703 [('r', 'reset', False, _('reset bisect state')),
704 ('g', 'good', False, _('mark changeset good')),
704 ('g', 'good', False, _('mark changeset good')),
705 ('b', 'bad', False, _('mark changeset bad')),
705 ('b', 'bad', False, _('mark changeset bad')),
706 ('s', 'skip', False, _('skip testing changeset')),
706 ('s', 'skip', False, _('skip testing changeset')),
707 ('e', 'extend', False, _('extend the bisect range')),
707 ('e', 'extend', False, _('extend the bisect range')),
708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
709 ('U', 'noupdate', False, _('do not update to target'))],
709 ('U', 'noupdate', False, _('do not update to target'))],
710 _("[-gbsr] [-U] [-c CMD] [REV]"),
710 _("[-gbsr] [-U] [-c CMD] [REV]"),
711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
712 def bisect(ui, repo, rev=None, extra=None, command=None,
712 def bisect(ui, repo, rev=None, extra=None, command=None,
713 reset=None, good=None, bad=None, skip=None, extend=None,
713 reset=None, good=None, bad=None, skip=None, extend=None,
714 noupdate=None):
714 noupdate=None):
715 """subdivision search of changesets
715 """subdivision search of changesets
716
716
717 This command helps to find changesets which introduce problems. To
717 This command helps to find changesets which introduce problems. To
718 use, mark the earliest changeset you know exhibits the problem as
718 use, mark the earliest changeset you know exhibits the problem as
719 bad, then mark the latest changeset which is free from the problem
719 bad, then mark the latest changeset which is free from the problem
720 as good. Bisect will update your working directory to a revision
720 as good. Bisect will update your working directory to a revision
721 for testing (unless the -U/--noupdate option is specified). Once
721 for testing (unless the -U/--noupdate option is specified). Once
722 you have performed tests, mark the working directory as good or
722 you have performed tests, mark the working directory as good or
723 bad, and bisect will either update to another candidate changeset
723 bad, and bisect will either update to another candidate changeset
724 or announce that it has found the bad revision.
724 or announce that it has found the bad revision.
725
725
726 As a shortcut, you can also use the revision argument to mark a
726 As a shortcut, you can also use the revision argument to mark a
727 revision as good or bad without checking it out first.
727 revision as good or bad without checking it out first.
728
728
729 If you supply a command, it will be used for automatic bisection.
729 If you supply a command, it will be used for automatic bisection.
730 The environment variable HG_NODE will contain the ID of the
730 The environment variable HG_NODE will contain the ID of the
731 changeset being tested. The exit status of the command will be
731 changeset being tested. The exit status of the command will be
732 used to mark revisions as good or bad: status 0 means good, 125
732 used to mark revisions as good or bad: status 0 means good, 125
733 means to skip the revision, 127 (command not found) will abort the
733 means to skip the revision, 127 (command not found) will abort the
734 bisection, and any other non-zero exit status means the revision
734 bisection, and any other non-zero exit status means the revision
735 is bad.
735 is bad.
736
736
737 .. container:: verbose
737 .. container:: verbose
738
738
739 Some examples:
739 Some examples:
740
740
741 - start a bisection with known bad revision 34, and good revision 12::
741 - start a bisection with known bad revision 34, and good revision 12::
742
742
743 hg bisect --bad 34
743 hg bisect --bad 34
744 hg bisect --good 12
744 hg bisect --good 12
745
745
746 - advance the current bisection by marking current revision as good or
746 - advance the current bisection by marking current revision as good or
747 bad::
747 bad::
748
748
749 hg bisect --good
749 hg bisect --good
750 hg bisect --bad
750 hg bisect --bad
751
751
752 - mark the current revision, or a known revision, to be skipped (e.g. if
752 - mark the current revision, or a known revision, to be skipped (e.g. if
753 that revision is not usable because of another issue)::
753 that revision is not usable because of another issue)::
754
754
755 hg bisect --skip
755 hg bisect --skip
756 hg bisect --skip 23
756 hg bisect --skip 23
757
757
758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
759
759
760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
761
761
762 - forget the current bisection::
762 - forget the current bisection::
763
763
764 hg bisect --reset
764 hg bisect --reset
765
765
766 - use 'make && make tests' to automatically find the first broken
766 - use 'make && make tests' to automatically find the first broken
767 revision::
767 revision::
768
768
769 hg bisect --reset
769 hg bisect --reset
770 hg bisect --bad 34
770 hg bisect --bad 34
771 hg bisect --good 12
771 hg bisect --good 12
772 hg bisect --command "make && make tests"
772 hg bisect --command "make && make tests"
773
773
774 - see all changesets whose states are already known in the current
774 - see all changesets whose states are already known in the current
775 bisection::
775 bisection::
776
776
777 hg log -r "bisect(pruned)"
777 hg log -r "bisect(pruned)"
778
778
779 - see the changeset currently being bisected (especially useful
779 - see the changeset currently being bisected (especially useful
780 if running with -U/--noupdate)::
780 if running with -U/--noupdate)::
781
781
782 hg log -r "bisect(current)"
782 hg log -r "bisect(current)"
783
783
784 - see all changesets that took part in the current bisection::
784 - see all changesets that took part in the current bisection::
785
785
786 hg log -r "bisect(range)"
786 hg log -r "bisect(range)"
787
787
788 - you can even get a nice graph::
788 - you can even get a nice graph::
789
789
790 hg log --graph -r "bisect(range)"
790 hg log --graph -r "bisect(range)"
791
791
792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
793
793
794 Returns 0 on success.
794 Returns 0 on success.
795 """
795 """
796 # backward compatibility
796 # backward compatibility
797 if rev in "good bad reset init".split():
797 if rev in "good bad reset init".split():
798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
799 cmd, rev, extra = rev, extra, None
799 cmd, rev, extra = rev, extra, None
800 if cmd == "good":
800 if cmd == "good":
801 good = True
801 good = True
802 elif cmd == "bad":
802 elif cmd == "bad":
803 bad = True
803 bad = True
804 else:
804 else:
805 reset = True
805 reset = True
806 elif extra:
806 elif extra:
807 raise error.Abort(_('incompatible arguments'))
807 raise error.Abort(_('incompatible arguments'))
808
808
809 incompatibles = {
809 incompatibles = {
810 '--bad': bad,
810 '--bad': bad,
811 '--command': bool(command),
811 '--command': bool(command),
812 '--extend': extend,
812 '--extend': extend,
813 '--good': good,
813 '--good': good,
814 '--reset': reset,
814 '--reset': reset,
815 '--skip': skip,
815 '--skip': skip,
816 }
816 }
817
817
818 enabled = [x for x in incompatibles if incompatibles[x]]
818 enabled = [x for x in incompatibles if incompatibles[x]]
819
819
820 if len(enabled) > 1:
820 if len(enabled) > 1:
821 raise error.Abort(_('%s and %s are incompatible') %
821 raise error.Abort(_('%s and %s are incompatible') %
822 tuple(sorted(enabled)[0:2]))
822 tuple(sorted(enabled)[0:2]))
823
823
824 if reset:
824 if reset:
825 hbisect.resetstate(repo)
825 hbisect.resetstate(repo)
826 return
826 return
827
827
828 state = hbisect.load_state(repo)
828 state = hbisect.load_state(repo)
829
829
830 # update state
830 # update state
831 if good or bad or skip:
831 if good or bad or skip:
832 if rev:
832 if rev:
833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
834 else:
834 else:
835 nodes = [repo.lookup('.')]
835 nodes = [repo.lookup('.')]
836 if good:
836 if good:
837 state['good'] += nodes
837 state['good'] += nodes
838 elif bad:
838 elif bad:
839 state['bad'] += nodes
839 state['bad'] += nodes
840 elif skip:
840 elif skip:
841 state['skip'] += nodes
841 state['skip'] += nodes
842 hbisect.save_state(repo, state)
842 hbisect.save_state(repo, state)
843 if not (state['good'] and state['bad']):
843 if not (state['good'] and state['bad']):
844 return
844 return
845
845
846 def mayupdate(repo, node, show_stats=True):
846 def mayupdate(repo, node, show_stats=True):
847 """common used update sequence"""
847 """common used update sequence"""
848 if noupdate:
848 if noupdate:
849 return
849 return
850 cmdutil.checkunfinished(repo)
850 cmdutil.checkunfinished(repo)
851 cmdutil.bailifchanged(repo)
851 cmdutil.bailifchanged(repo)
852 return hg.clean(repo, node, show_stats=show_stats)
852 return hg.clean(repo, node, show_stats=show_stats)
853
853
854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
855
855
856 if command:
856 if command:
857 changesets = 1
857 changesets = 1
858 if noupdate:
858 if noupdate:
859 try:
859 try:
860 node = state['current'][0]
860 node = state['current'][0]
861 except LookupError:
861 except LookupError:
862 raise error.Abort(_('current bisect revision is unknown - '
862 raise error.Abort(_('current bisect revision is unknown - '
863 'start a new bisect to fix'))
863 'start a new bisect to fix'))
864 else:
864 else:
865 node, p2 = repo.dirstate.parents()
865 node, p2 = repo.dirstate.parents()
866 if p2 != nullid:
866 if p2 != nullid:
867 raise error.Abort(_('current bisect revision is a merge'))
867 raise error.Abort(_('current bisect revision is a merge'))
868 if rev:
868 if rev:
869 node = repo[scmutil.revsingle(repo, rev, node)].node()
869 node = repo[scmutil.revsingle(repo, rev, node)].node()
870 try:
870 try:
871 while changesets:
871 while changesets:
872 # update state
872 # update state
873 state['current'] = [node]
873 state['current'] = [node]
874 hbisect.save_state(repo, state)
874 hbisect.save_state(repo, state)
875 status = ui.system(command, environ={'HG_NODE': hex(node)},
875 status = ui.system(command, environ={'HG_NODE': hex(node)},
876 blockedtag='bisect_check')
876 blockedtag='bisect_check')
877 if status == 125:
877 if status == 125:
878 transition = "skip"
878 transition = "skip"
879 elif status == 0:
879 elif status == 0:
880 transition = "good"
880 transition = "good"
881 # status < 0 means process was killed
881 # status < 0 means process was killed
882 elif status == 127:
882 elif status == 127:
883 raise error.Abort(_("failed to execute %s") % command)
883 raise error.Abort(_("failed to execute %s") % command)
884 elif status < 0:
884 elif status < 0:
885 raise error.Abort(_("%s killed") % command)
885 raise error.Abort(_("%s killed") % command)
886 else:
886 else:
887 transition = "bad"
887 transition = "bad"
888 state[transition].append(node)
888 state[transition].append(node)
889 ctx = repo[node]
889 ctx = repo[node]
890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
891 transition))
891 transition))
892 hbisect.checkstate(state)
892 hbisect.checkstate(state)
893 # bisect
893 # bisect
894 nodes, changesets, bgood = hbisect.bisect(repo, state)
894 nodes, changesets, bgood = hbisect.bisect(repo, state)
895 # update to next check
895 # update to next check
896 node = nodes[0]
896 node = nodes[0]
897 mayupdate(repo, node, show_stats=False)
897 mayupdate(repo, node, show_stats=False)
898 finally:
898 finally:
899 state['current'] = [node]
899 state['current'] = [node]
900 hbisect.save_state(repo, state)
900 hbisect.save_state(repo, state)
901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
902 return
902 return
903
903
904 hbisect.checkstate(state)
904 hbisect.checkstate(state)
905
905
906 # actually bisect
906 # actually bisect
907 nodes, changesets, good = hbisect.bisect(repo, state)
907 nodes, changesets, good = hbisect.bisect(repo, state)
908 if extend:
908 if extend:
909 if not changesets:
909 if not changesets:
910 extendnode = hbisect.extendrange(repo, state, nodes, good)
910 extendnode = hbisect.extendrange(repo, state, nodes, good)
911 if extendnode is not None:
911 if extendnode is not None:
912 ui.write(_("Extending search to changeset %d:%s\n")
912 ui.write(_("Extending search to changeset %d:%s\n")
913 % (extendnode.rev(), extendnode))
913 % (extendnode.rev(), extendnode))
914 state['current'] = [extendnode.node()]
914 state['current'] = [extendnode.node()]
915 hbisect.save_state(repo, state)
915 hbisect.save_state(repo, state)
916 return mayupdate(repo, extendnode.node())
916 return mayupdate(repo, extendnode.node())
917 raise error.Abort(_("nothing to extend"))
917 raise error.Abort(_("nothing to extend"))
918
918
919 if changesets == 0:
919 if changesets == 0:
920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
921 else:
921 else:
922 assert len(nodes) == 1 # only a single node can be tested next
922 assert len(nodes) == 1 # only a single node can be tested next
923 node = nodes[0]
923 node = nodes[0]
924 # compute the approximate number of remaining tests
924 # compute the approximate number of remaining tests
925 tests, size = 0, 2
925 tests, size = 0, 2
926 while size <= changesets:
926 while size <= changesets:
927 tests, size = tests + 1, size * 2
927 tests, size = tests + 1, size * 2
928 rev = repo.changelog.rev(node)
928 rev = repo.changelog.rev(node)
929 ui.write(_("Testing changeset %d:%s "
929 ui.write(_("Testing changeset %d:%s "
930 "(%d changesets remaining, ~%d tests)\n")
930 "(%d changesets remaining, ~%d tests)\n")
931 % (rev, short(node), changesets, tests))
931 % (rev, short(node), changesets, tests))
932 state['current'] = [node]
932 state['current'] = [node]
933 hbisect.save_state(repo, state)
933 hbisect.save_state(repo, state)
934 return mayupdate(repo, node)
934 return mayupdate(repo, node)
935
935
936 @command('bookmarks|bookmark',
936 @command('bookmarks|bookmark',
937 [('f', 'force', False, _('force')),
937 [('f', 'force', False, _('force')),
938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
939 ('d', 'delete', False, _('delete a given bookmark')),
939 ('d', 'delete', False, _('delete a given bookmark')),
940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
941 ('i', 'inactive', False, _('mark a bookmark inactive')),
941 ('i', 'inactive', False, _('mark a bookmark inactive')),
942 ('l', 'list', False, _('list existing bookmarks')),
942 ('l', 'list', False, _('list existing bookmarks')),
943 ] + formatteropts,
943 ] + formatteropts,
944 _('hg bookmarks [OPTIONS]... [NAME]...'),
944 _('hg bookmarks [OPTIONS]... [NAME]...'),
945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
946 def bookmark(ui, repo, *names, **opts):
946 def bookmark(ui, repo, *names, **opts):
947 '''create a new bookmark or list existing bookmarks
947 '''create a new bookmark or list existing bookmarks
948
948
949 Bookmarks are labels on changesets to help track lines of development.
949 Bookmarks are labels on changesets to help track lines of development.
950 Bookmarks are unversioned and can be moved, renamed and deleted.
950 Bookmarks are unversioned and can be moved, renamed and deleted.
951 Deleting or moving a bookmark has no effect on the associated changesets.
951 Deleting or moving a bookmark has no effect on the associated changesets.
952
952
953 Creating or updating to a bookmark causes it to be marked as 'active'.
953 Creating or updating to a bookmark causes it to be marked as 'active'.
954 The active bookmark is indicated with a '*'.
954 The active bookmark is indicated with a '*'.
955 When a commit is made, the active bookmark will advance to the new commit.
955 When a commit is made, the active bookmark will advance to the new commit.
956 A plain :hg:`update` will also advance an active bookmark, if possible.
956 A plain :hg:`update` will also advance an active bookmark, if possible.
957 Updating away from a bookmark will cause it to be deactivated.
957 Updating away from a bookmark will cause it to be deactivated.
958
958
959 Bookmarks can be pushed and pulled between repositories (see
959 Bookmarks can be pushed and pulled between repositories (see
960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
961 diverged, a new 'divergent bookmark' of the form 'name@path' will
961 diverged, a new 'divergent bookmark' of the form 'name@path' will
962 be created. Using :hg:`merge` will resolve the divergence.
962 be created. Using :hg:`merge` will resolve the divergence.
963
963
964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
965 the active bookmark's name.
965 the active bookmark's name.
966
966
967 A bookmark named '@' has the special property that :hg:`clone` will
967 A bookmark named '@' has the special property that :hg:`clone` will
968 check it out by default if it exists.
968 check it out by default if it exists.
969
969
970 .. container:: verbose
970 .. container:: verbose
971
971
972 Template:
972 Template:
973
973
974 The following keywords are supported in addition to the common template
974 The following keywords are supported in addition to the common template
975 keywords and functions such as ``{bookmark}``. See also
975 keywords and functions such as ``{bookmark}``. See also
976 :hg:`help templates`.
976 :hg:`help templates`.
977
977
978 :active: Boolean. True if the bookmark is active.
978 :active: Boolean. True if the bookmark is active.
979
979
980 Examples:
980 Examples:
981
981
982 - create an active bookmark for a new line of development::
982 - create an active bookmark for a new line of development::
983
983
984 hg book new-feature
984 hg book new-feature
985
985
986 - create an inactive bookmark as a place marker::
986 - create an inactive bookmark as a place marker::
987
987
988 hg book -i reviewed
988 hg book -i reviewed
989
989
990 - create an inactive bookmark on another changeset::
990 - create an inactive bookmark on another changeset::
991
991
992 hg book -r .^ tested
992 hg book -r .^ tested
993
993
994 - rename bookmark turkey to dinner::
994 - rename bookmark turkey to dinner::
995
995
996 hg book -m turkey dinner
996 hg book -m turkey dinner
997
997
998 - move the '@' bookmark from another branch::
998 - move the '@' bookmark from another branch::
999
999
1000 hg book -f @
1000 hg book -f @
1001
1001
1002 - print only the active bookmark name::
1002 - print only the active bookmark name::
1003
1003
1004 hg book -ql .
1004 hg book -ql .
1005 '''
1005 '''
1006 opts = pycompat.byteskwargs(opts)
1006 opts = pycompat.byteskwargs(opts)
1007 force = opts.get('force')
1007 force = opts.get('force')
1008 rev = opts.get('rev')
1008 rev = opts.get('rev')
1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1010
1010
1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1012 if len(selactions) > 1:
1012 if len(selactions) > 1:
1013 raise error.Abort(_('--%s and --%s are incompatible')
1013 raise error.Abort(_('--%s and --%s are incompatible')
1014 % tuple(selactions[:2]))
1014 % tuple(selactions[:2]))
1015 if selactions:
1015 if selactions:
1016 action = selactions[0]
1016 action = selactions[0]
1017 elif names or rev:
1017 elif names or rev:
1018 action = 'add'
1018 action = 'add'
1019 elif inactive:
1019 elif inactive:
1020 action = 'inactive' # meaning deactivate
1020 action = 'inactive' # meaning deactivate
1021 else:
1021 else:
1022 action = 'list'
1022 action = 'list'
1023
1023
1024 if rev and action in {'delete', 'rename', 'list'}:
1024 if rev and action in {'delete', 'rename', 'list'}:
1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1026 if inactive and action in {'delete', 'list'}:
1026 if inactive and action in {'delete', 'list'}:
1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1028 if not names and action in {'add', 'delete'}:
1028 if not names and action in {'add', 'delete'}:
1029 raise error.Abort(_("bookmark name required"))
1029 raise error.Abort(_("bookmark name required"))
1030
1030
1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1033 if action == 'delete':
1033 if action == 'delete':
1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1035 bookmarks.delete(repo, tr, names)
1035 bookmarks.delete(repo, tr, names)
1036 elif action == 'rename':
1036 elif action == 'rename':
1037 if not names:
1037 if not names:
1038 raise error.Abort(_("new bookmark name required"))
1038 raise error.Abort(_("new bookmark name required"))
1039 elif len(names) > 1:
1039 elif len(names) > 1:
1040 raise error.Abort(_("only one new bookmark name allowed"))
1040 raise error.Abort(_("only one new bookmark name allowed"))
1041 oldname = repo._bookmarks.expandname(opts['rename'])
1041 oldname = repo._bookmarks.expandname(opts['rename'])
1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1043 elif action == 'add':
1043 elif action == 'add':
1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1045 elif action == 'inactive':
1045 elif action == 'inactive':
1046 if len(repo._bookmarks) == 0:
1046 if len(repo._bookmarks) == 0:
1047 ui.status(_("no bookmarks set\n"))
1047 ui.status(_("no bookmarks set\n"))
1048 elif not repo._activebookmark:
1048 elif not repo._activebookmark:
1049 ui.status(_("no active bookmark\n"))
1049 ui.status(_("no active bookmark\n"))
1050 else:
1050 else:
1051 bookmarks.deactivate(repo)
1051 bookmarks.deactivate(repo)
1052 elif action == 'list':
1052 elif action == 'list':
1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1054 with ui.formatter('bookmarks', opts) as fm:
1054 with ui.formatter('bookmarks', opts) as fm:
1055 bookmarks.printbookmarks(ui, repo, fm, names)
1055 bookmarks.printbookmarks(ui, repo, fm, names)
1056 else:
1056 else:
1057 raise error.ProgrammingError('invalid action: %s' % action)
1057 raise error.ProgrammingError('invalid action: %s' % action)
1058
1058
1059 @command('branch',
1059 @command('branch',
1060 [('f', 'force', None,
1060 [('f', 'force', None,
1061 _('set branch name even if it shadows an existing branch')),
1061 _('set branch name even if it shadows an existing branch')),
1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1064 ],
1064 ],
1065 _('[-fC] [NAME]'),
1065 _('[-fC] [NAME]'),
1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1067 def branch(ui, repo, label=None, **opts):
1067 def branch(ui, repo, label=None, **opts):
1068 """set or show the current branch name
1068 """set or show the current branch name
1069
1069
1070 .. note::
1070 .. note::
1071
1071
1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1073 light-weight bookmark instead. See :hg:`help glossary` for more
1073 light-weight bookmark instead. See :hg:`help glossary` for more
1074 information about named branches and bookmarks.
1074 information about named branches and bookmarks.
1075
1075
1076 With no argument, show the current branch name. With one argument,
1076 With no argument, show the current branch name. With one argument,
1077 set the working directory branch name (the branch will not exist
1077 set the working directory branch name (the branch will not exist
1078 in the repository until the next commit). Standard practice
1078 in the repository until the next commit). Standard practice
1079 recommends that primary development take place on the 'default'
1079 recommends that primary development take place on the 'default'
1080 branch.
1080 branch.
1081
1081
1082 Unless -f/--force is specified, branch will not let you set a
1082 Unless -f/--force is specified, branch will not let you set a
1083 branch name that already exists.
1083 branch name that already exists.
1084
1084
1085 Use -C/--clean to reset the working directory branch to that of
1085 Use -C/--clean to reset the working directory branch to that of
1086 the parent of the working directory, negating a previous branch
1086 the parent of the working directory, negating a previous branch
1087 change.
1087 change.
1088
1088
1089 Use the command :hg:`update` to switch to an existing branch. Use
1089 Use the command :hg:`update` to switch to an existing branch. Use
1090 :hg:`commit --close-branch` to mark this branch head as closed.
1090 :hg:`commit --close-branch` to mark this branch head as closed.
1091 When all heads of a branch are closed, the branch will be
1091 When all heads of a branch are closed, the branch will be
1092 considered closed.
1092 considered closed.
1093
1093
1094 Returns 0 on success.
1094 Returns 0 on success.
1095 """
1095 """
1096 opts = pycompat.byteskwargs(opts)
1096 opts = pycompat.byteskwargs(opts)
1097 revs = opts.get('rev')
1097 revs = opts.get('rev')
1098 if label:
1098 if label:
1099 label = label.strip()
1099 label = label.strip()
1100
1100
1101 if not opts.get('clean') and not label:
1101 if not opts.get('clean') and not label:
1102 if revs:
1102 if revs:
1103 raise error.Abort(_("no branch name specified for the revisions"))
1103 raise error.Abort(_("no branch name specified for the revisions"))
1104 ui.write("%s\n" % repo.dirstate.branch())
1104 ui.write("%s\n" % repo.dirstate.branch())
1105 return
1105 return
1106
1106
1107 with repo.wlock():
1107 with repo.wlock():
1108 if opts.get('clean'):
1108 if opts.get('clean'):
1109 label = repo['.'].branch()
1109 label = repo['.'].branch()
1110 repo.dirstate.setbranch(label)
1110 repo.dirstate.setbranch(label)
1111 ui.status(_('reset working directory to branch %s\n') % label)
1111 ui.status(_('reset working directory to branch %s\n') % label)
1112 elif label:
1112 elif label:
1113
1113
1114 scmutil.checknewlabel(repo, label, 'branch')
1114 scmutil.checknewlabel(repo, label, 'branch')
1115 if revs:
1115 if revs:
1116 return cmdutil.changebranch(ui, repo, revs, label)
1116 return cmdutil.changebranch(ui, repo, revs, label)
1117
1117
1118 if not opts.get('force') and label in repo.branchmap():
1118 if not opts.get('force') and label in repo.branchmap():
1119 if label not in [p.branch() for p in repo[None].parents()]:
1119 if label not in [p.branch() for p in repo[None].parents()]:
1120 raise error.Abort(_('a branch of the same name already'
1120 raise error.Abort(_('a branch of the same name already'
1121 ' exists'),
1121 ' exists'),
1122 # i18n: "it" refers to an existing branch
1122 # i18n: "it" refers to an existing branch
1123 hint=_("use 'hg update' to switch to it"))
1123 hint=_("use 'hg update' to switch to it"))
1124
1124
1125 repo.dirstate.setbranch(label)
1125 repo.dirstate.setbranch(label)
1126 ui.status(_('marked working directory as branch %s\n') % label)
1126 ui.status(_('marked working directory as branch %s\n') % label)
1127
1127
1128 # find any open named branches aside from default
1128 # find any open named branches aside from default
1129 for n, h, t, c in repo.branchmap().iterbranches():
1129 for n, h, t, c in repo.branchmap().iterbranches():
1130 if n != "default" and not c:
1130 if n != "default" and not c:
1131 return 0
1131 return 0
1132 ui.status(_('(branches are permanent and global, '
1132 ui.status(_('(branches are permanent and global, '
1133 'did you want a bookmark?)\n'))
1133 'did you want a bookmark?)\n'))
1134
1134
1135 @command('branches',
1135 @command('branches',
1136 [('a', 'active', False,
1136 [('a', 'active', False,
1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1138 ('c', 'closed', False, _('show normal and closed branches')),
1138 ('c', 'closed', False, _('show normal and closed branches')),
1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1140 ] + formatteropts,
1140 ] + formatteropts,
1141 _('[-c]'),
1141 _('[-c]'),
1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1143 intents={INTENT_READONLY})
1143 intents={INTENT_READONLY})
1144 def branches(ui, repo, active=False, closed=False, **opts):
1144 def branches(ui, repo, active=False, closed=False, **opts):
1145 """list repository named branches
1145 """list repository named branches
1146
1146
1147 List the repository's named branches, indicating which ones are
1147 List the repository's named branches, indicating which ones are
1148 inactive. If -c/--closed is specified, also list branches which have
1148 inactive. If -c/--closed is specified, also list branches which have
1149 been marked closed (see :hg:`commit --close-branch`).
1149 been marked closed (see :hg:`commit --close-branch`).
1150
1150
1151 Use the command :hg:`update` to switch to an existing branch.
1151 Use the command :hg:`update` to switch to an existing branch.
1152
1152
1153 .. container:: verbose
1153 .. container:: verbose
1154
1154
1155 Template:
1155 Template:
1156
1156
1157 The following keywords are supported in addition to the common template
1157 The following keywords are supported in addition to the common template
1158 keywords and functions such as ``{branch}``. See also
1158 keywords and functions such as ``{branch}``. See also
1159 :hg:`help templates`.
1159 :hg:`help templates`.
1160
1160
1161 :active: Boolean. True if the branch is active.
1161 :active: Boolean. True if the branch is active.
1162 :closed: Boolean. True if the branch is closed.
1162 :closed: Boolean. True if the branch is closed.
1163 :current: Boolean. True if it is the current branch.
1163 :current: Boolean. True if it is the current branch.
1164
1164
1165 Returns 0.
1165 Returns 0.
1166 """
1166 """
1167
1167
1168 opts = pycompat.byteskwargs(opts)
1168 opts = pycompat.byteskwargs(opts)
1169 revs = opts.get('rev')
1169 revs = opts.get('rev')
1170 selectedbranches = None
1170 selectedbranches = None
1171 if revs:
1171 if revs:
1172 revs = scmutil.revrange(repo, revs)
1172 revs = scmutil.revrange(repo, revs)
1173 getbi = repo.revbranchcache().branchinfo
1173 getbi = repo.revbranchcache().branchinfo
1174 selectedbranches = {getbi(r)[0] for r in revs}
1174 selectedbranches = {getbi(r)[0] for r in revs}
1175
1175
1176 ui.pager('branches')
1176 ui.pager('branches')
1177 fm = ui.formatter('branches', opts)
1177 fm = ui.formatter('branches', opts)
1178 hexfunc = fm.hexfunc
1178 hexfunc = fm.hexfunc
1179
1179
1180 allheads = set(repo.heads())
1180 allheads = set(repo.heads())
1181 branches = []
1181 branches = []
1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1183 if selectedbranches is not None and tag not in selectedbranches:
1183 if selectedbranches is not None and tag not in selectedbranches:
1184 continue
1184 continue
1185 isactive = False
1185 isactive = False
1186 if not isclosed:
1186 if not isclosed:
1187 openheads = set(repo.branchmap().iteropen(heads))
1187 openheads = set(repo.branchmap().iteropen(heads))
1188 isactive = bool(openheads & allheads)
1188 isactive = bool(openheads & allheads)
1189 branches.append((tag, repo[tip], isactive, not isclosed))
1189 branches.append((tag, repo[tip], isactive, not isclosed))
1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1191 reverse=True)
1191 reverse=True)
1192
1192
1193 for tag, ctx, isactive, isopen in branches:
1193 for tag, ctx, isactive, isopen in branches:
1194 if active and not isactive:
1194 if active and not isactive:
1195 continue
1195 continue
1196 if isactive:
1196 if isactive:
1197 label = 'branches.active'
1197 label = 'branches.active'
1198 notice = ''
1198 notice = ''
1199 elif not isopen:
1199 elif not isopen:
1200 if not closed:
1200 if not closed:
1201 continue
1201 continue
1202 label = 'branches.closed'
1202 label = 'branches.closed'
1203 notice = _(' (closed)')
1203 notice = _(' (closed)')
1204 else:
1204 else:
1205 label = 'branches.inactive'
1205 label = 'branches.inactive'
1206 notice = _(' (inactive)')
1206 notice = _(' (inactive)')
1207 current = (tag == repo.dirstate.branch())
1207 current = (tag == repo.dirstate.branch())
1208 if current:
1208 if current:
1209 label = 'branches.current'
1209 label = 'branches.current'
1210
1210
1211 fm.startitem()
1211 fm.startitem()
1212 fm.write('branch', '%s', tag, label=label)
1212 fm.write('branch', '%s', tag, label=label)
1213 rev = ctx.rev()
1213 rev = ctx.rev()
1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1215 fmt = ' ' * padsize + ' %d:%s'
1215 fmt = ' ' * padsize + ' %d:%s'
1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1217 label='log.changeset changeset.%s' % ctx.phasestr())
1217 label='log.changeset changeset.%s' % ctx.phasestr())
1218 fm.context(ctx=ctx)
1218 fm.context(ctx=ctx)
1219 fm.data(active=isactive, closed=not isopen, current=current)
1219 fm.data(active=isactive, closed=not isopen, current=current)
1220 if not ui.quiet:
1220 if not ui.quiet:
1221 fm.plain(notice)
1221 fm.plain(notice)
1222 fm.plain('\n')
1222 fm.plain('\n')
1223 fm.end()
1223 fm.end()
1224
1224
1225 @command('bundle',
1225 @command('bundle',
1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1228 _('REV')),
1228 _('REV')),
1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1230 _('BRANCH')),
1230 _('BRANCH')),
1231 ('', 'base', [],
1231 ('', 'base', [],
1232 _('a base changeset assumed to be available at the destination'),
1232 _('a base changeset assumed to be available at the destination'),
1233 _('REV')),
1233 _('REV')),
1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1236 ] + remoteopts,
1236 ] + remoteopts,
1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1239 def bundle(ui, repo, fname, dest=None, **opts):
1239 def bundle(ui, repo, fname, dest=None, **opts):
1240 """create a bundle file
1240 """create a bundle file
1241
1241
1242 Generate a bundle file containing data to be transferred to another
1242 Generate a bundle file containing data to be transferred to another
1243 repository.
1243 repository.
1244
1244
1245 To create a bundle containing all changesets, use -a/--all
1245 To create a bundle containing all changesets, use -a/--all
1246 (or --base null). Otherwise, hg assumes the destination will have
1246 (or --base null). Otherwise, hg assumes the destination will have
1247 all the nodes you specify with --base parameters. Otherwise, hg
1247 all the nodes you specify with --base parameters. Otherwise, hg
1248 will assume the repository has all the nodes in destination, or
1248 will assume the repository has all the nodes in destination, or
1249 default-push/default if no destination is specified, where destination
1249 default-push/default if no destination is specified, where destination
1250 is the repository you provide through DEST option.
1250 is the repository you provide through DEST option.
1251
1251
1252 You can change bundle format with the -t/--type option. See
1252 You can change bundle format with the -t/--type option. See
1253 :hg:`help bundlespec` for documentation on this format. By default,
1253 :hg:`help bundlespec` for documentation on this format. By default,
1254 the most appropriate format is used and compression defaults to
1254 the most appropriate format is used and compression defaults to
1255 bzip2.
1255 bzip2.
1256
1256
1257 The bundle file can then be transferred using conventional means
1257 The bundle file can then be transferred using conventional means
1258 and applied to another repository with the unbundle or pull
1258 and applied to another repository with the unbundle or pull
1259 command. This is useful when direct push and pull are not
1259 command. This is useful when direct push and pull are not
1260 available or when exporting an entire repository is undesirable.
1260 available or when exporting an entire repository is undesirable.
1261
1261
1262 Applying bundles preserves all changeset contents including
1262 Applying bundles preserves all changeset contents including
1263 permissions, copy/rename information, and revision history.
1263 permissions, copy/rename information, and revision history.
1264
1264
1265 Returns 0 on success, 1 if no changes found.
1265 Returns 0 on success, 1 if no changes found.
1266 """
1266 """
1267 opts = pycompat.byteskwargs(opts)
1267 opts = pycompat.byteskwargs(opts)
1268 revs = None
1268 revs = None
1269 if 'rev' in opts:
1269 if 'rev' in opts:
1270 revstrings = opts['rev']
1270 revstrings = opts['rev']
1271 revs = scmutil.revrange(repo, revstrings)
1271 revs = scmutil.revrange(repo, revstrings)
1272 if revstrings and not revs:
1272 if revstrings and not revs:
1273 raise error.Abort(_('no commits to bundle'))
1273 raise error.Abort(_('no commits to bundle'))
1274
1274
1275 bundletype = opts.get('type', 'bzip2').lower()
1275 bundletype = opts.get('type', 'bzip2').lower()
1276 try:
1276 try:
1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1278 except error.UnsupportedBundleSpecification as e:
1278 except error.UnsupportedBundleSpecification as e:
1279 raise error.Abort(pycompat.bytestr(e),
1279 raise error.Abort(pycompat.bytestr(e),
1280 hint=_("see 'hg help bundlespec' for supported "
1280 hint=_("see 'hg help bundlespec' for supported "
1281 "values for --type"))
1281 "values for --type"))
1282 cgversion = bundlespec.contentopts["cg.version"]
1282 cgversion = bundlespec.contentopts["cg.version"]
1283
1283
1284 # Packed bundles are a pseudo bundle format for now.
1284 # Packed bundles are a pseudo bundle format for now.
1285 if cgversion == 's1':
1285 if cgversion == 's1':
1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1288
1288
1289 if opts.get('all'):
1289 if opts.get('all'):
1290 if dest:
1290 if dest:
1291 raise error.Abort(_("--all is incompatible with specifying "
1291 raise error.Abort(_("--all is incompatible with specifying "
1292 "a destination"))
1292 "a destination"))
1293 if opts.get('base'):
1293 if opts.get('base'):
1294 ui.warn(_("ignoring --base because --all was specified\n"))
1294 ui.warn(_("ignoring --base because --all was specified\n"))
1295 base = [nullrev]
1295 base = [nullrev]
1296 else:
1296 else:
1297 base = scmutil.revrange(repo, opts.get('base'))
1297 base = scmutil.revrange(repo, opts.get('base'))
1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1299 raise error.Abort(_("repository does not support bundle version %s") %
1299 raise error.Abort(_("repository does not support bundle version %s") %
1300 cgversion)
1300 cgversion)
1301
1301
1302 if base:
1302 if base:
1303 if dest:
1303 if dest:
1304 raise error.Abort(_("--base is incompatible with specifying "
1304 raise error.Abort(_("--base is incompatible with specifying "
1305 "a destination"))
1305 "a destination"))
1306 common = [repo[rev].node() for rev in base]
1306 common = [repo[rev].node() for rev in base]
1307 heads = [repo[r].node() for r in revs] if revs else None
1307 heads = [repo[r].node() for r in revs] if revs else None
1308 outgoing = discovery.outgoing(repo, common, heads)
1308 outgoing = discovery.outgoing(repo, common, heads)
1309 else:
1309 else:
1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1312 other = hg.peer(repo, opts, dest)
1312 other = hg.peer(repo, opts, dest)
1313 revs = [repo[r].hex() for r in revs]
1313 revs = [repo[r].hex() for r in revs]
1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1316 outgoing = discovery.findcommonoutgoing(repo, other,
1316 outgoing = discovery.findcommonoutgoing(repo, other,
1317 onlyheads=heads,
1317 onlyheads=heads,
1318 force=opts.get('force'),
1318 force=opts.get('force'),
1319 portable=True)
1319 portable=True)
1320
1320
1321 if not outgoing.missing:
1321 if not outgoing.missing:
1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1323 return 1
1323 return 1
1324
1324
1325 if cgversion == '01': #bundle1
1325 if cgversion == '01': #bundle1
1326 bversion = 'HG10' + bundlespec.wirecompression
1326 bversion = 'HG10' + bundlespec.wirecompression
1327 bcompression = None
1327 bcompression = None
1328 elif cgversion in ('02', '03'):
1328 elif cgversion in ('02', '03'):
1329 bversion = 'HG20'
1329 bversion = 'HG20'
1330 bcompression = bundlespec.wirecompression
1330 bcompression = bundlespec.wirecompression
1331 else:
1331 else:
1332 raise error.ProgrammingError(
1332 raise error.ProgrammingError(
1333 'bundle: unexpected changegroup version %s' % cgversion)
1333 'bundle: unexpected changegroup version %s' % cgversion)
1334
1334
1335 # TODO compression options should be derived from bundlespec parsing.
1335 # TODO compression options should be derived from bundlespec parsing.
1336 # This is a temporary hack to allow adjusting bundle compression
1336 # This is a temporary hack to allow adjusting bundle compression
1337 # level without a) formalizing the bundlespec changes to declare it
1337 # level without a) formalizing the bundlespec changes to declare it
1338 # b) introducing a command flag.
1338 # b) introducing a command flag.
1339 compopts = {}
1339 compopts = {}
1340 complevel = ui.configint('experimental',
1340 complevel = ui.configint('experimental',
1341 'bundlecomplevel.' + bundlespec.compression)
1341 'bundlecomplevel.' + bundlespec.compression)
1342 if complevel is None:
1342 if complevel is None:
1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1344 if complevel is not None:
1344 if complevel is not None:
1345 compopts['level'] = complevel
1345 compopts['level'] = complevel
1346
1346
1347 # Allow overriding the bundling of obsmarker in phases through
1347 # Allow overriding the bundling of obsmarker in phases through
1348 # configuration while we don't have a bundle version that include them
1348 # configuration while we don't have a bundle version that include them
1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1350 bundlespec.contentopts['obsolescence'] = True
1350 bundlespec.contentopts['obsolescence'] = True
1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1352 bundlespec.contentopts['phases'] = True
1352 bundlespec.contentopts['phases'] = True
1353
1353
1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1355 bundlespec.contentopts, compression=bcompression,
1355 bundlespec.contentopts, compression=bcompression,
1356 compopts=compopts)
1356 compopts=compopts)
1357
1357
1358 @command('cat',
1358 @command('cat',
1359 [('o', 'output', '',
1359 [('o', 'output', '',
1360 _('print output to file with formatted name'), _('FORMAT')),
1360 _('print output to file with formatted name'), _('FORMAT')),
1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1362 ('', 'decode', None, _('apply any matching decode filter')),
1362 ('', 'decode', None, _('apply any matching decode filter')),
1363 ] + walkopts + formatteropts,
1363 ] + walkopts + formatteropts,
1364 _('[OPTION]... FILE...'),
1364 _('[OPTION]... FILE...'),
1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1366 inferrepo=True,
1366 inferrepo=True,
1367 intents={INTENT_READONLY})
1367 intents={INTENT_READONLY})
1368 def cat(ui, repo, file1, *pats, **opts):
1368 def cat(ui, repo, file1, *pats, **opts):
1369 """output the current or given revision of files
1369 """output the current or given revision of files
1370
1370
1371 Print the specified files as they were at the given revision. If
1371 Print the specified files as they were at the given revision. If
1372 no revision is given, the parent of the working directory is used.
1372 no revision is given, the parent of the working directory is used.
1373
1373
1374 Output may be to a file, in which case the name of the file is
1374 Output may be to a file, in which case the name of the file is
1375 given using a template string. See :hg:`help templates`. In addition
1375 given using a template string. See :hg:`help templates`. In addition
1376 to the common template keywords, the following formatting rules are
1376 to the common template keywords, the following formatting rules are
1377 supported:
1377 supported:
1378
1378
1379 :``%%``: literal "%" character
1379 :``%%``: literal "%" character
1380 :``%s``: basename of file being printed
1380 :``%s``: basename of file being printed
1381 :``%d``: dirname of file being printed, or '.' if in repository root
1381 :``%d``: dirname of file being printed, or '.' if in repository root
1382 :``%p``: root-relative path name of file being printed
1382 :``%p``: root-relative path name of file being printed
1383 :``%H``: changeset hash (40 hexadecimal digits)
1383 :``%H``: changeset hash (40 hexadecimal digits)
1384 :``%R``: changeset revision number
1384 :``%R``: changeset revision number
1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1386 :``%r``: zero-padded changeset revision number
1386 :``%r``: zero-padded changeset revision number
1387 :``%b``: basename of the exporting repository
1387 :``%b``: basename of the exporting repository
1388 :``\\``: literal "\\" character
1388 :``\\``: literal "\\" character
1389
1389
1390 .. container:: verbose
1390 .. container:: verbose
1391
1391
1392 Template:
1392 Template:
1393
1393
1394 The following keywords are supported in addition to the common template
1394 The following keywords are supported in addition to the common template
1395 keywords and functions. See also :hg:`help templates`.
1395 keywords and functions. See also :hg:`help templates`.
1396
1396
1397 :data: String. File content.
1397 :data: String. File content.
1398 :path: String. Repository-absolute path of the file.
1398 :path: String. Repository-absolute path of the file.
1399
1399
1400 Returns 0 on success.
1400 Returns 0 on success.
1401 """
1401 """
1402 opts = pycompat.byteskwargs(opts)
1402 opts = pycompat.byteskwargs(opts)
1403 rev = opts.get('rev')
1403 rev = opts.get('rev')
1404 if rev:
1404 if rev:
1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1406 ctx = scmutil.revsingle(repo, rev)
1406 ctx = scmutil.revsingle(repo, rev)
1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1408 fntemplate = opts.pop('output', '')
1408 fntemplate = opts.pop('output', '')
1409 if cmdutil.isstdiofilename(fntemplate):
1409 if cmdutil.isstdiofilename(fntemplate):
1410 fntemplate = ''
1410 fntemplate = ''
1411
1411
1412 if fntemplate:
1412 if fntemplate:
1413 fm = formatter.nullformatter(ui, 'cat', opts)
1413 fm = formatter.nullformatter(ui, 'cat', opts)
1414 else:
1414 else:
1415 ui.pager('cat')
1415 ui.pager('cat')
1416 fm = ui.formatter('cat', opts)
1416 fm = ui.formatter('cat', opts)
1417 with fm:
1417 with fm:
1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1419 **pycompat.strkwargs(opts))
1419 **pycompat.strkwargs(opts))
1420
1420
1421 @command('clone',
1421 @command('clone',
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1423 'directory (only a repository)')),
1423 'directory (only a repository)')),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1425 _('REV')),
1425 _('REV')),
1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1427 ' and its ancestors'), _('REV')),
1427 ' and its ancestors'), _('REV')),
1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1429 ' changesets and their ancestors'), _('BRANCH')),
1429 ' changesets and their ancestors'), _('BRANCH')),
1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1431 ('', 'uncompressed', None,
1431 ('', 'uncompressed', None,
1432 _('an alias to --stream (DEPRECATED)')),
1432 _('an alias to --stream (DEPRECATED)')),
1433 ('', 'stream', None,
1433 ('', 'stream', None,
1434 _('clone with minimal data processing')),
1434 _('clone with minimal data processing')),
1435 ] + remoteopts,
1435 ] + remoteopts,
1436 _('[OPTION]... SOURCE [DEST]'),
1436 _('[OPTION]... SOURCE [DEST]'),
1437 helpcategory=command.CATEGORY_REPO_CREATION,
1437 helpcategory=command.CATEGORY_REPO_CREATION,
1438 helpbasic=True, norepo=True)
1438 helpbasic=True, norepo=True)
1439 def clone(ui, source, dest=None, **opts):
1439 def clone(ui, source, dest=None, **opts):
1440 """make a copy of an existing repository
1440 """make a copy of an existing repository
1441
1441
1442 Create a copy of an existing repository in a new directory.
1442 Create a copy of an existing repository in a new directory.
1443
1443
1444 If no destination directory name is specified, it defaults to the
1444 If no destination directory name is specified, it defaults to the
1445 basename of the source.
1445 basename of the source.
1446
1446
1447 The location of the source is added to the new repository's
1447 The location of the source is added to the new repository's
1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1449
1449
1450 Only local paths and ``ssh://`` URLs are supported as
1450 Only local paths and ``ssh://`` URLs are supported as
1451 destinations. For ``ssh://`` destinations, no working directory or
1451 destinations. For ``ssh://`` destinations, no working directory or
1452 ``.hg/hgrc`` will be created on the remote side.
1452 ``.hg/hgrc`` will be created on the remote side.
1453
1453
1454 If the source repository has a bookmark called '@' set, that
1454 If the source repository has a bookmark called '@' set, that
1455 revision will be checked out in the new repository by default.
1455 revision will be checked out in the new repository by default.
1456
1456
1457 To check out a particular version, use -u/--update, or
1457 To check out a particular version, use -u/--update, or
1458 -U/--noupdate to create a clone with no working directory.
1458 -U/--noupdate to create a clone with no working directory.
1459
1459
1460 To pull only a subset of changesets, specify one or more revisions
1460 To pull only a subset of changesets, specify one or more revisions
1461 identifiers with -r/--rev or branches with -b/--branch. The
1461 identifiers with -r/--rev or branches with -b/--branch. The
1462 resulting clone will contain only the specified changesets and
1462 resulting clone will contain only the specified changesets and
1463 their ancestors. These options (or 'clone src#rev dest') imply
1463 their ancestors. These options (or 'clone src#rev dest') imply
1464 --pull, even for local source repositories.
1464 --pull, even for local source repositories.
1465
1465
1466 In normal clone mode, the remote normalizes repository data into a common
1466 In normal clone mode, the remote normalizes repository data into a common
1467 exchange format and the receiving end translates this data into its local
1467 exchange format and the receiving end translates this data into its local
1468 storage format. --stream activates a different clone mode that essentially
1468 storage format. --stream activates a different clone mode that essentially
1469 copies repository files from the remote with minimal data processing. This
1469 copies repository files from the remote with minimal data processing. This
1470 significantly reduces the CPU cost of a clone both remotely and locally.
1470 significantly reduces the CPU cost of a clone both remotely and locally.
1471 However, it often increases the transferred data size by 30-40%. This can
1471 However, it often increases the transferred data size by 30-40%. This can
1472 result in substantially faster clones where I/O throughput is plentiful,
1472 result in substantially faster clones where I/O throughput is plentiful,
1473 especially for larger repositories. A side-effect of --stream clones is
1473 especially for larger repositories. A side-effect of --stream clones is
1474 that storage settings and requirements on the remote are applied locally:
1474 that storage settings and requirements on the remote are applied locally:
1475 a modern client may inherit legacy or inefficient storage used by the
1475 a modern client may inherit legacy or inefficient storage used by the
1476 remote or a legacy Mercurial client may not be able to clone from a
1476 remote or a legacy Mercurial client may not be able to clone from a
1477 modern Mercurial remote.
1477 modern Mercurial remote.
1478
1478
1479 .. note::
1479 .. note::
1480
1480
1481 Specifying a tag will include the tagged changeset but not the
1481 Specifying a tag will include the tagged changeset but not the
1482 changeset containing the tag.
1482 changeset containing the tag.
1483
1483
1484 .. container:: verbose
1484 .. container:: verbose
1485
1485
1486 For efficiency, hardlinks are used for cloning whenever the
1486 For efficiency, hardlinks are used for cloning whenever the
1487 source and destination are on the same filesystem (note this
1487 source and destination are on the same filesystem (note this
1488 applies only to the repository data, not to the working
1488 applies only to the repository data, not to the working
1489 directory). Some filesystems, such as AFS, implement hardlinking
1489 directory). Some filesystems, such as AFS, implement hardlinking
1490 incorrectly, but do not report errors. In these cases, use the
1490 incorrectly, but do not report errors. In these cases, use the
1491 --pull option to avoid hardlinking.
1491 --pull option to avoid hardlinking.
1492
1492
1493 Mercurial will update the working directory to the first applicable
1493 Mercurial will update the working directory to the first applicable
1494 revision from this list:
1494 revision from this list:
1495
1495
1496 a) null if -U or the source repository has no changesets
1496 a) null if -U or the source repository has no changesets
1497 b) if -u . and the source repository is local, the first parent of
1497 b) if -u . and the source repository is local, the first parent of
1498 the source repository's working directory
1498 the source repository's working directory
1499 c) the changeset specified with -u (if a branch name, this means the
1499 c) the changeset specified with -u (if a branch name, this means the
1500 latest head of that branch)
1500 latest head of that branch)
1501 d) the changeset specified with -r
1501 d) the changeset specified with -r
1502 e) the tipmost head specified with -b
1502 e) the tipmost head specified with -b
1503 f) the tipmost head specified with the url#branch source syntax
1503 f) the tipmost head specified with the url#branch source syntax
1504 g) the revision marked with the '@' bookmark, if present
1504 g) the revision marked with the '@' bookmark, if present
1505 h) the tipmost head of the default branch
1505 h) the tipmost head of the default branch
1506 i) tip
1506 i) tip
1507
1507
1508 When cloning from servers that support it, Mercurial may fetch
1508 When cloning from servers that support it, Mercurial may fetch
1509 pre-generated data from a server-advertised URL or inline from the
1509 pre-generated data from a server-advertised URL or inline from the
1510 same stream. When this is done, hooks operating on incoming changesets
1510 same stream. When this is done, hooks operating on incoming changesets
1511 and changegroups may fire more than once, once for each pre-generated
1511 and changegroups may fire more than once, once for each pre-generated
1512 bundle and as well as for any additional remaining data. In addition,
1512 bundle and as well as for any additional remaining data. In addition,
1513 if an error occurs, the repository may be rolled back to a partial
1513 if an error occurs, the repository may be rolled back to a partial
1514 clone. This behavior may change in future releases.
1514 clone. This behavior may change in future releases.
1515 See :hg:`help -e clonebundles` for more.
1515 See :hg:`help -e clonebundles` for more.
1516
1516
1517 Examples:
1517 Examples:
1518
1518
1519 - clone a remote repository to a new directory named hg/::
1519 - clone a remote repository to a new directory named hg/::
1520
1520
1521 hg clone https://www.mercurial-scm.org/repo/hg/
1521 hg clone https://www.mercurial-scm.org/repo/hg/
1522
1522
1523 - create a lightweight local clone::
1523 - create a lightweight local clone::
1524
1524
1525 hg clone project/ project-feature/
1525 hg clone project/ project-feature/
1526
1526
1527 - clone from an absolute path on an ssh server (note double-slash)::
1527 - clone from an absolute path on an ssh server (note double-slash)::
1528
1528
1529 hg clone ssh://user@server//home/projects/alpha/
1529 hg clone ssh://user@server//home/projects/alpha/
1530
1530
1531 - do a streaming clone while checking out a specified version::
1531 - do a streaming clone while checking out a specified version::
1532
1532
1533 hg clone --stream http://server/repo -u 1.5
1533 hg clone --stream http://server/repo -u 1.5
1534
1534
1535 - create a repository without changesets after a particular revision::
1535 - create a repository without changesets after a particular revision::
1536
1536
1537 hg clone -r 04e544 experimental/ good/
1537 hg clone -r 04e544 experimental/ good/
1538
1538
1539 - clone (and track) a particular named branch::
1539 - clone (and track) a particular named branch::
1540
1540
1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1542
1542
1543 See :hg:`help urls` for details on specifying URLs.
1543 See :hg:`help urls` for details on specifying URLs.
1544
1544
1545 Returns 0 on success.
1545 Returns 0 on success.
1546 """
1546 """
1547 opts = pycompat.byteskwargs(opts)
1547 opts = pycompat.byteskwargs(opts)
1548 if opts.get('noupdate') and opts.get('updaterev'):
1548 if opts.get('noupdate') and opts.get('updaterev'):
1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1550
1550
1551 # --include/--exclude can come from narrow or sparse.
1551 # --include/--exclude can come from narrow or sparse.
1552 includepats, excludepats = None, None
1552 includepats, excludepats = None, None
1553
1553
1554 # hg.clone() differentiates between None and an empty set. So make sure
1554 # hg.clone() differentiates between None and an empty set. So make sure
1555 # patterns are sets if narrow is requested without patterns.
1555 # patterns are sets if narrow is requested without patterns.
1556 if opts.get('narrow'):
1556 if opts.get('narrow'):
1557 includepats = set()
1557 includepats = set()
1558 excludepats = set()
1558 excludepats = set()
1559
1559
1560 if opts.get('include'):
1560 if opts.get('include'):
1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1562 if opts.get('exclude'):
1562 if opts.get('exclude'):
1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1564
1564
1565 r = hg.clone(ui, opts, source, dest,
1565 r = hg.clone(ui, opts, source, dest,
1566 pull=opts.get('pull'),
1566 pull=opts.get('pull'),
1567 stream=opts.get('stream') or opts.get('uncompressed'),
1567 stream=opts.get('stream') or opts.get('uncompressed'),
1568 revs=opts.get('rev'),
1568 revs=opts.get('rev'),
1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1570 branch=opts.get('branch'),
1570 branch=opts.get('branch'),
1571 shareopts=opts.get('shareopts'),
1571 shareopts=opts.get('shareopts'),
1572 storeincludepats=includepats,
1572 storeincludepats=includepats,
1573 storeexcludepats=excludepats,
1573 storeexcludepats=excludepats,
1574 depth=opts.get('depth') or None)
1574 depth=opts.get('depth') or None)
1575
1575
1576 return r is None
1576 return r is None
1577
1577
1578 @command('commit|ci',
1578 @command('commit|ci',
1579 [('A', 'addremove', None,
1579 [('A', 'addremove', None,
1580 _('mark new/missing files as added/removed before committing')),
1580 _('mark new/missing files as added/removed before committing')),
1581 ('', 'close-branch', None,
1581 ('', 'close-branch', None,
1582 _('mark a branch head as closed')),
1582 _('mark a branch head as closed')),
1583 ('', 'amend', None, _('amend the parent of the working directory')),
1583 ('', 'amend', None, _('amend the parent of the working directory')),
1584 ('s', 'secret', None, _('use the secret phase for committing')),
1584 ('s', 'secret', None, _('use the secret phase for committing')),
1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1586 ('', 'force-close-branch', None,
1586 ('', 'force-close-branch', None,
1587 _('forcibly close branch from a non-head changeset (ADVANCED)')),
1587 _('forcibly close branch from a non-head changeset (ADVANCED)')),
1588 ('i', 'interactive', None, _('use interactive mode')),
1588 ('i', 'interactive', None, _('use interactive mode')),
1589 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1589 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1590 _('[OPTION]... [FILE]...'),
1590 _('[OPTION]... [FILE]...'),
1591 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1591 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1592 inferrepo=True)
1592 inferrepo=True)
1593 def commit(ui, repo, *pats, **opts):
1593 def commit(ui, repo, *pats, **opts):
1594 """commit the specified files or all outstanding changes
1594 """commit the specified files or all outstanding changes
1595
1595
1596 Commit changes to the given files into the repository. Unlike a
1596 Commit changes to the given files into the repository. Unlike a
1597 centralized SCM, this operation is a local operation. See
1597 centralized SCM, this operation is a local operation. See
1598 :hg:`push` for a way to actively distribute your changes.
1598 :hg:`push` for a way to actively distribute your changes.
1599
1599
1600 If a list of files is omitted, all changes reported by :hg:`status`
1600 If a list of files is omitted, all changes reported by :hg:`status`
1601 will be committed.
1601 will be committed.
1602
1602
1603 If you are committing the result of a merge, do not provide any
1603 If you are committing the result of a merge, do not provide any
1604 filenames or -I/-X filters.
1604 filenames or -I/-X filters.
1605
1605
1606 If no commit message is specified, Mercurial starts your
1606 If no commit message is specified, Mercurial starts your
1607 configured editor where you can enter a message. In case your
1607 configured editor where you can enter a message. In case your
1608 commit fails, you will find a backup of your message in
1608 commit fails, you will find a backup of your message in
1609 ``.hg/last-message.txt``.
1609 ``.hg/last-message.txt``.
1610
1610
1611 The --close-branch flag can be used to mark the current branch
1611 The --close-branch flag can be used to mark the current branch
1612 head closed. When all heads of a branch are closed, the branch
1612 head closed. When all heads of a branch are closed, the branch
1613 will be considered closed and no longer listed.
1613 will be considered closed and no longer listed.
1614
1614
1615 The --amend flag can be used to amend the parent of the
1615 The --amend flag can be used to amend the parent of the
1616 working directory with a new commit that contains the changes
1616 working directory with a new commit that contains the changes
1617 in the parent in addition to those currently reported by :hg:`status`,
1617 in the parent in addition to those currently reported by :hg:`status`,
1618 if there are any. The old commit is stored in a backup bundle in
1618 if there are any. The old commit is stored in a backup bundle in
1619 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1619 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1620 on how to restore it).
1620 on how to restore it).
1621
1621
1622 Message, user and date are taken from the amended commit unless
1622 Message, user and date are taken from the amended commit unless
1623 specified. When a message isn't specified on the command line,
1623 specified. When a message isn't specified on the command line,
1624 the editor will open with the message of the amended commit.
1624 the editor will open with the message of the amended commit.
1625
1625
1626 It is not possible to amend public changesets (see :hg:`help phases`)
1626 It is not possible to amend public changesets (see :hg:`help phases`)
1627 or changesets that have children.
1627 or changesets that have children.
1628
1628
1629 See :hg:`help dates` for a list of formats valid for -d/--date.
1629 See :hg:`help dates` for a list of formats valid for -d/--date.
1630
1630
1631 Returns 0 on success, 1 if nothing changed.
1631 Returns 0 on success, 1 if nothing changed.
1632
1632
1633 .. container:: verbose
1633 .. container:: verbose
1634
1634
1635 Examples:
1635 Examples:
1636
1636
1637 - commit all files ending in .py::
1637 - commit all files ending in .py::
1638
1638
1639 hg commit --include "set:**.py"
1639 hg commit --include "set:**.py"
1640
1640
1641 - commit all non-binary files::
1641 - commit all non-binary files::
1642
1642
1643 hg commit --exclude "set:binary()"
1643 hg commit --exclude "set:binary()"
1644
1644
1645 - amend the current commit and set the date to now::
1645 - amend the current commit and set the date to now::
1646
1646
1647 hg commit --amend --date now
1647 hg commit --amend --date now
1648 """
1648 """
1649 with repo.wlock(), repo.lock():
1649 with repo.wlock(), repo.lock():
1650 return _docommit(ui, repo, *pats, **opts)
1650 return _docommit(ui, repo, *pats, **opts)
1651
1651
1652 def _docommit(ui, repo, *pats, **opts):
1652 def _docommit(ui, repo, *pats, **opts):
1653 if opts.get(r'interactive'):
1653 if opts.get(r'interactive'):
1654 opts.pop(r'interactive')
1654 opts.pop(r'interactive')
1655 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1655 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1656 cmdutil.recordfilter, *pats,
1656 cmdutil.recordfilter, *pats,
1657 **opts)
1657 **opts)
1658 # ret can be 0 (no changes to record) or the value returned by
1658 # ret can be 0 (no changes to record) or the value returned by
1659 # commit(), 1 if nothing changed or None on success.
1659 # commit(), 1 if nothing changed or None on success.
1660 return 1 if ret == 0 else ret
1660 return 1 if ret == 0 else ret
1661
1661
1662 opts = pycompat.byteskwargs(opts)
1662 opts = pycompat.byteskwargs(opts)
1663 if opts.get('subrepos'):
1663 if opts.get('subrepos'):
1664 if opts.get('amend'):
1664 if opts.get('amend'):
1665 raise error.Abort(_('cannot amend with --subrepos'))
1665 raise error.Abort(_('cannot amend with --subrepos'))
1666 # Let --subrepos on the command line override config setting.
1666 # Let --subrepos on the command line override config setting.
1667 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1667 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1668
1668
1669 cmdutil.checkunfinished(repo, commit=True)
1669 cmdutil.checkunfinished(repo, commit=True)
1670
1670
1671 branch = repo[None].branch()
1671 branch = repo[None].branch()
1672 bheads = repo.branchheads(branch)
1672 bheads = repo.branchheads(branch)
1673
1673
1674 extra = {}
1674 extra = {}
1675 if opts.get('close_branch') or opts.get('force_close_branch'):
1675 if opts.get('close_branch') or opts.get('force_close_branch'):
1676 extra['close'] = '1'
1676 extra['close'] = '1'
1677
1677
1678 if repo['.'].closesbranch():
1678 if repo['.'].closesbranch():
1679 raise error.Abort(_('current revision is already a branch closing'
1679 raise error.Abort(_('current revision is already a branch closing'
1680 ' head'))
1680 ' head'))
1681 elif not bheads:
1681 elif not bheads:
1682 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1682 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1683 elif (branch == repo['.'].branch() and repo['.'].node() not in bheads
1683 elif (branch == repo['.'].branch() and repo['.'].node() not in bheads
1684 and not opts.get('force_close_branch')):
1684 and not opts.get('force_close_branch')):
1685 hint = _('use --force-close-branch to close branch from a non-head'
1685 hint = _('use --force-close-branch to close branch from a non-head'
1686 ' changeset')
1686 ' changeset')
1687 raise error.Abort(_('can only close branch heads'), hint=hint)
1687 raise error.Abort(_('can only close branch heads'), hint=hint)
1688 elif opts.get('amend'):
1688 elif opts.get('amend'):
1689 if (repo['.'].p1().branch() != branch and
1689 if (repo['.'].p1().branch() != branch and
1690 repo['.'].p2().branch() != branch):
1690 repo['.'].p2().branch() != branch):
1691 raise error.Abort(_('can only close branch heads'))
1691 raise error.Abort(_('can only close branch heads'))
1692
1692
1693 if opts.get('amend'):
1693 if opts.get('amend'):
1694 if ui.configbool('ui', 'commitsubrepos'):
1694 if ui.configbool('ui', 'commitsubrepos'):
1695 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1695 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1696
1696
1697 old = repo['.']
1697 old = repo['.']
1698 rewriteutil.precheck(repo, [old.rev()], 'amend')
1698 rewriteutil.precheck(repo, [old.rev()], 'amend')
1699
1699
1700 # Currently histedit gets confused if an amend happens while histedit
1700 # Currently histedit gets confused if an amend happens while histedit
1701 # is in progress. Since we have a checkunfinished command, we are
1701 # is in progress. Since we have a checkunfinished command, we are
1702 # temporarily honoring it.
1702 # temporarily honoring it.
1703 #
1703 #
1704 # Note: eventually this guard will be removed. Please do not expect
1704 # Note: eventually this guard will be removed. Please do not expect
1705 # this behavior to remain.
1705 # this behavior to remain.
1706 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1706 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1707 cmdutil.checkunfinished(repo)
1707 cmdutil.checkunfinished(repo)
1708
1708
1709 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1709 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1710 if node == old.node():
1710 if node == old.node():
1711 ui.status(_("nothing changed\n"))
1711 ui.status(_("nothing changed\n"))
1712 return 1
1712 return 1
1713 else:
1713 else:
1714 def commitfunc(ui, repo, message, match, opts):
1714 def commitfunc(ui, repo, message, match, opts):
1715 overrides = {}
1715 overrides = {}
1716 if opts.get('secret'):
1716 if opts.get('secret'):
1717 overrides[('phases', 'new-commit')] = 'secret'
1717 overrides[('phases', 'new-commit')] = 'secret'
1718
1718
1719 baseui = repo.baseui
1719 baseui = repo.baseui
1720 with baseui.configoverride(overrides, 'commit'):
1720 with baseui.configoverride(overrides, 'commit'):
1721 with ui.configoverride(overrides, 'commit'):
1721 with ui.configoverride(overrides, 'commit'):
1722 editform = cmdutil.mergeeditform(repo[None],
1722 editform = cmdutil.mergeeditform(repo[None],
1723 'commit.normal')
1723 'commit.normal')
1724 editor = cmdutil.getcommiteditor(
1724 editor = cmdutil.getcommiteditor(
1725 editform=editform, **pycompat.strkwargs(opts))
1725 editform=editform, **pycompat.strkwargs(opts))
1726 return repo.commit(message,
1726 return repo.commit(message,
1727 opts.get('user'),
1727 opts.get('user'),
1728 opts.get('date'),
1728 opts.get('date'),
1729 match,
1729 match,
1730 editor=editor,
1730 editor=editor,
1731 extra=extra)
1731 extra=extra)
1732
1732
1733 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1733 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1734
1734
1735 if not node:
1735 if not node:
1736 stat = cmdutil.postcommitstatus(repo, pats, opts)
1736 stat = cmdutil.postcommitstatus(repo, pats, opts)
1737 if stat[3]:
1737 if stat[3]:
1738 ui.status(_("nothing changed (%d missing files, see "
1738 ui.status(_("nothing changed (%d missing files, see "
1739 "'hg status')\n") % len(stat[3]))
1739 "'hg status')\n") % len(stat[3]))
1740 else:
1740 else:
1741 ui.status(_("nothing changed\n"))
1741 ui.status(_("nothing changed\n"))
1742 return 1
1742 return 1
1743
1743
1744 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1744 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1745
1745
1746 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1746 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1747 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1747 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1748 unknown=True, subrepos=opts.get('subrepos'))
1748 unknown=True, subrepos=opts.get('subrepos'))
1749
1749
1750 @command('config|showconfig|debugconfig',
1750 @command('config|showconfig|debugconfig',
1751 [('u', 'untrusted', None, _('show untrusted configuration options')),
1751 [('u', 'untrusted', None, _('show untrusted configuration options')),
1752 ('e', 'edit', None, _('edit user config')),
1752 ('e', 'edit', None, _('edit user config')),
1753 ('l', 'local', None, _('edit repository config')),
1753 ('l', 'local', None, _('edit repository config')),
1754 ('g', 'global', None, _('edit global config'))] + formatteropts,
1754 ('g', 'global', None, _('edit global config'))] + formatteropts,
1755 _('[-u] [NAME]...'),
1755 _('[-u] [NAME]...'),
1756 helpcategory=command.CATEGORY_HELP,
1756 helpcategory=command.CATEGORY_HELP,
1757 optionalrepo=True,
1757 optionalrepo=True,
1758 intents={INTENT_READONLY})
1758 intents={INTENT_READONLY})
1759 def config(ui, repo, *values, **opts):
1759 def config(ui, repo, *values, **opts):
1760 """show combined config settings from all hgrc files
1760 """show combined config settings from all hgrc files
1761
1761
1762 With no arguments, print names and values of all config items.
1762 With no arguments, print names and values of all config items.
1763
1763
1764 With one argument of the form section.name, print just the value
1764 With one argument of the form section.name, print just the value
1765 of that config item.
1765 of that config item.
1766
1766
1767 With multiple arguments, print names and values of all config
1767 With multiple arguments, print names and values of all config
1768 items with matching section names or section.names.
1768 items with matching section names or section.names.
1769
1769
1770 With --edit, start an editor on the user-level config file. With
1770 With --edit, start an editor on the user-level config file. With
1771 --global, edit the system-wide config file. With --local, edit the
1771 --global, edit the system-wide config file. With --local, edit the
1772 repository-level config file.
1772 repository-level config file.
1773
1773
1774 With --debug, the source (filename and line number) is printed
1774 With --debug, the source (filename and line number) is printed
1775 for each config item.
1775 for each config item.
1776
1776
1777 See :hg:`help config` for more information about config files.
1777 See :hg:`help config` for more information about config files.
1778
1778
1779 .. container:: verbose
1779 .. container:: verbose
1780
1780
1781 Template:
1781 Template:
1782
1782
1783 The following keywords are supported. See also :hg:`help templates`.
1783 The following keywords are supported. See also :hg:`help templates`.
1784
1784
1785 :name: String. Config name.
1785 :name: String. Config name.
1786 :source: String. Filename and line number where the item is defined.
1786 :source: String. Filename and line number where the item is defined.
1787 :value: String. Config value.
1787 :value: String. Config value.
1788
1788
1789 Returns 0 on success, 1 if NAME does not exist.
1789 Returns 0 on success, 1 if NAME does not exist.
1790
1790
1791 """
1791 """
1792
1792
1793 opts = pycompat.byteskwargs(opts)
1793 opts = pycompat.byteskwargs(opts)
1794 if opts.get('edit') or opts.get('local') or opts.get('global'):
1794 if opts.get('edit') or opts.get('local') or opts.get('global'):
1795 if opts.get('local') and opts.get('global'):
1795 if opts.get('local') and opts.get('global'):
1796 raise error.Abort(_("can't use --local and --global together"))
1796 raise error.Abort(_("can't use --local and --global together"))
1797
1797
1798 if opts.get('local'):
1798 if opts.get('local'):
1799 if not repo:
1799 if not repo:
1800 raise error.Abort(_("can't use --local outside a repository"))
1800 raise error.Abort(_("can't use --local outside a repository"))
1801 paths = [repo.vfs.join('hgrc')]
1801 paths = [repo.vfs.join('hgrc')]
1802 elif opts.get('global'):
1802 elif opts.get('global'):
1803 paths = rcutil.systemrcpath()
1803 paths = rcutil.systemrcpath()
1804 else:
1804 else:
1805 paths = rcutil.userrcpath()
1805 paths = rcutil.userrcpath()
1806
1806
1807 for f in paths:
1807 for f in paths:
1808 if os.path.exists(f):
1808 if os.path.exists(f):
1809 break
1809 break
1810 else:
1810 else:
1811 if opts.get('global'):
1811 if opts.get('global'):
1812 samplehgrc = uimod.samplehgrcs['global']
1812 samplehgrc = uimod.samplehgrcs['global']
1813 elif opts.get('local'):
1813 elif opts.get('local'):
1814 samplehgrc = uimod.samplehgrcs['local']
1814 samplehgrc = uimod.samplehgrcs['local']
1815 else:
1815 else:
1816 samplehgrc = uimod.samplehgrcs['user']
1816 samplehgrc = uimod.samplehgrcs['user']
1817
1817
1818 f = paths[0]
1818 f = paths[0]
1819 fp = open(f, "wb")
1819 fp = open(f, "wb")
1820 fp.write(util.tonativeeol(samplehgrc))
1820 fp.write(util.tonativeeol(samplehgrc))
1821 fp.close()
1821 fp.close()
1822
1822
1823 editor = ui.geteditor()
1823 editor = ui.geteditor()
1824 ui.system("%s \"%s\"" % (editor, f),
1824 ui.system("%s \"%s\"" % (editor, f),
1825 onerr=error.Abort, errprefix=_("edit failed"),
1825 onerr=error.Abort, errprefix=_("edit failed"),
1826 blockedtag='config_edit')
1826 blockedtag='config_edit')
1827 return
1827 return
1828 ui.pager('config')
1828 ui.pager('config')
1829 fm = ui.formatter('config', opts)
1829 fm = ui.formatter('config', opts)
1830 for t, f in rcutil.rccomponents():
1830 for t, f in rcutil.rccomponents():
1831 if t == 'path':
1831 if t == 'path':
1832 ui.debug('read config from: %s\n' % f)
1832 ui.debug('read config from: %s\n' % f)
1833 elif t == 'items':
1833 elif t == 'items':
1834 for section, name, value, source in f:
1834 for section, name, value, source in f:
1835 ui.debug('set config by: %s\n' % source)
1835 ui.debug('set config by: %s\n' % source)
1836 else:
1836 else:
1837 raise error.ProgrammingError('unknown rctype: %s' % t)
1837 raise error.ProgrammingError('unknown rctype: %s' % t)
1838 untrusted = bool(opts.get('untrusted'))
1838 untrusted = bool(opts.get('untrusted'))
1839
1839
1840 selsections = selentries = []
1840 selsections = selentries = []
1841 if values:
1841 if values:
1842 selsections = [v for v in values if '.' not in v]
1842 selsections = [v for v in values if '.' not in v]
1843 selentries = [v for v in values if '.' in v]
1843 selentries = [v for v in values if '.' in v]
1844 uniquesel = (len(selentries) == 1 and not selsections)
1844 uniquesel = (len(selentries) == 1 and not selsections)
1845 selsections = set(selsections)
1845 selsections = set(selsections)
1846 selentries = set(selentries)
1846 selentries = set(selentries)
1847
1847
1848 matched = False
1848 matched = False
1849 for section, name, value in ui.walkconfig(untrusted=untrusted):
1849 for section, name, value in ui.walkconfig(untrusted=untrusted):
1850 source = ui.configsource(section, name, untrusted)
1850 source = ui.configsource(section, name, untrusted)
1851 value = pycompat.bytestr(value)
1851 value = pycompat.bytestr(value)
1852 if fm.isplain():
1852 if fm.isplain():
1853 source = source or 'none'
1853 source = source or 'none'
1854 value = value.replace('\n', '\\n')
1854 value = value.replace('\n', '\\n')
1855 entryname = section + '.' + name
1855 entryname = section + '.' + name
1856 if values and not (section in selsections or entryname in selentries):
1856 if values and not (section in selsections or entryname in selentries):
1857 continue
1857 continue
1858 fm.startitem()
1858 fm.startitem()
1859 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1859 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1860 if uniquesel:
1860 if uniquesel:
1861 fm.data(name=entryname)
1861 fm.data(name=entryname)
1862 fm.write('value', '%s\n', value)
1862 fm.write('value', '%s\n', value)
1863 else:
1863 else:
1864 fm.write('name value', '%s=%s\n', entryname, value)
1864 fm.write('name value', '%s=%s\n', entryname, value)
1865 matched = True
1865 matched = True
1866 fm.end()
1866 fm.end()
1867 if matched:
1867 if matched:
1868 return 0
1868 return 0
1869 return 1
1869 return 1
1870
1870
1871 @command('copy|cp',
1871 @command('copy|cp',
1872 [('A', 'after', None, _('record a copy that has already occurred')),
1872 [('A', 'after', None, _('record a copy that has already occurred')),
1873 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1873 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1874 ] + walkopts + dryrunopts,
1874 ] + walkopts + dryrunopts,
1875 _('[OPTION]... [SOURCE]... DEST'),
1875 _('[OPTION]... [SOURCE]... DEST'),
1876 helpcategory=command.CATEGORY_FILE_CONTENTS)
1876 helpcategory=command.CATEGORY_FILE_CONTENTS)
1877 def copy(ui, repo, *pats, **opts):
1877 def copy(ui, repo, *pats, **opts):
1878 """mark files as copied for the next commit
1878 """mark files as copied for the next commit
1879
1879
1880 Mark dest as having copies of source files. If dest is a
1880 Mark dest as having copies of source files. If dest is a
1881 directory, copies are put in that directory. If dest is a file,
1881 directory, copies are put in that directory. If dest is a file,
1882 the source must be a single file.
1882 the source must be a single file.
1883
1883
1884 By default, this command copies the contents of files as they
1884 By default, this command copies the contents of files as they
1885 exist in the working directory. If invoked with -A/--after, the
1885 exist in the working directory. If invoked with -A/--after, the
1886 operation is recorded, but no copying is performed.
1886 operation is recorded, but no copying is performed.
1887
1887
1888 This command takes effect with the next commit. To undo a copy
1888 This command takes effect with the next commit. To undo a copy
1889 before that, see :hg:`revert`.
1889 before that, see :hg:`revert`.
1890
1890
1891 Returns 0 on success, 1 if errors are encountered.
1891 Returns 0 on success, 1 if errors are encountered.
1892 """
1892 """
1893 opts = pycompat.byteskwargs(opts)
1893 opts = pycompat.byteskwargs(opts)
1894 with repo.wlock(False):
1894 with repo.wlock(False):
1895 return cmdutil.copy(ui, repo, pats, opts)
1895 return cmdutil.copy(ui, repo, pats, opts)
1896
1896
1897 @command(
1897 @command(
1898 'debugcommands', [], _('[COMMAND]'),
1898 'debugcommands', [], _('[COMMAND]'),
1899 helpcategory=command.CATEGORY_HELP,
1899 helpcategory=command.CATEGORY_HELP,
1900 norepo=True)
1900 norepo=True)
1901 def debugcommands(ui, cmd='', *args):
1901 def debugcommands(ui, cmd='', *args):
1902 """list all available commands and options"""
1902 """list all available commands and options"""
1903 for cmd, vals in sorted(table.iteritems()):
1903 for cmd, vals in sorted(table.iteritems()):
1904 cmd = cmd.split('|')[0]
1904 cmd = cmd.split('|')[0]
1905 opts = ', '.join([i[1] for i in vals[1]])
1905 opts = ', '.join([i[1] for i in vals[1]])
1906 ui.write('%s: %s\n' % (cmd, opts))
1906 ui.write('%s: %s\n' % (cmd, opts))
1907
1907
1908 @command('debugcomplete',
1908 @command('debugcomplete',
1909 [('o', 'options', None, _('show the command options'))],
1909 [('o', 'options', None, _('show the command options'))],
1910 _('[-o] CMD'),
1910 _('[-o] CMD'),
1911 helpcategory=command.CATEGORY_HELP,
1911 helpcategory=command.CATEGORY_HELP,
1912 norepo=True)
1912 norepo=True)
1913 def debugcomplete(ui, cmd='', **opts):
1913 def debugcomplete(ui, cmd='', **opts):
1914 """returns the completion list associated with the given command"""
1914 """returns the completion list associated with the given command"""
1915
1915
1916 if opts.get(r'options'):
1916 if opts.get(r'options'):
1917 options = []
1917 options = []
1918 otables = [globalopts]
1918 otables = [globalopts]
1919 if cmd:
1919 if cmd:
1920 aliases, entry = cmdutil.findcmd(cmd, table, False)
1920 aliases, entry = cmdutil.findcmd(cmd, table, False)
1921 otables.append(entry[1])
1921 otables.append(entry[1])
1922 for t in otables:
1922 for t in otables:
1923 for o in t:
1923 for o in t:
1924 if "(DEPRECATED)" in o[3]:
1924 if "(DEPRECATED)" in o[3]:
1925 continue
1925 continue
1926 if o[0]:
1926 if o[0]:
1927 options.append('-%s' % o[0])
1927 options.append('-%s' % o[0])
1928 options.append('--%s' % o[1])
1928 options.append('--%s' % o[1])
1929 ui.write("%s\n" % "\n".join(options))
1929 ui.write("%s\n" % "\n".join(options))
1930 return
1930 return
1931
1931
1932 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1932 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1933 if ui.verbose:
1933 if ui.verbose:
1934 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1934 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1935 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1935 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1936
1936
1937 @command('diff',
1937 @command('diff',
1938 [('r', 'rev', [], _('revision'), _('REV')),
1938 [('r', 'rev', [], _('revision'), _('REV')),
1939 ('c', 'change', '', _('change made by revision'), _('REV'))
1939 ('c', 'change', '', _('change made by revision'), _('REV'))
1940 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1940 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1941 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1941 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1942 helpcategory=command.CATEGORY_FILE_CONTENTS,
1942 helpcategory=command.CATEGORY_FILE_CONTENTS,
1943 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1943 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1944 def diff(ui, repo, *pats, **opts):
1944 def diff(ui, repo, *pats, **opts):
1945 """diff repository (or selected files)
1945 """diff repository (or selected files)
1946
1946
1947 Show differences between revisions for the specified files.
1947 Show differences between revisions for the specified files.
1948
1948
1949 Differences between files are shown using the unified diff format.
1949 Differences between files are shown using the unified diff format.
1950
1950
1951 .. note::
1951 .. note::
1952
1952
1953 :hg:`diff` may generate unexpected results for merges, as it will
1953 :hg:`diff` may generate unexpected results for merges, as it will
1954 default to comparing against the working directory's first
1954 default to comparing against the working directory's first
1955 parent changeset if no revisions are specified.
1955 parent changeset if no revisions are specified.
1956
1956
1957 When two revision arguments are given, then changes are shown
1957 When two revision arguments are given, then changes are shown
1958 between those revisions. If only one revision is specified then
1958 between those revisions. If only one revision is specified then
1959 that revision is compared to the working directory, and, when no
1959 that revision is compared to the working directory, and, when no
1960 revisions are specified, the working directory files are compared
1960 revisions are specified, the working directory files are compared
1961 to its first parent.
1961 to its first parent.
1962
1962
1963 Alternatively you can specify -c/--change with a revision to see
1963 Alternatively you can specify -c/--change with a revision to see
1964 the changes in that changeset relative to its first parent.
1964 the changes in that changeset relative to its first parent.
1965
1965
1966 Without the -a/--text option, diff will avoid generating diffs of
1966 Without the -a/--text option, diff will avoid generating diffs of
1967 files it detects as binary. With -a, diff will generate a diff
1967 files it detects as binary. With -a, diff will generate a diff
1968 anyway, probably with undesirable results.
1968 anyway, probably with undesirable results.
1969
1969
1970 Use the -g/--git option to generate diffs in the git extended diff
1970 Use the -g/--git option to generate diffs in the git extended diff
1971 format. For more information, read :hg:`help diffs`.
1971 format. For more information, read :hg:`help diffs`.
1972
1972
1973 .. container:: verbose
1973 .. container:: verbose
1974
1974
1975 Examples:
1975 Examples:
1976
1976
1977 - compare a file in the current working directory to its parent::
1977 - compare a file in the current working directory to its parent::
1978
1978
1979 hg diff foo.c
1979 hg diff foo.c
1980
1980
1981 - compare two historical versions of a directory, with rename info::
1981 - compare two historical versions of a directory, with rename info::
1982
1982
1983 hg diff --git -r 1.0:1.2 lib/
1983 hg diff --git -r 1.0:1.2 lib/
1984
1984
1985 - get change stats relative to the last change on some date::
1985 - get change stats relative to the last change on some date::
1986
1986
1987 hg diff --stat -r "date('may 2')"
1987 hg diff --stat -r "date('may 2')"
1988
1988
1989 - diff all newly-added files that contain a keyword::
1989 - diff all newly-added files that contain a keyword::
1990
1990
1991 hg diff "set:added() and grep(GNU)"
1991 hg diff "set:added() and grep(GNU)"
1992
1992
1993 - compare a revision and its parents::
1993 - compare a revision and its parents::
1994
1994
1995 hg diff -c 9353 # compare against first parent
1995 hg diff -c 9353 # compare against first parent
1996 hg diff -r 9353^:9353 # same using revset syntax
1996 hg diff -r 9353^:9353 # same using revset syntax
1997 hg diff -r 9353^2:9353 # compare against the second parent
1997 hg diff -r 9353^2:9353 # compare against the second parent
1998
1998
1999 Returns 0 on success.
1999 Returns 0 on success.
2000 """
2000 """
2001
2001
2002 opts = pycompat.byteskwargs(opts)
2002 opts = pycompat.byteskwargs(opts)
2003 revs = opts.get('rev')
2003 revs = opts.get('rev')
2004 change = opts.get('change')
2004 change = opts.get('change')
2005 stat = opts.get('stat')
2005 stat = opts.get('stat')
2006 reverse = opts.get('reverse')
2006 reverse = opts.get('reverse')
2007
2007
2008 if revs and change:
2008 if revs and change:
2009 msg = _('cannot specify --rev and --change at the same time')
2009 msg = _('cannot specify --rev and --change at the same time')
2010 raise error.Abort(msg)
2010 raise error.Abort(msg)
2011 elif change:
2011 elif change:
2012 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2012 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2013 ctx2 = scmutil.revsingle(repo, change, None)
2013 ctx2 = scmutil.revsingle(repo, change, None)
2014 ctx1 = ctx2.p1()
2014 ctx1 = ctx2.p1()
2015 else:
2015 else:
2016 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2016 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2017 ctx1, ctx2 = scmutil.revpair(repo, revs)
2017 ctx1, ctx2 = scmutil.revpair(repo, revs)
2018 node1, node2 = ctx1.node(), ctx2.node()
2018 node1, node2 = ctx1.node(), ctx2.node()
2019
2019
2020 if reverse:
2020 if reverse:
2021 node1, node2 = node2, node1
2021 node1, node2 = node2, node1
2022
2022
2023 diffopts = patch.diffallopts(ui, opts)
2023 diffopts = patch.diffallopts(ui, opts)
2024 m = scmutil.match(ctx2, pats, opts)
2024 m = scmutil.match(ctx2, pats, opts)
2025 m = repo.narrowmatch(m)
2025 m = repo.narrowmatch(m)
2026 ui.pager('diff')
2026 ui.pager('diff')
2027 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2027 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2028 listsubrepos=opts.get('subrepos'),
2028 listsubrepos=opts.get('subrepos'),
2029 root=opts.get('root'))
2029 root=opts.get('root'))
2030
2030
2031 @command('export',
2031 @command('export',
2032 [('B', 'bookmark', '',
2032 [('B', 'bookmark', '',
2033 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2033 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2034 ('o', 'output', '',
2034 ('o', 'output', '',
2035 _('print output to file with formatted name'), _('FORMAT')),
2035 _('print output to file with formatted name'), _('FORMAT')),
2036 ('', 'switch-parent', None, _('diff against the second parent')),
2036 ('', 'switch-parent', None, _('diff against the second parent')),
2037 ('r', 'rev', [], _('revisions to export'), _('REV')),
2037 ('r', 'rev', [], _('revisions to export'), _('REV')),
2038 ] + diffopts + formatteropts,
2038 ] + diffopts + formatteropts,
2039 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2039 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2040 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2040 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2041 helpbasic=True, intents={INTENT_READONLY})
2041 helpbasic=True, intents={INTENT_READONLY})
2042 def export(ui, repo, *changesets, **opts):
2042 def export(ui, repo, *changesets, **opts):
2043 """dump the header and diffs for one or more changesets
2043 """dump the header and diffs for one or more changesets
2044
2044
2045 Print the changeset header and diffs for one or more revisions.
2045 Print the changeset header and diffs for one or more revisions.
2046 If no revision is given, the parent of the working directory is used.
2046 If no revision is given, the parent of the working directory is used.
2047
2047
2048 The information shown in the changeset header is: author, date,
2048 The information shown in the changeset header is: author, date,
2049 branch name (if non-default), changeset hash, parent(s) and commit
2049 branch name (if non-default), changeset hash, parent(s) and commit
2050 comment.
2050 comment.
2051
2051
2052 .. note::
2052 .. note::
2053
2053
2054 :hg:`export` may generate unexpected diff output for merge
2054 :hg:`export` may generate unexpected diff output for merge
2055 changesets, as it will compare the merge changeset against its
2055 changesets, as it will compare the merge changeset against its
2056 first parent only.
2056 first parent only.
2057
2057
2058 Output may be to a file, in which case the name of the file is
2058 Output may be to a file, in which case the name of the file is
2059 given using a template string. See :hg:`help templates`. In addition
2059 given using a template string. See :hg:`help templates`. In addition
2060 to the common template keywords, the following formatting rules are
2060 to the common template keywords, the following formatting rules are
2061 supported:
2061 supported:
2062
2062
2063 :``%%``: literal "%" character
2063 :``%%``: literal "%" character
2064 :``%H``: changeset hash (40 hexadecimal digits)
2064 :``%H``: changeset hash (40 hexadecimal digits)
2065 :``%N``: number of patches being generated
2065 :``%N``: number of patches being generated
2066 :``%R``: changeset revision number
2066 :``%R``: changeset revision number
2067 :``%b``: basename of the exporting repository
2067 :``%b``: basename of the exporting repository
2068 :``%h``: short-form changeset hash (12 hexadecimal digits)
2068 :``%h``: short-form changeset hash (12 hexadecimal digits)
2069 :``%m``: first line of the commit message (only alphanumeric characters)
2069 :``%m``: first line of the commit message (only alphanumeric characters)
2070 :``%n``: zero-padded sequence number, starting at 1
2070 :``%n``: zero-padded sequence number, starting at 1
2071 :``%r``: zero-padded changeset revision number
2071 :``%r``: zero-padded changeset revision number
2072 :``\\``: literal "\\" character
2072 :``\\``: literal "\\" character
2073
2073
2074 Without the -a/--text option, export will avoid generating diffs
2074 Without the -a/--text option, export will avoid generating diffs
2075 of files it detects as binary. With -a, export will generate a
2075 of files it detects as binary. With -a, export will generate a
2076 diff anyway, probably with undesirable results.
2076 diff anyway, probably with undesirable results.
2077
2077
2078 With -B/--bookmark changesets reachable by the given bookmark are
2078 With -B/--bookmark changesets reachable by the given bookmark are
2079 selected.
2079 selected.
2080
2080
2081 Use the -g/--git option to generate diffs in the git extended diff
2081 Use the -g/--git option to generate diffs in the git extended diff
2082 format. See :hg:`help diffs` for more information.
2082 format. See :hg:`help diffs` for more information.
2083
2083
2084 With the --switch-parent option, the diff will be against the
2084 With the --switch-parent option, the diff will be against the
2085 second parent. It can be useful to review a merge.
2085 second parent. It can be useful to review a merge.
2086
2086
2087 .. container:: verbose
2087 .. container:: verbose
2088
2088
2089 Template:
2089 Template:
2090
2090
2091 The following keywords are supported in addition to the common template
2091 The following keywords are supported in addition to the common template
2092 keywords and functions. See also :hg:`help templates`.
2092 keywords and functions. See also :hg:`help templates`.
2093
2093
2094 :diff: String. Diff content.
2094 :diff: String. Diff content.
2095 :parents: List of strings. Parent nodes of the changeset.
2095 :parents: List of strings. Parent nodes of the changeset.
2096
2096
2097 Examples:
2097 Examples:
2098
2098
2099 - use export and import to transplant a bugfix to the current
2099 - use export and import to transplant a bugfix to the current
2100 branch::
2100 branch::
2101
2101
2102 hg export -r 9353 | hg import -
2102 hg export -r 9353 | hg import -
2103
2103
2104 - export all the changesets between two revisions to a file with
2104 - export all the changesets between two revisions to a file with
2105 rename information::
2105 rename information::
2106
2106
2107 hg export --git -r 123:150 > changes.txt
2107 hg export --git -r 123:150 > changes.txt
2108
2108
2109 - split outgoing changes into a series of patches with
2109 - split outgoing changes into a series of patches with
2110 descriptive names::
2110 descriptive names::
2111
2111
2112 hg export -r "outgoing()" -o "%n-%m.patch"
2112 hg export -r "outgoing()" -o "%n-%m.patch"
2113
2113
2114 Returns 0 on success.
2114 Returns 0 on success.
2115 """
2115 """
2116 opts = pycompat.byteskwargs(opts)
2116 opts = pycompat.byteskwargs(opts)
2117 bookmark = opts.get('bookmark')
2117 bookmark = opts.get('bookmark')
2118 changesets += tuple(opts.get('rev', []))
2118 changesets += tuple(opts.get('rev', []))
2119
2119
2120 if bookmark and changesets:
2120 if bookmark and changesets:
2121 raise error.Abort(_("-r and -B are mutually exclusive"))
2121 raise error.Abort(_("-r and -B are mutually exclusive"))
2122
2122
2123 if bookmark:
2123 if bookmark:
2124 if bookmark not in repo._bookmarks:
2124 if bookmark not in repo._bookmarks:
2125 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2125 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2126
2126
2127 revs = scmutil.bookmarkrevs(repo, bookmark)
2127 revs = scmutil.bookmarkrevs(repo, bookmark)
2128 else:
2128 else:
2129 if not changesets:
2129 if not changesets:
2130 changesets = ['.']
2130 changesets = ['.']
2131
2131
2132 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2132 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2133 revs = scmutil.revrange(repo, changesets)
2133 revs = scmutil.revrange(repo, changesets)
2134
2134
2135 if not revs:
2135 if not revs:
2136 raise error.Abort(_("export requires at least one changeset"))
2136 raise error.Abort(_("export requires at least one changeset"))
2137 if len(revs) > 1:
2137 if len(revs) > 1:
2138 ui.note(_('exporting patches:\n'))
2138 ui.note(_('exporting patches:\n'))
2139 else:
2139 else:
2140 ui.note(_('exporting patch:\n'))
2140 ui.note(_('exporting patch:\n'))
2141
2141
2142 fntemplate = opts.get('output')
2142 fntemplate = opts.get('output')
2143 if cmdutil.isstdiofilename(fntemplate):
2143 if cmdutil.isstdiofilename(fntemplate):
2144 fntemplate = ''
2144 fntemplate = ''
2145
2145
2146 if fntemplate:
2146 if fntemplate:
2147 fm = formatter.nullformatter(ui, 'export', opts)
2147 fm = formatter.nullformatter(ui, 'export', opts)
2148 else:
2148 else:
2149 ui.pager('export')
2149 ui.pager('export')
2150 fm = ui.formatter('export', opts)
2150 fm = ui.formatter('export', opts)
2151 with fm:
2151 with fm:
2152 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2152 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2153 switch_parent=opts.get('switch_parent'),
2153 switch_parent=opts.get('switch_parent'),
2154 opts=patch.diffallopts(ui, opts))
2154 opts=patch.diffallopts(ui, opts))
2155
2155
2156 @command('files',
2156 @command('files',
2157 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2157 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2158 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2158 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2159 ] + walkopts + formatteropts + subrepoopts,
2159 ] + walkopts + formatteropts + subrepoopts,
2160 _('[OPTION]... [FILE]...'),
2160 _('[OPTION]... [FILE]...'),
2161 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2161 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2162 intents={INTENT_READONLY})
2162 intents={INTENT_READONLY})
2163 def files(ui, repo, *pats, **opts):
2163 def files(ui, repo, *pats, **opts):
2164 """list tracked files
2164 """list tracked files
2165
2165
2166 Print files under Mercurial control in the working directory or
2166 Print files under Mercurial control in the working directory or
2167 specified revision for given files (excluding removed files).
2167 specified revision for given files (excluding removed files).
2168 Files can be specified as filenames or filesets.
2168 Files can be specified as filenames or filesets.
2169
2169
2170 If no files are given to match, this command prints the names
2170 If no files are given to match, this command prints the names
2171 of all files under Mercurial control.
2171 of all files under Mercurial control.
2172
2172
2173 .. container:: verbose
2173 .. container:: verbose
2174
2174
2175 Template:
2175 Template:
2176
2176
2177 The following keywords are supported in addition to the common template
2177 The following keywords are supported in addition to the common template
2178 keywords and functions. See also :hg:`help templates`.
2178 keywords and functions. See also :hg:`help templates`.
2179
2179
2180 :flags: String. Character denoting file's symlink and executable bits.
2180 :flags: String. Character denoting file's symlink and executable bits.
2181 :path: String. Repository-absolute path of the file.
2181 :path: String. Repository-absolute path of the file.
2182 :size: Integer. Size of the file in bytes.
2182 :size: Integer. Size of the file in bytes.
2183
2183
2184 Examples:
2184 Examples:
2185
2185
2186 - list all files under the current directory::
2186 - list all files under the current directory::
2187
2187
2188 hg files .
2188 hg files .
2189
2189
2190 - shows sizes and flags for current revision::
2190 - shows sizes and flags for current revision::
2191
2191
2192 hg files -vr .
2192 hg files -vr .
2193
2193
2194 - list all files named README::
2194 - list all files named README::
2195
2195
2196 hg files -I "**/README"
2196 hg files -I "**/README"
2197
2197
2198 - list all binary files::
2198 - list all binary files::
2199
2199
2200 hg files "set:binary()"
2200 hg files "set:binary()"
2201
2201
2202 - find files containing a regular expression::
2202 - find files containing a regular expression::
2203
2203
2204 hg files "set:grep('bob')"
2204 hg files "set:grep('bob')"
2205
2205
2206 - search tracked file contents with xargs and grep::
2206 - search tracked file contents with xargs and grep::
2207
2207
2208 hg files -0 | xargs -0 grep foo
2208 hg files -0 | xargs -0 grep foo
2209
2209
2210 See :hg:`help patterns` and :hg:`help filesets` for more information
2210 See :hg:`help patterns` and :hg:`help filesets` for more information
2211 on specifying file patterns.
2211 on specifying file patterns.
2212
2212
2213 Returns 0 if a match is found, 1 otherwise.
2213 Returns 0 if a match is found, 1 otherwise.
2214
2214
2215 """
2215 """
2216
2216
2217 opts = pycompat.byteskwargs(opts)
2217 opts = pycompat.byteskwargs(opts)
2218 rev = opts.get('rev')
2218 rev = opts.get('rev')
2219 if rev:
2219 if rev:
2220 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2220 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2221 ctx = scmutil.revsingle(repo, rev, None)
2221 ctx = scmutil.revsingle(repo, rev, None)
2222
2222
2223 end = '\n'
2223 end = '\n'
2224 if opts.get('print0'):
2224 if opts.get('print0'):
2225 end = '\0'
2225 end = '\0'
2226 fmt = '%s' + end
2226 fmt = '%s' + end
2227
2227
2228 m = scmutil.match(ctx, pats, opts)
2228 m = scmutil.match(ctx, pats, opts)
2229 ui.pager('files')
2229 ui.pager('files')
2230 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2230 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2231 with ui.formatter('files', opts) as fm:
2231 with ui.formatter('files', opts) as fm:
2232 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2232 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2233 opts.get('subrepos'))
2233 opts.get('subrepos'))
2234
2234
2235 @command(
2235 @command(
2236 'forget',
2236 'forget',
2237 [('i', 'interactive', None, _('use interactive mode')),
2237 [('i', 'interactive', None, _('use interactive mode')),
2238 ] + walkopts + dryrunopts,
2238 ] + walkopts + dryrunopts,
2239 _('[OPTION]... FILE...'),
2239 _('[OPTION]... FILE...'),
2240 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2240 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2241 helpbasic=True, inferrepo=True)
2241 helpbasic=True, inferrepo=True)
2242 def forget(ui, repo, *pats, **opts):
2242 def forget(ui, repo, *pats, **opts):
2243 """forget the specified files on the next commit
2243 """forget the specified files on the next commit
2244
2244
2245 Mark the specified files so they will no longer be tracked
2245 Mark the specified files so they will no longer be tracked
2246 after the next commit.
2246 after the next commit.
2247
2247
2248 This only removes files from the current branch, not from the
2248 This only removes files from the current branch, not from the
2249 entire project history, and it does not delete them from the
2249 entire project history, and it does not delete them from the
2250 working directory.
2250 working directory.
2251
2251
2252 To delete the file from the working directory, see :hg:`remove`.
2252 To delete the file from the working directory, see :hg:`remove`.
2253
2253
2254 To undo a forget before the next commit, see :hg:`add`.
2254 To undo a forget before the next commit, see :hg:`add`.
2255
2255
2256 .. container:: verbose
2256 .. container:: verbose
2257
2257
2258 Examples:
2258 Examples:
2259
2259
2260 - forget newly-added binary files::
2260 - forget newly-added binary files::
2261
2261
2262 hg forget "set:added() and binary()"
2262 hg forget "set:added() and binary()"
2263
2263
2264 - forget files that would be excluded by .hgignore::
2264 - forget files that would be excluded by .hgignore::
2265
2265
2266 hg forget "set:hgignore()"
2266 hg forget "set:hgignore()"
2267
2267
2268 Returns 0 on success.
2268 Returns 0 on success.
2269 """
2269 """
2270
2270
2271 opts = pycompat.byteskwargs(opts)
2271 opts = pycompat.byteskwargs(opts)
2272 if not pats:
2272 if not pats:
2273 raise error.Abort(_('no files specified'))
2273 raise error.Abort(_('no files specified'))
2274
2274
2275 m = scmutil.match(repo[None], pats, opts)
2275 m = scmutil.match(repo[None], pats, opts)
2276 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2276 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2277 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2277 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2278 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2278 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2279 explicitonly=False, dryrun=dryrun,
2279 explicitonly=False, dryrun=dryrun,
2280 interactive=interactive)[0]
2280 interactive=interactive)[0]
2281 return rejected and 1 or 0
2281 return rejected and 1 or 0
2282
2282
2283 @command(
2283 @command(
2284 'graft',
2284 'graft',
2285 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2285 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2286 ('', 'base', '',
2286 ('', 'base', '',
2287 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2287 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2288 ('c', 'continue', False, _('resume interrupted graft')),
2288 ('c', 'continue', False, _('resume interrupted graft')),
2289 ('', 'stop', False, _('stop interrupted graft')),
2289 ('', 'stop', False, _('stop interrupted graft')),
2290 ('', 'abort', False, _('abort interrupted graft')),
2290 ('', 'abort', False, _('abort interrupted graft')),
2291 ('e', 'edit', False, _('invoke editor on commit messages')),
2291 ('e', 'edit', False, _('invoke editor on commit messages')),
2292 ('', 'log', None, _('append graft info to log message')),
2292 ('', 'log', None, _('append graft info to log message')),
2293 ('', 'no-commit', None,
2293 ('', 'no-commit', None,
2294 _("don't commit, just apply the changes in working directory")),
2294 _("don't commit, just apply the changes in working directory")),
2295 ('f', 'force', False, _('force graft')),
2295 ('f', 'force', False, _('force graft')),
2296 ('D', 'currentdate', False,
2296 ('D', 'currentdate', False,
2297 _('record the current date as commit date')),
2297 _('record the current date as commit date')),
2298 ('U', 'currentuser', False,
2298 ('U', 'currentuser', False,
2299 _('record the current user as committer'))]
2299 _('record the current user as committer'))]
2300 + commitopts2 + mergetoolopts + dryrunopts,
2300 + commitopts2 + mergetoolopts + dryrunopts,
2301 _('[OPTION]... [-r REV]... REV...'),
2301 _('[OPTION]... [-r REV]... REV...'),
2302 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2302 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2303 def graft(ui, repo, *revs, **opts):
2303 def graft(ui, repo, *revs, **opts):
2304 '''copy changes from other branches onto the current branch
2304 '''copy changes from other branches onto the current branch
2305
2305
2306 This command uses Mercurial's merge logic to copy individual
2306 This command uses Mercurial's merge logic to copy individual
2307 changes from other branches without merging branches in the
2307 changes from other branches without merging branches in the
2308 history graph. This is sometimes known as 'backporting' or
2308 history graph. This is sometimes known as 'backporting' or
2309 'cherry-picking'. By default, graft will copy user, date, and
2309 'cherry-picking'. By default, graft will copy user, date, and
2310 description from the source changesets.
2310 description from the source changesets.
2311
2311
2312 Changesets that are ancestors of the current revision, that have
2312 Changesets that are ancestors of the current revision, that have
2313 already been grafted, or that are merges will be skipped.
2313 already been grafted, or that are merges will be skipped.
2314
2314
2315 If --log is specified, log messages will have a comment appended
2315 If --log is specified, log messages will have a comment appended
2316 of the form::
2316 of the form::
2317
2317
2318 (grafted from CHANGESETHASH)
2318 (grafted from CHANGESETHASH)
2319
2319
2320 If --force is specified, revisions will be grafted even if they
2320 If --force is specified, revisions will be grafted even if they
2321 are already ancestors of, or have been grafted to, the destination.
2321 are already ancestors of, or have been grafted to, the destination.
2322 This is useful when the revisions have since been backed out.
2322 This is useful when the revisions have since been backed out.
2323
2323
2324 If a graft merge results in conflicts, the graft process is
2324 If a graft merge results in conflicts, the graft process is
2325 interrupted so that the current merge can be manually resolved.
2325 interrupted so that the current merge can be manually resolved.
2326 Once all conflicts are addressed, the graft process can be
2326 Once all conflicts are addressed, the graft process can be
2327 continued with the -c/--continue option.
2327 continued with the -c/--continue option.
2328
2328
2329 The -c/--continue option reapplies all the earlier options.
2329 The -c/--continue option reapplies all the earlier options.
2330
2330
2331 .. container:: verbose
2331 .. container:: verbose
2332
2332
2333 The --base option exposes more of how graft internally uses merge with a
2333 The --base option exposes more of how graft internally uses merge with a
2334 custom base revision. --base can be used to specify another ancestor than
2334 custom base revision. --base can be used to specify another ancestor than
2335 the first and only parent.
2335 the first and only parent.
2336
2336
2337 The command::
2337 The command::
2338
2338
2339 hg graft -r 345 --base 234
2339 hg graft -r 345 --base 234
2340
2340
2341 is thus pretty much the same as::
2341 is thus pretty much the same as::
2342
2342
2343 hg diff -r 234 -r 345 | hg import
2343 hg diff -r 234 -r 345 | hg import
2344
2344
2345 but using merge to resolve conflicts and track moved files.
2345 but using merge to resolve conflicts and track moved files.
2346
2346
2347 The result of a merge can thus be backported as a single commit by
2347 The result of a merge can thus be backported as a single commit by
2348 specifying one of the merge parents as base, and thus effectively
2348 specifying one of the merge parents as base, and thus effectively
2349 grafting the changes from the other side.
2349 grafting the changes from the other side.
2350
2350
2351 It is also possible to collapse multiple changesets and clean up history
2351 It is also possible to collapse multiple changesets and clean up history
2352 by specifying another ancestor as base, much like rebase --collapse
2352 by specifying another ancestor as base, much like rebase --collapse
2353 --keep.
2353 --keep.
2354
2354
2355 The commit message can be tweaked after the fact using commit --amend .
2355 The commit message can be tweaked after the fact using commit --amend .
2356
2356
2357 For using non-ancestors as the base to backout changes, see the backout
2357 For using non-ancestors as the base to backout changes, see the backout
2358 command and the hidden --parent option.
2358 command and the hidden --parent option.
2359
2359
2360 .. container:: verbose
2360 .. container:: verbose
2361
2361
2362 Examples:
2362 Examples:
2363
2363
2364 - copy a single change to the stable branch and edit its description::
2364 - copy a single change to the stable branch and edit its description::
2365
2365
2366 hg update stable
2366 hg update stable
2367 hg graft --edit 9393
2367 hg graft --edit 9393
2368
2368
2369 - graft a range of changesets with one exception, updating dates::
2369 - graft a range of changesets with one exception, updating dates::
2370
2370
2371 hg graft -D "2085::2093 and not 2091"
2371 hg graft -D "2085::2093 and not 2091"
2372
2372
2373 - continue a graft after resolving conflicts::
2373 - continue a graft after resolving conflicts::
2374
2374
2375 hg graft -c
2375 hg graft -c
2376
2376
2377 - show the source of a grafted changeset::
2377 - show the source of a grafted changeset::
2378
2378
2379 hg log --debug -r .
2379 hg log --debug -r .
2380
2380
2381 - show revisions sorted by date::
2381 - show revisions sorted by date::
2382
2382
2383 hg log -r "sort(all(), date)"
2383 hg log -r "sort(all(), date)"
2384
2384
2385 - backport the result of a merge as a single commit::
2385 - backport the result of a merge as a single commit::
2386
2386
2387 hg graft -r 123 --base 123^
2387 hg graft -r 123 --base 123^
2388
2388
2389 - land a feature branch as one changeset::
2389 - land a feature branch as one changeset::
2390
2390
2391 hg up -cr default
2391 hg up -cr default
2392 hg graft -r featureX --base "ancestor('featureX', 'default')"
2392 hg graft -r featureX --base "ancestor('featureX', 'default')"
2393
2393
2394 See :hg:`help revisions` for more about specifying revisions.
2394 See :hg:`help revisions` for more about specifying revisions.
2395
2395
2396 Returns 0 on successful completion.
2396 Returns 0 on successful completion.
2397 '''
2397 '''
2398 with repo.wlock():
2398 with repo.wlock():
2399 return _dograft(ui, repo, *revs, **opts)
2399 return _dograft(ui, repo, *revs, **opts)
2400
2400
2401 def _dograft(ui, repo, *revs, **opts):
2401 def _dograft(ui, repo, *revs, **opts):
2402 opts = pycompat.byteskwargs(opts)
2402 opts = pycompat.byteskwargs(opts)
2403 if revs and opts.get('rev'):
2403 if revs and opts.get('rev'):
2404 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2404 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2405 'revision ordering!\n'))
2405 'revision ordering!\n'))
2406
2406
2407 revs = list(revs)
2407 revs = list(revs)
2408 revs.extend(opts.get('rev'))
2408 revs.extend(opts.get('rev'))
2409 basectx = None
2409 basectx = None
2410 if opts.get('base'):
2410 if opts.get('base'):
2411 basectx = scmutil.revsingle(repo, opts['base'], None)
2411 basectx = scmutil.revsingle(repo, opts['base'], None)
2412 # a dict of data to be stored in state file
2412 # a dict of data to be stored in state file
2413 statedata = {}
2413 statedata = {}
2414 # list of new nodes created by ongoing graft
2414 # list of new nodes created by ongoing graft
2415 statedata['newnodes'] = []
2415 statedata['newnodes'] = []
2416
2416
2417 if opts.get('user') and opts.get('currentuser'):
2417 if opts.get('user') and opts.get('currentuser'):
2418 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2418 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2419 if opts.get('date') and opts.get('currentdate'):
2419 if opts.get('date') and opts.get('currentdate'):
2420 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2420 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2421 if not opts.get('user') and opts.get('currentuser'):
2421 if not opts.get('user') and opts.get('currentuser'):
2422 opts['user'] = ui.username()
2422 opts['user'] = ui.username()
2423 if not opts.get('date') and opts.get('currentdate'):
2423 if not opts.get('date') and opts.get('currentdate'):
2424 opts['date'] = "%d %d" % dateutil.makedate()
2424 opts['date'] = "%d %d" % dateutil.makedate()
2425
2425
2426 editor = cmdutil.getcommiteditor(editform='graft',
2426 editor = cmdutil.getcommiteditor(editform='graft',
2427 **pycompat.strkwargs(opts))
2427 **pycompat.strkwargs(opts))
2428
2428
2429 cont = False
2429 cont = False
2430 if opts.get('no_commit'):
2430 if opts.get('no_commit'):
2431 if opts.get('edit'):
2431 if opts.get('edit'):
2432 raise error.Abort(_("cannot specify --no-commit and "
2432 raise error.Abort(_("cannot specify --no-commit and "
2433 "--edit together"))
2433 "--edit together"))
2434 if opts.get('currentuser'):
2434 if opts.get('currentuser'):
2435 raise error.Abort(_("cannot specify --no-commit and "
2435 raise error.Abort(_("cannot specify --no-commit and "
2436 "--currentuser together"))
2436 "--currentuser together"))
2437 if opts.get('currentdate'):
2437 if opts.get('currentdate'):
2438 raise error.Abort(_("cannot specify --no-commit and "
2438 raise error.Abort(_("cannot specify --no-commit and "
2439 "--currentdate together"))
2439 "--currentdate together"))
2440 if opts.get('log'):
2440 if opts.get('log'):
2441 raise error.Abort(_("cannot specify --no-commit and "
2441 raise error.Abort(_("cannot specify --no-commit and "
2442 "--log together"))
2442 "--log together"))
2443
2443
2444 graftstate = statemod.cmdstate(repo, 'graftstate')
2444 graftstate = statemod.cmdstate(repo, 'graftstate')
2445
2445
2446 if opts.get('stop'):
2446 if opts.get('stop'):
2447 if opts.get('continue'):
2447 if opts.get('continue'):
2448 raise error.Abort(_("cannot use '--continue' and "
2448 raise error.Abort(_("cannot use '--continue' and "
2449 "'--stop' together"))
2449 "'--stop' together"))
2450 if opts.get('abort'):
2450 if opts.get('abort'):
2451 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2451 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2452
2452
2453 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2453 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2454 opts.get('date'), opts.get('currentdate'),
2454 opts.get('date'), opts.get('currentdate'),
2455 opts.get('currentuser'), opts.get('rev'))):
2455 opts.get('currentuser'), opts.get('rev'))):
2456 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2456 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2457 return _stopgraft(ui, repo, graftstate)
2457 return _stopgraft(ui, repo, graftstate)
2458 elif opts.get('abort'):
2458 elif opts.get('abort'):
2459 if opts.get('continue'):
2459 if opts.get('continue'):
2460 raise error.Abort(_("cannot use '--continue' and "
2460 raise error.Abort(_("cannot use '--continue' and "
2461 "'--abort' together"))
2461 "'--abort' together"))
2462 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2462 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2463 opts.get('date'), opts.get('currentdate'),
2463 opts.get('date'), opts.get('currentdate'),
2464 opts.get('currentuser'), opts.get('rev'))):
2464 opts.get('currentuser'), opts.get('rev'))):
2465 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2465 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2466
2466
2467 return cmdutil.abortgraft(ui, repo, graftstate)
2467 return cmdutil.abortgraft(ui, repo, graftstate)
2468 elif opts.get('continue'):
2468 elif opts.get('continue'):
2469 cont = True
2469 cont = True
2470 if revs:
2470 if revs:
2471 raise error.Abort(_("can't specify --continue and revisions"))
2471 raise error.Abort(_("can't specify --continue and revisions"))
2472 # read in unfinished revisions
2472 # read in unfinished revisions
2473 if graftstate.exists():
2473 if graftstate.exists():
2474 statedata = cmdutil.readgraftstate(repo, graftstate)
2474 statedata = cmdutil.readgraftstate(repo, graftstate)
2475 if statedata.get('date'):
2475 if statedata.get('date'):
2476 opts['date'] = statedata['date']
2476 opts['date'] = statedata['date']
2477 if statedata.get('user'):
2477 if statedata.get('user'):
2478 opts['user'] = statedata['user']
2478 opts['user'] = statedata['user']
2479 if statedata.get('log'):
2479 if statedata.get('log'):
2480 opts['log'] = True
2480 opts['log'] = True
2481 if statedata.get('no_commit'):
2481 if statedata.get('no_commit'):
2482 opts['no_commit'] = statedata.get('no_commit')
2482 opts['no_commit'] = statedata.get('no_commit')
2483 nodes = statedata['nodes']
2483 nodes = statedata['nodes']
2484 revs = [repo[node].rev() for node in nodes]
2484 revs = [repo[node].rev() for node in nodes]
2485 else:
2485 else:
2486 cmdutil.wrongtooltocontinue(repo, _('graft'))
2486 cmdutil.wrongtooltocontinue(repo, _('graft'))
2487 else:
2487 else:
2488 if not revs:
2488 if not revs:
2489 raise error.Abort(_('no revisions specified'))
2489 raise error.Abort(_('no revisions specified'))
2490 cmdutil.checkunfinished(repo)
2490 cmdutil.checkunfinished(repo)
2491 cmdutil.bailifchanged(repo)
2491 cmdutil.bailifchanged(repo)
2492 revs = scmutil.revrange(repo, revs)
2492 revs = scmutil.revrange(repo, revs)
2493
2493
2494 skipped = set()
2494 skipped = set()
2495 if basectx is None:
2495 if basectx is None:
2496 # check for merges
2496 # check for merges
2497 for rev in repo.revs('%ld and merge()', revs):
2497 for rev in repo.revs('%ld and merge()', revs):
2498 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2498 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2499 skipped.add(rev)
2499 skipped.add(rev)
2500 revs = [r for r in revs if r not in skipped]
2500 revs = [r for r in revs if r not in skipped]
2501 if not revs:
2501 if not revs:
2502 return -1
2502 return -1
2503 if basectx is not None and len(revs) != 1:
2503 if basectx is not None and len(revs) != 1:
2504 raise error.Abort(_('only one revision allowed with --base '))
2504 raise error.Abort(_('only one revision allowed with --base '))
2505
2505
2506 # Don't check in the --continue case, in effect retaining --force across
2506 # Don't check in the --continue case, in effect retaining --force across
2507 # --continues. That's because without --force, any revisions we decided to
2507 # --continues. That's because without --force, any revisions we decided to
2508 # skip would have been filtered out here, so they wouldn't have made their
2508 # skip would have been filtered out here, so they wouldn't have made their
2509 # way to the graftstate. With --force, any revisions we would have otherwise
2509 # way to the graftstate. With --force, any revisions we would have otherwise
2510 # skipped would not have been filtered out, and if they hadn't been applied
2510 # skipped would not have been filtered out, and if they hadn't been applied
2511 # already, they'd have been in the graftstate.
2511 # already, they'd have been in the graftstate.
2512 if not (cont or opts.get('force')) and basectx is None:
2512 if not (cont or opts.get('force')) and basectx is None:
2513 # check for ancestors of dest branch
2513 # check for ancestors of dest branch
2514 crev = repo['.'].rev()
2514 crev = repo['.'].rev()
2515 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2515 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2516 # XXX make this lazy in the future
2516 # XXX make this lazy in the future
2517 # don't mutate while iterating, create a copy
2517 # don't mutate while iterating, create a copy
2518 for rev in list(revs):
2518 for rev in list(revs):
2519 if rev in ancestors:
2519 if rev in ancestors:
2520 ui.warn(_('skipping ancestor revision %d:%s\n') %
2520 ui.warn(_('skipping ancestor revision %d:%s\n') %
2521 (rev, repo[rev]))
2521 (rev, repo[rev]))
2522 # XXX remove on list is slow
2522 # XXX remove on list is slow
2523 revs.remove(rev)
2523 revs.remove(rev)
2524 if not revs:
2524 if not revs:
2525 return -1
2525 return -1
2526
2526
2527 # analyze revs for earlier grafts
2527 # analyze revs for earlier grafts
2528 ids = {}
2528 ids = {}
2529 for ctx in repo.set("%ld", revs):
2529 for ctx in repo.set("%ld", revs):
2530 ids[ctx.hex()] = ctx.rev()
2530 ids[ctx.hex()] = ctx.rev()
2531 n = ctx.extra().get('source')
2531 n = ctx.extra().get('source')
2532 if n:
2532 if n:
2533 ids[n] = ctx.rev()
2533 ids[n] = ctx.rev()
2534
2534
2535 # check ancestors for earlier grafts
2535 # check ancestors for earlier grafts
2536 ui.debug('scanning for duplicate grafts\n')
2536 ui.debug('scanning for duplicate grafts\n')
2537
2537
2538 # The only changesets we can be sure doesn't contain grafts of any
2538 # The only changesets we can be sure doesn't contain grafts of any
2539 # revs, are the ones that are common ancestors of *all* revs:
2539 # revs, are the ones that are common ancestors of *all* revs:
2540 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2540 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2541 ctx = repo[rev]
2541 ctx = repo[rev]
2542 n = ctx.extra().get('source')
2542 n = ctx.extra().get('source')
2543 if n in ids:
2543 if n in ids:
2544 try:
2544 try:
2545 r = repo[n].rev()
2545 r = repo[n].rev()
2546 except error.RepoLookupError:
2546 except error.RepoLookupError:
2547 r = None
2547 r = None
2548 if r in revs:
2548 if r in revs:
2549 ui.warn(_('skipping revision %d:%s '
2549 ui.warn(_('skipping revision %d:%s '
2550 '(already grafted to %d:%s)\n')
2550 '(already grafted to %d:%s)\n')
2551 % (r, repo[r], rev, ctx))
2551 % (r, repo[r], rev, ctx))
2552 revs.remove(r)
2552 revs.remove(r)
2553 elif ids[n] in revs:
2553 elif ids[n] in revs:
2554 if r is None:
2554 if r is None:
2555 ui.warn(_('skipping already grafted revision %d:%s '
2555 ui.warn(_('skipping already grafted revision %d:%s '
2556 '(%d:%s also has unknown origin %s)\n')
2556 '(%d:%s also has unknown origin %s)\n')
2557 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2557 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2558 else:
2558 else:
2559 ui.warn(_('skipping already grafted revision %d:%s '
2559 ui.warn(_('skipping already grafted revision %d:%s '
2560 '(%d:%s also has origin %d:%s)\n')
2560 '(%d:%s also has origin %d:%s)\n')
2561 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2561 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2562 revs.remove(ids[n])
2562 revs.remove(ids[n])
2563 elif ctx.hex() in ids:
2563 elif ctx.hex() in ids:
2564 r = ids[ctx.hex()]
2564 r = ids[ctx.hex()]
2565 if r in revs:
2565 if r in revs:
2566 ui.warn(_('skipping already grafted revision %d:%s '
2566 ui.warn(_('skipping already grafted revision %d:%s '
2567 '(was grafted from %d:%s)\n') %
2567 '(was grafted from %d:%s)\n') %
2568 (r, repo[r], rev, ctx))
2568 (r, repo[r], rev, ctx))
2569 revs.remove(r)
2569 revs.remove(r)
2570 if not revs:
2570 if not revs:
2571 return -1
2571 return -1
2572
2572
2573 if opts.get('no_commit'):
2573 if opts.get('no_commit'):
2574 statedata['no_commit'] = True
2574 statedata['no_commit'] = True
2575 for pos, ctx in enumerate(repo.set("%ld", revs)):
2575 for pos, ctx in enumerate(repo.set("%ld", revs)):
2576 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2576 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2577 ctx.description().split('\n', 1)[0])
2577 ctx.description().split('\n', 1)[0])
2578 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2578 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2579 if names:
2579 if names:
2580 desc += ' (%s)' % ' '.join(names)
2580 desc += ' (%s)' % ' '.join(names)
2581 ui.status(_('grafting %s\n') % desc)
2581 ui.status(_('grafting %s\n') % desc)
2582 if opts.get('dry_run'):
2582 if opts.get('dry_run'):
2583 continue
2583 continue
2584
2584
2585 source = ctx.extra().get('source')
2585 source = ctx.extra().get('source')
2586 extra = {}
2586 extra = {}
2587 if source:
2587 if source:
2588 extra['source'] = source
2588 extra['source'] = source
2589 extra['intermediate-source'] = ctx.hex()
2589 extra['intermediate-source'] = ctx.hex()
2590 else:
2590 else:
2591 extra['source'] = ctx.hex()
2591 extra['source'] = ctx.hex()
2592 user = ctx.user()
2592 user = ctx.user()
2593 if opts.get('user'):
2593 if opts.get('user'):
2594 user = opts['user']
2594 user = opts['user']
2595 statedata['user'] = user
2595 statedata['user'] = user
2596 date = ctx.date()
2596 date = ctx.date()
2597 if opts.get('date'):
2597 if opts.get('date'):
2598 date = opts['date']
2598 date = opts['date']
2599 statedata['date'] = date
2599 statedata['date'] = date
2600 message = ctx.description()
2600 message = ctx.description()
2601 if opts.get('log'):
2601 if opts.get('log'):
2602 message += '\n(grafted from %s)' % ctx.hex()
2602 message += '\n(grafted from %s)' % ctx.hex()
2603 statedata['log'] = True
2603 statedata['log'] = True
2604
2604
2605 # we don't merge the first commit when continuing
2605 # we don't merge the first commit when continuing
2606 if not cont:
2606 if not cont:
2607 # perform the graft merge with p1(rev) as 'ancestor'
2607 # perform the graft merge with p1(rev) as 'ancestor'
2608 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2608 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2609 base = ctx.p1() if basectx is None else basectx
2609 base = ctx.p1() if basectx is None else basectx
2610 with ui.configoverride(overrides, 'graft'):
2610 with ui.configoverride(overrides, 'graft'):
2611 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2611 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2612 # report any conflicts
2612 # report any conflicts
2613 if stats.unresolvedcount > 0:
2613 if stats.unresolvedcount > 0:
2614 # write out state for --continue
2614 # write out state for --continue
2615 nodes = [repo[rev].hex() for rev in revs[pos:]]
2615 nodes = [repo[rev].hex() for rev in revs[pos:]]
2616 statedata['nodes'] = nodes
2616 statedata['nodes'] = nodes
2617 stateversion = 1
2617 stateversion = 1
2618 graftstate.save(stateversion, statedata)
2618 graftstate.save(stateversion, statedata)
2619 hint = _("use 'hg resolve' and 'hg graft --continue'")
2619 hint = _("use 'hg resolve' and 'hg graft --continue'")
2620 raise error.Abort(
2620 raise error.Abort(
2621 _("unresolved conflicts, can't continue"),
2621 _("unresolved conflicts, can't continue"),
2622 hint=hint)
2622 hint=hint)
2623 else:
2623 else:
2624 cont = False
2624 cont = False
2625
2625
2626 # commit if --no-commit is false
2626 # commit if --no-commit is false
2627 if not opts.get('no_commit'):
2627 if not opts.get('no_commit'):
2628 node = repo.commit(text=message, user=user, date=date, extra=extra,
2628 node = repo.commit(text=message, user=user, date=date, extra=extra,
2629 editor=editor)
2629 editor=editor)
2630 if node is None:
2630 if node is None:
2631 ui.warn(
2631 ui.warn(
2632 _('note: graft of %d:%s created no changes to commit\n') %
2632 _('note: graft of %d:%s created no changes to commit\n') %
2633 (ctx.rev(), ctx))
2633 (ctx.rev(), ctx))
2634 # checking that newnodes exist because old state files won't have it
2634 # checking that newnodes exist because old state files won't have it
2635 elif statedata.get('newnodes') is not None:
2635 elif statedata.get('newnodes') is not None:
2636 statedata['newnodes'].append(node)
2636 statedata['newnodes'].append(node)
2637
2637
2638 # remove state when we complete successfully
2638 # remove state when we complete successfully
2639 if not opts.get('dry_run'):
2639 if not opts.get('dry_run'):
2640 graftstate.delete()
2640 graftstate.delete()
2641
2641
2642 return 0
2642 return 0
2643
2643
2644 def _stopgraft(ui, repo, graftstate):
2644 def _stopgraft(ui, repo, graftstate):
2645 """stop the interrupted graft"""
2645 """stop the interrupted graft"""
2646 if not graftstate.exists():
2646 if not graftstate.exists():
2647 raise error.Abort(_("no interrupted graft found"))
2647 raise error.Abort(_("no interrupted graft found"))
2648 pctx = repo['.']
2648 pctx = repo['.']
2649 hg.updaterepo(repo, pctx.node(), overwrite=True)
2649 hg.updaterepo(repo, pctx.node(), overwrite=True)
2650 graftstate.delete()
2650 graftstate.delete()
2651 ui.status(_("stopped the interrupted graft\n"))
2651 ui.status(_("stopped the interrupted graft\n"))
2652 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2652 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2653 return 0
2653 return 0
2654
2654
2655 @command('grep',
2655 @command('grep',
2656 [('0', 'print0', None, _('end fields with NUL')),
2656 [('0', 'print0', None, _('end fields with NUL')),
2657 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2657 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2658 ('', 'diff', None, _('print all revisions when the term was introduced '
2658 ('', 'diff', None, _('print all revisions when the term was introduced '
2659 'or removed')),
2659 'or removed')),
2660 ('a', 'text', None, _('treat all files as text')),
2660 ('a', 'text', None, _('treat all files as text')),
2661 ('f', 'follow', None,
2661 ('f', 'follow', None,
2662 _('follow changeset history,'
2662 _('follow changeset history,'
2663 ' or file history across copies and renames')),
2663 ' or file history across copies and renames')),
2664 ('i', 'ignore-case', None, _('ignore case when matching')),
2664 ('i', 'ignore-case', None, _('ignore case when matching')),
2665 ('l', 'files-with-matches', None,
2665 ('l', 'files-with-matches', None,
2666 _('print only filenames and revisions that match')),
2666 _('print only filenames and revisions that match')),
2667 ('n', 'line-number', None, _('print matching line numbers')),
2667 ('n', 'line-number', None, _('print matching line numbers')),
2668 ('r', 'rev', [],
2668 ('r', 'rev', [],
2669 _('only search files changed within revision range'), _('REV')),
2669 _('only search files changed within revision range'), _('REV')),
2670 ('', 'all-files', None,
2670 ('', 'all-files', None,
2671 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2671 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2672 ('u', 'user', None, _('list the author (long with -v)')),
2672 ('u', 'user', None, _('list the author (long with -v)')),
2673 ('d', 'date', None, _('list the date (short with -q)')),
2673 ('d', 'date', None, _('list the date (short with -q)')),
2674 ] + formatteropts + walkopts,
2674 ] + formatteropts + walkopts,
2675 _('[OPTION]... PATTERN [FILE]...'),
2675 _('[OPTION]... PATTERN [FILE]...'),
2676 helpcategory=command.CATEGORY_FILE_CONTENTS,
2676 helpcategory=command.CATEGORY_FILE_CONTENTS,
2677 inferrepo=True,
2677 inferrepo=True,
2678 intents={INTENT_READONLY})
2678 intents={INTENT_READONLY})
2679 def grep(ui, repo, pattern, *pats, **opts):
2679 def grep(ui, repo, pattern, *pats, **opts):
2680 """search revision history for a pattern in specified files
2680 """search revision history for a pattern in specified files
2681
2681
2682 Search revision history for a regular expression in the specified
2682 Search revision history for a regular expression in the specified
2683 files or the entire project.
2683 files or the entire project.
2684
2684
2685 By default, grep prints the most recent revision number for each
2685 By default, grep prints the most recent revision number for each
2686 file in which it finds a match. To get it to print every revision
2686 file in which it finds a match. To get it to print every revision
2687 that contains a change in match status ("-" for a match that becomes
2687 that contains a change in match status ("-" for a match that becomes
2688 a non-match, or "+" for a non-match that becomes a match), use the
2688 a non-match, or "+" for a non-match that becomes a match), use the
2689 --diff flag.
2689 --diff flag.
2690
2690
2691 PATTERN can be any Python (roughly Perl-compatible) regular
2691 PATTERN can be any Python (roughly Perl-compatible) regular
2692 expression.
2692 expression.
2693
2693
2694 If no FILEs are specified (and -f/--follow isn't set), all files in
2694 If no FILEs are specified (and -f/--follow isn't set), all files in
2695 the repository are searched, including those that don't exist in the
2695 the repository are searched, including those that don't exist in the
2696 current branch or have been deleted in a prior changeset.
2696 current branch or have been deleted in a prior changeset.
2697
2697
2698 .. container:: verbose
2698 .. container:: verbose
2699
2699
2700 Template:
2700 Template:
2701
2701
2702 The following keywords are supported in addition to the common template
2702 The following keywords are supported in addition to the common template
2703 keywords and functions. See also :hg:`help templates`.
2703 keywords and functions. See also :hg:`help templates`.
2704
2704
2705 :change: String. Character denoting insertion ``+`` or removal ``-``.
2705 :change: String. Character denoting insertion ``+`` or removal ``-``.
2706 Available if ``--diff`` is specified.
2706 Available if ``--diff`` is specified.
2707 :lineno: Integer. Line number of the match.
2707 :lineno: Integer. Line number of the match.
2708 :path: String. Repository-absolute path of the file.
2708 :path: String. Repository-absolute path of the file.
2709 :texts: List of text chunks.
2709 :texts: List of text chunks.
2710
2710
2711 And each entry of ``{texts}`` provides the following sub-keywords.
2711 And each entry of ``{texts}`` provides the following sub-keywords.
2712
2712
2713 :matched: Boolean. True if the chunk matches the specified pattern.
2713 :matched: Boolean. True if the chunk matches the specified pattern.
2714 :text: String. Chunk content.
2714 :text: String. Chunk content.
2715
2715
2716 See :hg:`help templates.operators` for the list expansion syntax.
2716 See :hg:`help templates.operators` for the list expansion syntax.
2717
2717
2718 Returns 0 if a match is found, 1 otherwise.
2718 Returns 0 if a match is found, 1 otherwise.
2719 """
2719 """
2720 opts = pycompat.byteskwargs(opts)
2720 opts = pycompat.byteskwargs(opts)
2721 diff = opts.get('all') or opts.get('diff')
2721 diff = opts.get('all') or opts.get('diff')
2722 all_files = opts.get('all_files')
2722 all_files = opts.get('all_files')
2723 if diff and opts.get('all_files'):
2723 if diff and opts.get('all_files'):
2724 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2724 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2725 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2725 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2726 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2726 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2727 # experimental config: commands.grep.all-files
2727 # experimental config: commands.grep.all-files
2728 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2728 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2729 plaingrep = opts.get('all_files') and not opts.get('rev')
2729 plaingrep = opts.get('all_files') and not opts.get('rev')
2730 if plaingrep:
2730 if plaingrep:
2731 opts['rev'] = ['wdir()']
2731 opts['rev'] = ['wdir()']
2732
2732
2733 reflags = re.M
2733 reflags = re.M
2734 if opts.get('ignore_case'):
2734 if opts.get('ignore_case'):
2735 reflags |= re.I
2735 reflags |= re.I
2736 try:
2736 try:
2737 regexp = util.re.compile(pattern, reflags)
2737 regexp = util.re.compile(pattern, reflags)
2738 except re.error as inst:
2738 except re.error as inst:
2739 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2739 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2740 return 1
2740 return 1
2741 sep, eol = ':', '\n'
2741 sep, eol = ':', '\n'
2742 if opts.get('print0'):
2742 if opts.get('print0'):
2743 sep = eol = '\0'
2743 sep = eol = '\0'
2744
2744
2745 getfile = util.lrucachefunc(repo.file)
2745 getfile = util.lrucachefunc(repo.file)
2746
2746
2747 def matchlines(body):
2747 def matchlines(body):
2748 begin = 0
2748 begin = 0
2749 linenum = 0
2749 linenum = 0
2750 while begin < len(body):
2750 while begin < len(body):
2751 match = regexp.search(body, begin)
2751 match = regexp.search(body, begin)
2752 if not match:
2752 if not match:
2753 break
2753 break
2754 mstart, mend = match.span()
2754 mstart, mend = match.span()
2755 linenum += body.count('\n', begin, mstart) + 1
2755 linenum += body.count('\n', begin, mstart) + 1
2756 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2756 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2757 begin = body.find('\n', mend) + 1 or len(body) + 1
2757 begin = body.find('\n', mend) + 1 or len(body) + 1
2758 lend = begin - 1
2758 lend = begin - 1
2759 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2759 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2760
2760
2761 class linestate(object):
2761 class linestate(object):
2762 def __init__(self, line, linenum, colstart, colend):
2762 def __init__(self, line, linenum, colstart, colend):
2763 self.line = line
2763 self.line = line
2764 self.linenum = linenum
2764 self.linenum = linenum
2765 self.colstart = colstart
2765 self.colstart = colstart
2766 self.colend = colend
2766 self.colend = colend
2767
2767
2768 def __hash__(self):
2768 def __hash__(self):
2769 return hash((self.linenum, self.line))
2769 return hash((self.linenum, self.line))
2770
2770
2771 def __eq__(self, other):
2771 def __eq__(self, other):
2772 return self.line == other.line
2772 return self.line == other.line
2773
2773
2774 def findpos(self):
2774 def findpos(self):
2775 """Iterate all (start, end) indices of matches"""
2775 """Iterate all (start, end) indices of matches"""
2776 yield self.colstart, self.colend
2776 yield self.colstart, self.colend
2777 p = self.colend
2777 p = self.colend
2778 while p < len(self.line):
2778 while p < len(self.line):
2779 m = regexp.search(self.line, p)
2779 m = regexp.search(self.line, p)
2780 if not m:
2780 if not m:
2781 break
2781 break
2782 yield m.span()
2782 yield m.span()
2783 p = m.end()
2783 p = m.end()
2784
2784
2785 matches = {}
2785 matches = {}
2786 copies = {}
2786 copies = {}
2787 def grepbody(fn, rev, body):
2787 def grepbody(fn, rev, body):
2788 matches[rev].setdefault(fn, [])
2788 matches[rev].setdefault(fn, [])
2789 m = matches[rev][fn]
2789 m = matches[rev][fn]
2790 for lnum, cstart, cend, line in matchlines(body):
2790 for lnum, cstart, cend, line in matchlines(body):
2791 s = linestate(line, lnum, cstart, cend)
2791 s = linestate(line, lnum, cstart, cend)
2792 m.append(s)
2792 m.append(s)
2793
2793
2794 def difflinestates(a, b):
2794 def difflinestates(a, b):
2795 sm = difflib.SequenceMatcher(None, a, b)
2795 sm = difflib.SequenceMatcher(None, a, b)
2796 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2796 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2797 if tag == r'insert':
2797 if tag == r'insert':
2798 for i in pycompat.xrange(blo, bhi):
2798 for i in pycompat.xrange(blo, bhi):
2799 yield ('+', b[i])
2799 yield ('+', b[i])
2800 elif tag == r'delete':
2800 elif tag == r'delete':
2801 for i in pycompat.xrange(alo, ahi):
2801 for i in pycompat.xrange(alo, ahi):
2802 yield ('-', a[i])
2802 yield ('-', a[i])
2803 elif tag == r'replace':
2803 elif tag == r'replace':
2804 for i in pycompat.xrange(alo, ahi):
2804 for i in pycompat.xrange(alo, ahi):
2805 yield ('-', a[i])
2805 yield ('-', a[i])
2806 for i in pycompat.xrange(blo, bhi):
2806 for i in pycompat.xrange(blo, bhi):
2807 yield ('+', b[i])
2807 yield ('+', b[i])
2808
2808
2809 uipathfn = scmutil.getuipathfn(repo)
2809 uipathfn = scmutil.getuipathfn(repo)
2810 def display(fm, fn, ctx, pstates, states):
2810 def display(fm, fn, ctx, pstates, states):
2811 rev = scmutil.intrev(ctx)
2811 rev = scmutil.intrev(ctx)
2812 if fm.isplain():
2812 if fm.isplain():
2813 formatuser = ui.shortuser
2813 formatuser = ui.shortuser
2814 else:
2814 else:
2815 formatuser = pycompat.bytestr
2815 formatuser = pycompat.bytestr
2816 if ui.quiet:
2816 if ui.quiet:
2817 datefmt = '%Y-%m-%d'
2817 datefmt = '%Y-%m-%d'
2818 else:
2818 else:
2819 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2819 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2820 found = False
2820 found = False
2821 @util.cachefunc
2821 @util.cachefunc
2822 def binary():
2822 def binary():
2823 flog = getfile(fn)
2823 flog = getfile(fn)
2824 try:
2824 try:
2825 return stringutil.binary(flog.read(ctx.filenode(fn)))
2825 return stringutil.binary(flog.read(ctx.filenode(fn)))
2826 except error.WdirUnsupported:
2826 except error.WdirUnsupported:
2827 return ctx[fn].isbinary()
2827 return ctx[fn].isbinary()
2828
2828
2829 fieldnamemap = {'linenumber': 'lineno'}
2829 fieldnamemap = {'linenumber': 'lineno'}
2830 if diff:
2830 if diff:
2831 iter = difflinestates(pstates, states)
2831 iter = difflinestates(pstates, states)
2832 else:
2832 else:
2833 iter = [('', l) for l in states]
2833 iter = [('', l) for l in states]
2834 for change, l in iter:
2834 for change, l in iter:
2835 fm.startitem()
2835 fm.startitem()
2836 fm.context(ctx=ctx)
2836 fm.context(ctx=ctx)
2837 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2837 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2838 fm.plain(uipathfn(fn), label='grep.filename')
2838 fm.plain(uipathfn(fn), label='grep.filename')
2839
2839
2840 cols = [
2840 cols = [
2841 ('rev', '%d', rev, not plaingrep, ''),
2841 ('rev', '%d', rev, not plaingrep, ''),
2842 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2842 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2843 ]
2843 ]
2844 if diff:
2844 if diff:
2845 cols.append(
2845 cols.append(
2846 ('change', '%s', change, True,
2846 ('change', '%s', change, True,
2847 'grep.inserted ' if change == '+' else 'grep.deleted ')
2847 'grep.inserted ' if change == '+' else 'grep.deleted ')
2848 )
2848 )
2849 cols.extend([
2849 cols.extend([
2850 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2850 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2851 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2851 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2852 opts.get('date'), ''),
2852 opts.get('date'), ''),
2853 ])
2853 ])
2854 for name, fmt, data, cond, extra_label in cols:
2854 for name, fmt, data, cond, extra_label in cols:
2855 if cond:
2855 if cond:
2856 fm.plain(sep, label='grep.sep')
2856 fm.plain(sep, label='grep.sep')
2857 field = fieldnamemap.get(name, name)
2857 field = fieldnamemap.get(name, name)
2858 label = extra_label + ('grep.%s' % name)
2858 label = extra_label + ('grep.%s' % name)
2859 fm.condwrite(cond, field, fmt, data, label=label)
2859 fm.condwrite(cond, field, fmt, data, label=label)
2860 if not opts.get('files_with_matches'):
2860 if not opts.get('files_with_matches'):
2861 fm.plain(sep, label='grep.sep')
2861 fm.plain(sep, label='grep.sep')
2862 if not opts.get('text') and binary():
2862 if not opts.get('text') and binary():
2863 fm.plain(_(" Binary file matches"))
2863 fm.plain(_(" Binary file matches"))
2864 else:
2864 else:
2865 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2865 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2866 fm.plain(eol)
2866 fm.plain(eol)
2867 found = True
2867 found = True
2868 if opts.get('files_with_matches'):
2868 if opts.get('files_with_matches'):
2869 break
2869 break
2870 return found
2870 return found
2871
2871
2872 def displaymatches(fm, l):
2872 def displaymatches(fm, l):
2873 p = 0
2873 p = 0
2874 for s, e in l.findpos():
2874 for s, e in l.findpos():
2875 if p < s:
2875 if p < s:
2876 fm.startitem()
2876 fm.startitem()
2877 fm.write('text', '%s', l.line[p:s])
2877 fm.write('text', '%s', l.line[p:s])
2878 fm.data(matched=False)
2878 fm.data(matched=False)
2879 fm.startitem()
2879 fm.startitem()
2880 fm.write('text', '%s', l.line[s:e], label='grep.match')
2880 fm.write('text', '%s', l.line[s:e], label='grep.match')
2881 fm.data(matched=True)
2881 fm.data(matched=True)
2882 p = e
2882 p = e
2883 if p < len(l.line):
2883 if p < len(l.line):
2884 fm.startitem()
2884 fm.startitem()
2885 fm.write('text', '%s', l.line[p:])
2885 fm.write('text', '%s', l.line[p:])
2886 fm.data(matched=False)
2886 fm.data(matched=False)
2887 fm.end()
2887 fm.end()
2888
2888
2889 skip = set()
2889 skip = set()
2890 revfiles = {}
2890 revfiles = {}
2891 match = scmutil.match(repo[None], pats, opts)
2891 match = scmutil.match(repo[None], pats, opts)
2892 found = False
2892 found = False
2893 follow = opts.get('follow')
2893 follow = opts.get('follow')
2894
2894
2895 getrenamed = scmutil.getrenamedfn(repo)
2895 getrenamed = scmutil.getrenamedfn(repo)
2896 def prep(ctx, fns):
2896 def prep(ctx, fns):
2897 rev = ctx.rev()
2897 rev = ctx.rev()
2898 pctx = ctx.p1()
2898 pctx = ctx.p1()
2899 parent = pctx.rev()
2899 parent = pctx.rev()
2900 matches.setdefault(rev, {})
2900 matches.setdefault(rev, {})
2901 matches.setdefault(parent, {})
2901 matches.setdefault(parent, {})
2902 files = revfiles.setdefault(rev, [])
2902 files = revfiles.setdefault(rev, [])
2903 for fn in fns:
2903 for fn in fns:
2904 flog = getfile(fn)
2904 flog = getfile(fn)
2905 try:
2905 try:
2906 fnode = ctx.filenode(fn)
2906 fnode = ctx.filenode(fn)
2907 except error.LookupError:
2907 except error.LookupError:
2908 continue
2908 continue
2909
2909
2910 copy = None
2910 copy = None
2911 if follow:
2911 if follow:
2912 copy = getrenamed(fn, rev)
2912 copy = getrenamed(fn, rev)
2913 if copy:
2913 if copy:
2914 copies.setdefault(rev, {})[fn] = copy
2914 copies.setdefault(rev, {})[fn] = copy
2915 if fn in skip:
2915 if fn in skip:
2916 skip.add(copy)
2916 skip.add(copy)
2917 if fn in skip:
2917 if fn in skip:
2918 continue
2918 continue
2919 files.append(fn)
2919 files.append(fn)
2920
2920
2921 if fn not in matches[rev]:
2921 if fn not in matches[rev]:
2922 try:
2922 try:
2923 content = flog.read(fnode)
2923 content = flog.read(fnode)
2924 except error.WdirUnsupported:
2924 except error.WdirUnsupported:
2925 content = ctx[fn].data()
2925 content = ctx[fn].data()
2926 grepbody(fn, rev, content)
2926 grepbody(fn, rev, content)
2927
2927
2928 pfn = copy or fn
2928 pfn = copy or fn
2929 if pfn not in matches[parent]:
2929 if pfn not in matches[parent]:
2930 try:
2930 try:
2931 fnode = pctx.filenode(pfn)
2931 fnode = pctx.filenode(pfn)
2932 grepbody(pfn, parent, flog.read(fnode))
2932 grepbody(pfn, parent, flog.read(fnode))
2933 except error.LookupError:
2933 except error.LookupError:
2934 pass
2934 pass
2935
2935
2936 ui.pager('grep')
2936 ui.pager('grep')
2937 fm = ui.formatter('grep', opts)
2937 fm = ui.formatter('grep', opts)
2938 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2938 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2939 rev = ctx.rev()
2939 rev = ctx.rev()
2940 parent = ctx.p1().rev()
2940 parent = ctx.p1().rev()
2941 for fn in sorted(revfiles.get(rev, [])):
2941 for fn in sorted(revfiles.get(rev, [])):
2942 states = matches[rev][fn]
2942 states = matches[rev][fn]
2943 copy = copies.get(rev, {}).get(fn)
2943 copy = copies.get(rev, {}).get(fn)
2944 if fn in skip:
2944 if fn in skip:
2945 if copy:
2945 if copy:
2946 skip.add(copy)
2946 skip.add(copy)
2947 continue
2947 continue
2948 pstates = matches.get(parent, {}).get(copy or fn, [])
2948 pstates = matches.get(parent, {}).get(copy or fn, [])
2949 if pstates or states:
2949 if pstates or states:
2950 r = display(fm, fn, ctx, pstates, states)
2950 r = display(fm, fn, ctx, pstates, states)
2951 found = found or r
2951 found = found or r
2952 if r and not diff and not all_files:
2952 if r and not diff and not all_files:
2953 skip.add(fn)
2953 skip.add(fn)
2954 if copy:
2954 if copy:
2955 skip.add(copy)
2955 skip.add(copy)
2956 del revfiles[rev]
2956 del revfiles[rev]
2957 # We will keep the matches dict for the duration of the window
2957 # We will keep the matches dict for the duration of the window
2958 # clear the matches dict once the window is over
2958 # clear the matches dict once the window is over
2959 if not revfiles:
2959 if not revfiles:
2960 matches.clear()
2960 matches.clear()
2961 fm.end()
2961 fm.end()
2962
2962
2963 return not found
2963 return not found
2964
2964
2965 @command('heads',
2965 @command('heads',
2966 [('r', 'rev', '',
2966 [('r', 'rev', '',
2967 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2967 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2968 ('t', 'topo', False, _('show topological heads only')),
2968 ('t', 'topo', False, _('show topological heads only')),
2969 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2969 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2970 ('c', 'closed', False, _('show normal and closed branch heads')),
2970 ('c', 'closed', False, _('show normal and closed branch heads')),
2971 ] + templateopts,
2971 ] + templateopts,
2972 _('[-ct] [-r STARTREV] [REV]...'),
2972 _('[-ct] [-r STARTREV] [REV]...'),
2973 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
2973 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
2974 intents={INTENT_READONLY})
2974 intents={INTENT_READONLY})
2975 def heads(ui, repo, *branchrevs, **opts):
2975 def heads(ui, repo, *branchrevs, **opts):
2976 """show branch heads
2976 """show branch heads
2977
2977
2978 With no arguments, show all open branch heads in the repository.
2978 With no arguments, show all open branch heads in the repository.
2979 Branch heads are changesets that have no descendants on the
2979 Branch heads are changesets that have no descendants on the
2980 same branch. They are where development generally takes place and
2980 same branch. They are where development generally takes place and
2981 are the usual targets for update and merge operations.
2981 are the usual targets for update and merge operations.
2982
2982
2983 If one or more REVs are given, only open branch heads on the
2983 If one or more REVs are given, only open branch heads on the
2984 branches associated with the specified changesets are shown. This
2984 branches associated with the specified changesets are shown. This
2985 means that you can use :hg:`heads .` to see the heads on the
2985 means that you can use :hg:`heads .` to see the heads on the
2986 currently checked-out branch.
2986 currently checked-out branch.
2987
2987
2988 If -c/--closed is specified, also show branch heads marked closed
2988 If -c/--closed is specified, also show branch heads marked closed
2989 (see :hg:`commit --close-branch`).
2989 (see :hg:`commit --close-branch`).
2990
2990
2991 If STARTREV is specified, only those heads that are descendants of
2991 If STARTREV is specified, only those heads that are descendants of
2992 STARTREV will be displayed.
2992 STARTREV will be displayed.
2993
2993
2994 If -t/--topo is specified, named branch mechanics will be ignored and only
2994 If -t/--topo is specified, named branch mechanics will be ignored and only
2995 topological heads (changesets with no children) will be shown.
2995 topological heads (changesets with no children) will be shown.
2996
2996
2997 Returns 0 if matching heads are found, 1 if not.
2997 Returns 0 if matching heads are found, 1 if not.
2998 """
2998 """
2999
2999
3000 opts = pycompat.byteskwargs(opts)
3000 opts = pycompat.byteskwargs(opts)
3001 start = None
3001 start = None
3002 rev = opts.get('rev')
3002 rev = opts.get('rev')
3003 if rev:
3003 if rev:
3004 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3004 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3005 start = scmutil.revsingle(repo, rev, None).node()
3005 start = scmutil.revsingle(repo, rev, None).node()
3006
3006
3007 if opts.get('topo'):
3007 if opts.get('topo'):
3008 heads = [repo[h] for h in repo.heads(start)]
3008 heads = [repo[h] for h in repo.heads(start)]
3009 else:
3009 else:
3010 heads = []
3010 heads = []
3011 for branch in repo.branchmap():
3011 for branch in repo.branchmap():
3012 heads += repo.branchheads(branch, start, opts.get('closed'))
3012 heads += repo.branchheads(branch, start, opts.get('closed'))
3013 heads = [repo[h] for h in heads]
3013 heads = [repo[h] for h in heads]
3014
3014
3015 if branchrevs:
3015 if branchrevs:
3016 branches = set(repo[r].branch()
3016 branches = set(repo[r].branch()
3017 for r in scmutil.revrange(repo, branchrevs))
3017 for r in scmutil.revrange(repo, branchrevs))
3018 heads = [h for h in heads if h.branch() in branches]
3018 heads = [h for h in heads if h.branch() in branches]
3019
3019
3020 if opts.get('active') and branchrevs:
3020 if opts.get('active') and branchrevs:
3021 dagheads = repo.heads(start)
3021 dagheads = repo.heads(start)
3022 heads = [h for h in heads if h.node() in dagheads]
3022 heads = [h for h in heads if h.node() in dagheads]
3023
3023
3024 if branchrevs:
3024 if branchrevs:
3025 haveheads = set(h.branch() for h in heads)
3025 haveheads = set(h.branch() for h in heads)
3026 if branches - haveheads:
3026 if branches - haveheads:
3027 headless = ', '.join(b for b in branches - haveheads)
3027 headless = ', '.join(b for b in branches - haveheads)
3028 msg = _('no open branch heads found on branches %s')
3028 msg = _('no open branch heads found on branches %s')
3029 if opts.get('rev'):
3029 if opts.get('rev'):
3030 msg += _(' (started at %s)') % opts['rev']
3030 msg += _(' (started at %s)') % opts['rev']
3031 ui.warn((msg + '\n') % headless)
3031 ui.warn((msg + '\n') % headless)
3032
3032
3033 if not heads:
3033 if not heads:
3034 return 1
3034 return 1
3035
3035
3036 ui.pager('heads')
3036 ui.pager('heads')
3037 heads = sorted(heads, key=lambda x: -x.rev())
3037 heads = sorted(heads, key=lambda x: -x.rev())
3038 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3038 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3039 for ctx in heads:
3039 for ctx in heads:
3040 displayer.show(ctx)
3040 displayer.show(ctx)
3041 displayer.close()
3041 displayer.close()
3042
3042
3043 @command('help',
3043 @command('help',
3044 [('e', 'extension', None, _('show only help for extensions')),
3044 [('e', 'extension', None, _('show only help for extensions')),
3045 ('c', 'command', None, _('show only help for commands')),
3045 ('c', 'command', None, _('show only help for commands')),
3046 ('k', 'keyword', None, _('show topics matching keyword')),
3046 ('k', 'keyword', None, _('show topics matching keyword')),
3047 ('s', 'system', [],
3047 ('s', 'system', [],
3048 _('show help for specific platform(s)'), _('PLATFORM')),
3048 _('show help for specific platform(s)'), _('PLATFORM')),
3049 ],
3049 ],
3050 _('[-eck] [-s PLATFORM] [TOPIC]'),
3050 _('[-eck] [-s PLATFORM] [TOPIC]'),
3051 helpcategory=command.CATEGORY_HELP,
3051 helpcategory=command.CATEGORY_HELP,
3052 norepo=True,
3052 norepo=True,
3053 intents={INTENT_READONLY})
3053 intents={INTENT_READONLY})
3054 def help_(ui, name=None, **opts):
3054 def help_(ui, name=None, **opts):
3055 """show help for a given topic or a help overview
3055 """show help for a given topic or a help overview
3056
3056
3057 With no arguments, print a list of commands with short help messages.
3057 With no arguments, print a list of commands with short help messages.
3058
3058
3059 Given a topic, extension, or command name, print help for that
3059 Given a topic, extension, or command name, print help for that
3060 topic.
3060 topic.
3061
3061
3062 Returns 0 if successful.
3062 Returns 0 if successful.
3063 """
3063 """
3064
3064
3065 keep = opts.get(r'system') or []
3065 keep = opts.get(r'system') or []
3066 if len(keep) == 0:
3066 if len(keep) == 0:
3067 if pycompat.sysplatform.startswith('win'):
3067 if pycompat.sysplatform.startswith('win'):
3068 keep.append('windows')
3068 keep.append('windows')
3069 elif pycompat.sysplatform == 'OpenVMS':
3069 elif pycompat.sysplatform == 'OpenVMS':
3070 keep.append('vms')
3070 keep.append('vms')
3071 elif pycompat.sysplatform == 'plan9':
3071 elif pycompat.sysplatform == 'plan9':
3072 keep.append('plan9')
3072 keep.append('plan9')
3073 else:
3073 else:
3074 keep.append('unix')
3074 keep.append('unix')
3075 keep.append(pycompat.sysplatform.lower())
3075 keep.append(pycompat.sysplatform.lower())
3076 if ui.verbose:
3076 if ui.verbose:
3077 keep.append('verbose')
3077 keep.append('verbose')
3078
3078
3079 commands = sys.modules[__name__]
3079 commands = sys.modules[__name__]
3080 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3080 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3081 ui.pager('help')
3081 ui.pager('help')
3082 ui.write(formatted)
3082 ui.write(formatted)
3083
3083
3084
3084
3085 @command('identify|id',
3085 @command('identify|id',
3086 [('r', 'rev', '',
3086 [('r', 'rev', '',
3087 _('identify the specified revision'), _('REV')),
3087 _('identify the specified revision'), _('REV')),
3088 ('n', 'num', None, _('show local revision number')),
3088 ('n', 'num', None, _('show local revision number')),
3089 ('i', 'id', None, _('show global revision id')),
3089 ('i', 'id', None, _('show global revision id')),
3090 ('b', 'branch', None, _('show branch')),
3090 ('b', 'branch', None, _('show branch')),
3091 ('t', 'tags', None, _('show tags')),
3091 ('t', 'tags', None, _('show tags')),
3092 ('B', 'bookmarks', None, _('show bookmarks')),
3092 ('B', 'bookmarks', None, _('show bookmarks')),
3093 ] + remoteopts + formatteropts,
3093 ] + remoteopts + formatteropts,
3094 _('[-nibtB] [-r REV] [SOURCE]'),
3094 _('[-nibtB] [-r REV] [SOURCE]'),
3095 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3095 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3096 optionalrepo=True,
3096 optionalrepo=True,
3097 intents={INTENT_READONLY})
3097 intents={INTENT_READONLY})
3098 def identify(ui, repo, source=None, rev=None,
3098 def identify(ui, repo, source=None, rev=None,
3099 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3099 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3100 """identify the working directory or specified revision
3100 """identify the working directory or specified revision
3101
3101
3102 Print a summary identifying the repository state at REV using one or
3102 Print a summary identifying the repository state at REV using one or
3103 two parent hash identifiers, followed by a "+" if the working
3103 two parent hash identifiers, followed by a "+" if the working
3104 directory has uncommitted changes, the branch name (if not default),
3104 directory has uncommitted changes, the branch name (if not default),
3105 a list of tags, and a list of bookmarks.
3105 a list of tags, and a list of bookmarks.
3106
3106
3107 When REV is not given, print a summary of the current state of the
3107 When REV is not given, print a summary of the current state of the
3108 repository including the working directory. Specify -r. to get information
3108 repository including the working directory. Specify -r. to get information
3109 of the working directory parent without scanning uncommitted changes.
3109 of the working directory parent without scanning uncommitted changes.
3110
3110
3111 Specifying a path to a repository root or Mercurial bundle will
3111 Specifying a path to a repository root or Mercurial bundle will
3112 cause lookup to operate on that repository/bundle.
3112 cause lookup to operate on that repository/bundle.
3113
3113
3114 .. container:: verbose
3114 .. container:: verbose
3115
3115
3116 Template:
3116 Template:
3117
3117
3118 The following keywords are supported in addition to the common template
3118 The following keywords are supported in addition to the common template
3119 keywords and functions. See also :hg:`help templates`.
3119 keywords and functions. See also :hg:`help templates`.
3120
3120
3121 :dirty: String. Character ``+`` denoting if the working directory has
3121 :dirty: String. Character ``+`` denoting if the working directory has
3122 uncommitted changes.
3122 uncommitted changes.
3123 :id: String. One or two nodes, optionally followed by ``+``.
3123 :id: String. One or two nodes, optionally followed by ``+``.
3124 :parents: List of strings. Parent nodes of the changeset.
3124 :parents: List of strings. Parent nodes of the changeset.
3125
3125
3126 Examples:
3126 Examples:
3127
3127
3128 - generate a build identifier for the working directory::
3128 - generate a build identifier for the working directory::
3129
3129
3130 hg id --id > build-id.dat
3130 hg id --id > build-id.dat
3131
3131
3132 - find the revision corresponding to a tag::
3132 - find the revision corresponding to a tag::
3133
3133
3134 hg id -n -r 1.3
3134 hg id -n -r 1.3
3135
3135
3136 - check the most recent revision of a remote repository::
3136 - check the most recent revision of a remote repository::
3137
3137
3138 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3138 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3139
3139
3140 See :hg:`log` for generating more information about specific revisions,
3140 See :hg:`log` for generating more information about specific revisions,
3141 including full hash identifiers.
3141 including full hash identifiers.
3142
3142
3143 Returns 0 if successful.
3143 Returns 0 if successful.
3144 """
3144 """
3145
3145
3146 opts = pycompat.byteskwargs(opts)
3146 opts = pycompat.byteskwargs(opts)
3147 if not repo and not source:
3147 if not repo and not source:
3148 raise error.Abort(_("there is no Mercurial repository here "
3148 raise error.Abort(_("there is no Mercurial repository here "
3149 "(.hg not found)"))
3149 "(.hg not found)"))
3150
3150
3151 default = not (num or id or branch or tags or bookmarks)
3151 default = not (num or id or branch or tags or bookmarks)
3152 output = []
3152 output = []
3153 revs = []
3153 revs = []
3154
3154
3155 if source:
3155 if source:
3156 source, branches = hg.parseurl(ui.expandpath(source))
3156 source, branches = hg.parseurl(ui.expandpath(source))
3157 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3157 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3158 repo = peer.local()
3158 repo = peer.local()
3159 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3159 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3160
3160
3161 fm = ui.formatter('identify', opts)
3161 fm = ui.formatter('identify', opts)
3162 fm.startitem()
3162 fm.startitem()
3163
3163
3164 if not repo:
3164 if not repo:
3165 if num or branch or tags:
3165 if num or branch or tags:
3166 raise error.Abort(
3166 raise error.Abort(
3167 _("can't query remote revision number, branch, or tags"))
3167 _("can't query remote revision number, branch, or tags"))
3168 if not rev and revs:
3168 if not rev and revs:
3169 rev = revs[0]
3169 rev = revs[0]
3170 if not rev:
3170 if not rev:
3171 rev = "tip"
3171 rev = "tip"
3172
3172
3173 remoterev = peer.lookup(rev)
3173 remoterev = peer.lookup(rev)
3174 hexrev = fm.hexfunc(remoterev)
3174 hexrev = fm.hexfunc(remoterev)
3175 if default or id:
3175 if default or id:
3176 output = [hexrev]
3176 output = [hexrev]
3177 fm.data(id=hexrev)
3177 fm.data(id=hexrev)
3178
3178
3179 @util.cachefunc
3179 @util.cachefunc
3180 def getbms():
3180 def getbms():
3181 bms = []
3181 bms = []
3182
3182
3183 if 'bookmarks' in peer.listkeys('namespaces'):
3183 if 'bookmarks' in peer.listkeys('namespaces'):
3184 hexremoterev = hex(remoterev)
3184 hexremoterev = hex(remoterev)
3185 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3185 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3186 if bmr == hexremoterev]
3186 if bmr == hexremoterev]
3187
3187
3188 return sorted(bms)
3188 return sorted(bms)
3189
3189
3190 if fm.isplain():
3190 if fm.isplain():
3191 if bookmarks:
3191 if bookmarks:
3192 output.extend(getbms())
3192 output.extend(getbms())
3193 elif default and not ui.quiet:
3193 elif default and not ui.quiet:
3194 # multiple bookmarks for a single parent separated by '/'
3194 # multiple bookmarks for a single parent separated by '/'
3195 bm = '/'.join(getbms())
3195 bm = '/'.join(getbms())
3196 if bm:
3196 if bm:
3197 output.append(bm)
3197 output.append(bm)
3198 else:
3198 else:
3199 fm.data(node=hex(remoterev))
3199 fm.data(node=hex(remoterev))
3200 if bookmarks or 'bookmarks' in fm.datahint():
3200 if bookmarks or 'bookmarks' in fm.datahint():
3201 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3201 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3202 else:
3202 else:
3203 if rev:
3203 if rev:
3204 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3204 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3205 ctx = scmutil.revsingle(repo, rev, None)
3205 ctx = scmutil.revsingle(repo, rev, None)
3206
3206
3207 if ctx.rev() is None:
3207 if ctx.rev() is None:
3208 ctx = repo[None]
3208 ctx = repo[None]
3209 parents = ctx.parents()
3209 parents = ctx.parents()
3210 taglist = []
3210 taglist = []
3211 for p in parents:
3211 for p in parents:
3212 taglist.extend(p.tags())
3212 taglist.extend(p.tags())
3213
3213
3214 dirty = ""
3214 dirty = ""
3215 if ctx.dirty(missing=True, merge=False, branch=False):
3215 if ctx.dirty(missing=True, merge=False, branch=False):
3216 dirty = '+'
3216 dirty = '+'
3217 fm.data(dirty=dirty)
3217 fm.data(dirty=dirty)
3218
3218
3219 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3219 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3220 if default or id:
3220 if default or id:
3221 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3221 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3222 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3222 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3223
3223
3224 if num:
3224 if num:
3225 numoutput = ["%d" % p.rev() for p in parents]
3225 numoutput = ["%d" % p.rev() for p in parents]
3226 output.append("%s%s" % ('+'.join(numoutput), dirty))
3226 output.append("%s%s" % ('+'.join(numoutput), dirty))
3227
3227
3228 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3228 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3229 for p in parents], name='node'))
3229 for p in parents], name='node'))
3230 else:
3230 else:
3231 hexoutput = fm.hexfunc(ctx.node())
3231 hexoutput = fm.hexfunc(ctx.node())
3232 if default or id:
3232 if default or id:
3233 output = [hexoutput]
3233 output = [hexoutput]
3234 fm.data(id=hexoutput)
3234 fm.data(id=hexoutput)
3235
3235
3236 if num:
3236 if num:
3237 output.append(pycompat.bytestr(ctx.rev()))
3237 output.append(pycompat.bytestr(ctx.rev()))
3238 taglist = ctx.tags()
3238 taglist = ctx.tags()
3239
3239
3240 if default and not ui.quiet:
3240 if default and not ui.quiet:
3241 b = ctx.branch()
3241 b = ctx.branch()
3242 if b != 'default':
3242 if b != 'default':
3243 output.append("(%s)" % b)
3243 output.append("(%s)" % b)
3244
3244
3245 # multiple tags for a single parent separated by '/'
3245 # multiple tags for a single parent separated by '/'
3246 t = '/'.join(taglist)
3246 t = '/'.join(taglist)
3247 if t:
3247 if t:
3248 output.append(t)
3248 output.append(t)
3249
3249
3250 # multiple bookmarks for a single parent separated by '/'
3250 # multiple bookmarks for a single parent separated by '/'
3251 bm = '/'.join(ctx.bookmarks())
3251 bm = '/'.join(ctx.bookmarks())
3252 if bm:
3252 if bm:
3253 output.append(bm)
3253 output.append(bm)
3254 else:
3254 else:
3255 if branch:
3255 if branch:
3256 output.append(ctx.branch())
3256 output.append(ctx.branch())
3257
3257
3258 if tags:
3258 if tags:
3259 output.extend(taglist)
3259 output.extend(taglist)
3260
3260
3261 if bookmarks:
3261 if bookmarks:
3262 output.extend(ctx.bookmarks())
3262 output.extend(ctx.bookmarks())
3263
3263
3264 fm.data(node=ctx.hex())
3264 fm.data(node=ctx.hex())
3265 fm.data(branch=ctx.branch())
3265 fm.data(branch=ctx.branch())
3266 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3266 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3267 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3267 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3268 fm.context(ctx=ctx)
3268 fm.context(ctx=ctx)
3269
3269
3270 fm.plain("%s\n" % ' '.join(output))
3270 fm.plain("%s\n" % ' '.join(output))
3271 fm.end()
3271 fm.end()
3272
3272
3273 @command('import|patch',
3273 @command('import|patch',
3274 [('p', 'strip', 1,
3274 [('p', 'strip', 1,
3275 _('directory strip option for patch. This has the same '
3275 _('directory strip option for patch. This has the same '
3276 'meaning as the corresponding patch option'), _('NUM')),
3276 'meaning as the corresponding patch option'), _('NUM')),
3277 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3277 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3278 ('e', 'edit', False, _('invoke editor on commit messages')),
3278 ('e', 'edit', False, _('invoke editor on commit messages')),
3279 ('f', 'force', None,
3279 ('f', 'force', None,
3280 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3280 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3281 ('', 'no-commit', None,
3281 ('', 'no-commit', None,
3282 _("don't commit, just update the working directory")),
3282 _("don't commit, just update the working directory")),
3283 ('', 'bypass', None,
3283 ('', 'bypass', None,
3284 _("apply patch without touching the working directory")),
3284 _("apply patch without touching the working directory")),
3285 ('', 'partial', None,
3285 ('', 'partial', None,
3286 _('commit even if some hunks fail')),
3286 _('commit even if some hunks fail')),
3287 ('', 'exact', None,
3287 ('', 'exact', None,
3288 _('abort if patch would apply lossily')),
3288 _('abort if patch would apply lossily')),
3289 ('', 'prefix', '',
3289 ('', 'prefix', '',
3290 _('apply patch to subdirectory'), _('DIR')),
3290 _('apply patch to subdirectory'), _('DIR')),
3291 ('', 'import-branch', None,
3291 ('', 'import-branch', None,
3292 _('use any branch information in patch (implied by --exact)'))] +
3292 _('use any branch information in patch (implied by --exact)'))] +
3293 commitopts + commitopts2 + similarityopts,
3293 commitopts + commitopts2 + similarityopts,
3294 _('[OPTION]... PATCH...'),
3294 _('[OPTION]... PATCH...'),
3295 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3295 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3296 def import_(ui, repo, patch1=None, *patches, **opts):
3296 def import_(ui, repo, patch1=None, *patches, **opts):
3297 """import an ordered set of patches
3297 """import an ordered set of patches
3298
3298
3299 Import a list of patches and commit them individually (unless
3299 Import a list of patches and commit them individually (unless
3300 --no-commit is specified).
3300 --no-commit is specified).
3301
3301
3302 To read a patch from standard input (stdin), use "-" as the patch
3302 To read a patch from standard input (stdin), use "-" as the patch
3303 name. If a URL is specified, the patch will be downloaded from
3303 name. If a URL is specified, the patch will be downloaded from
3304 there.
3304 there.
3305
3305
3306 Import first applies changes to the working directory (unless
3306 Import first applies changes to the working directory (unless
3307 --bypass is specified), import will abort if there are outstanding
3307 --bypass is specified), import will abort if there are outstanding
3308 changes.
3308 changes.
3309
3309
3310 Use --bypass to apply and commit patches directly to the
3310 Use --bypass to apply and commit patches directly to the
3311 repository, without affecting the working directory. Without
3311 repository, without affecting the working directory. Without
3312 --exact, patches will be applied on top of the working directory
3312 --exact, patches will be applied on top of the working directory
3313 parent revision.
3313 parent revision.
3314
3314
3315 You can import a patch straight from a mail message. Even patches
3315 You can import a patch straight from a mail message. Even patches
3316 as attachments work (to use the body part, it must have type
3316 as attachments work (to use the body part, it must have type
3317 text/plain or text/x-patch). From and Subject headers of email
3317 text/plain or text/x-patch). From and Subject headers of email
3318 message are used as default committer and commit message. All
3318 message are used as default committer and commit message. All
3319 text/plain body parts before first diff are added to the commit
3319 text/plain body parts before first diff are added to the commit
3320 message.
3320 message.
3321
3321
3322 If the imported patch was generated by :hg:`export`, user and
3322 If the imported patch was generated by :hg:`export`, user and
3323 description from patch override values from message headers and
3323 description from patch override values from message headers and
3324 body. Values given on command line with -m/--message and -u/--user
3324 body. Values given on command line with -m/--message and -u/--user
3325 override these.
3325 override these.
3326
3326
3327 If --exact is specified, import will set the working directory to
3327 If --exact is specified, import will set the working directory to
3328 the parent of each patch before applying it, and will abort if the
3328 the parent of each patch before applying it, and will abort if the
3329 resulting changeset has a different ID than the one recorded in
3329 resulting changeset has a different ID than the one recorded in
3330 the patch. This will guard against various ways that portable
3330 the patch. This will guard against various ways that portable
3331 patch formats and mail systems might fail to transfer Mercurial
3331 patch formats and mail systems might fail to transfer Mercurial
3332 data or metadata. See :hg:`bundle` for lossless transmission.
3332 data or metadata. See :hg:`bundle` for lossless transmission.
3333
3333
3334 Use --partial to ensure a changeset will be created from the patch
3334 Use --partial to ensure a changeset will be created from the patch
3335 even if some hunks fail to apply. Hunks that fail to apply will be
3335 even if some hunks fail to apply. Hunks that fail to apply will be
3336 written to a <target-file>.rej file. Conflicts can then be resolved
3336 written to a <target-file>.rej file. Conflicts can then be resolved
3337 by hand before :hg:`commit --amend` is run to update the created
3337 by hand before :hg:`commit --amend` is run to update the created
3338 changeset. This flag exists to let people import patches that
3338 changeset. This flag exists to let people import patches that
3339 partially apply without losing the associated metadata (author,
3339 partially apply without losing the associated metadata (author,
3340 date, description, ...).
3340 date, description, ...).
3341
3341
3342 .. note::
3342 .. note::
3343
3343
3344 When no hunks apply cleanly, :hg:`import --partial` will create
3344 When no hunks apply cleanly, :hg:`import --partial` will create
3345 an empty changeset, importing only the patch metadata.
3345 an empty changeset, importing only the patch metadata.
3346
3346
3347 With -s/--similarity, hg will attempt to discover renames and
3347 With -s/--similarity, hg will attempt to discover renames and
3348 copies in the patch in the same way as :hg:`addremove`.
3348 copies in the patch in the same way as :hg:`addremove`.
3349
3349
3350 It is possible to use external patch programs to perform the patch
3350 It is possible to use external patch programs to perform the patch
3351 by setting the ``ui.patch`` configuration option. For the default
3351 by setting the ``ui.patch`` configuration option. For the default
3352 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3352 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3353 See :hg:`help config` for more information about configuration
3353 See :hg:`help config` for more information about configuration
3354 files and how to use these options.
3354 files and how to use these options.
3355
3355
3356 See :hg:`help dates` for a list of formats valid for -d/--date.
3356 See :hg:`help dates` for a list of formats valid for -d/--date.
3357
3357
3358 .. container:: verbose
3358 .. container:: verbose
3359
3359
3360 Examples:
3360 Examples:
3361
3361
3362 - import a traditional patch from a website and detect renames::
3362 - import a traditional patch from a website and detect renames::
3363
3363
3364 hg import -s 80 http://example.com/bugfix.patch
3364 hg import -s 80 http://example.com/bugfix.patch
3365
3365
3366 - import a changeset from an hgweb server::
3366 - import a changeset from an hgweb server::
3367
3367
3368 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3368 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3369
3369
3370 - import all the patches in an Unix-style mbox::
3370 - import all the patches in an Unix-style mbox::
3371
3371
3372 hg import incoming-patches.mbox
3372 hg import incoming-patches.mbox
3373
3373
3374 - import patches from stdin::
3374 - import patches from stdin::
3375
3375
3376 hg import -
3376 hg import -
3377
3377
3378 - attempt to exactly restore an exported changeset (not always
3378 - attempt to exactly restore an exported changeset (not always
3379 possible)::
3379 possible)::
3380
3380
3381 hg import --exact proposed-fix.patch
3381 hg import --exact proposed-fix.patch
3382
3382
3383 - use an external tool to apply a patch which is too fuzzy for
3383 - use an external tool to apply a patch which is too fuzzy for
3384 the default internal tool.
3384 the default internal tool.
3385
3385
3386 hg import --config ui.patch="patch --merge" fuzzy.patch
3386 hg import --config ui.patch="patch --merge" fuzzy.patch
3387
3387
3388 - change the default fuzzing from 2 to a less strict 7
3388 - change the default fuzzing from 2 to a less strict 7
3389
3389
3390 hg import --config ui.fuzz=7 fuzz.patch
3390 hg import --config ui.fuzz=7 fuzz.patch
3391
3391
3392 Returns 0 on success, 1 on partial success (see --partial).
3392 Returns 0 on success, 1 on partial success (see --partial).
3393 """
3393 """
3394
3394
3395 opts = pycompat.byteskwargs(opts)
3395 opts = pycompat.byteskwargs(opts)
3396 if not patch1:
3396 if not patch1:
3397 raise error.Abort(_('need at least one patch to import'))
3397 raise error.Abort(_('need at least one patch to import'))
3398
3398
3399 patches = (patch1,) + patches
3399 patches = (patch1,) + patches
3400
3400
3401 date = opts.get('date')
3401 date = opts.get('date')
3402 if date:
3402 if date:
3403 opts['date'] = dateutil.parsedate(date)
3403 opts['date'] = dateutil.parsedate(date)
3404
3404
3405 exact = opts.get('exact')
3405 exact = opts.get('exact')
3406 update = not opts.get('bypass')
3406 update = not opts.get('bypass')
3407 if not update and opts.get('no_commit'):
3407 if not update and opts.get('no_commit'):
3408 raise error.Abort(_('cannot use --no-commit with --bypass'))
3408 raise error.Abort(_('cannot use --no-commit with --bypass'))
3409 try:
3409 try:
3410 sim = float(opts.get('similarity') or 0)
3410 sim = float(opts.get('similarity') or 0)
3411 except ValueError:
3411 except ValueError:
3412 raise error.Abort(_('similarity must be a number'))
3412 raise error.Abort(_('similarity must be a number'))
3413 if sim < 0 or sim > 100:
3413 if sim < 0 or sim > 100:
3414 raise error.Abort(_('similarity must be between 0 and 100'))
3414 raise error.Abort(_('similarity must be between 0 and 100'))
3415 if sim and not update:
3415 if sim and not update:
3416 raise error.Abort(_('cannot use --similarity with --bypass'))
3416 raise error.Abort(_('cannot use --similarity with --bypass'))
3417 if exact:
3417 if exact:
3418 if opts.get('edit'):
3418 if opts.get('edit'):
3419 raise error.Abort(_('cannot use --exact with --edit'))
3419 raise error.Abort(_('cannot use --exact with --edit'))
3420 if opts.get('prefix'):
3420 if opts.get('prefix'):
3421 raise error.Abort(_('cannot use --exact with --prefix'))
3421 raise error.Abort(_('cannot use --exact with --prefix'))
3422
3422
3423 base = opts["base"]
3423 base = opts["base"]
3424 msgs = []
3424 msgs = []
3425 ret = 0
3425 ret = 0
3426
3426
3427 with repo.wlock():
3427 with repo.wlock():
3428 if update:
3428 if update:
3429 cmdutil.checkunfinished(repo)
3429 cmdutil.checkunfinished(repo)
3430 if (exact or not opts.get('force')):
3430 if (exact or not opts.get('force')):
3431 cmdutil.bailifchanged(repo)
3431 cmdutil.bailifchanged(repo)
3432
3432
3433 if not opts.get('no_commit'):
3433 if not opts.get('no_commit'):
3434 lock = repo.lock
3434 lock = repo.lock
3435 tr = lambda: repo.transaction('import')
3435 tr = lambda: repo.transaction('import')
3436 dsguard = util.nullcontextmanager
3436 dsguard = util.nullcontextmanager
3437 else:
3437 else:
3438 lock = util.nullcontextmanager
3438 lock = util.nullcontextmanager
3439 tr = util.nullcontextmanager
3439 tr = util.nullcontextmanager
3440 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3440 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3441 with lock(), tr(), dsguard():
3441 with lock(), tr(), dsguard():
3442 parents = repo[None].parents()
3442 parents = repo[None].parents()
3443 for patchurl in patches:
3443 for patchurl in patches:
3444 if patchurl == '-':
3444 if patchurl == '-':
3445 ui.status(_('applying patch from stdin\n'))
3445 ui.status(_('applying patch from stdin\n'))
3446 patchfile = ui.fin
3446 patchfile = ui.fin
3447 patchurl = 'stdin' # for error message
3447 patchurl = 'stdin' # for error message
3448 else:
3448 else:
3449 patchurl = os.path.join(base, patchurl)
3449 patchurl = os.path.join(base, patchurl)
3450 ui.status(_('applying %s\n') % patchurl)
3450 ui.status(_('applying %s\n') % patchurl)
3451 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3451 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3452
3452
3453 haspatch = False
3453 haspatch = False
3454 for hunk in patch.split(patchfile):
3454 for hunk in patch.split(patchfile):
3455 with patch.extract(ui, hunk) as patchdata:
3455 with patch.extract(ui, hunk) as patchdata:
3456 msg, node, rej = cmdutil.tryimportone(ui, repo,
3456 msg, node, rej = cmdutil.tryimportone(ui, repo,
3457 patchdata,
3457 patchdata,
3458 parents, opts,
3458 parents, opts,
3459 msgs, hg.clean)
3459 msgs, hg.clean)
3460 if msg:
3460 if msg:
3461 haspatch = True
3461 haspatch = True
3462 ui.note(msg + '\n')
3462 ui.note(msg + '\n')
3463 if update or exact:
3463 if update or exact:
3464 parents = repo[None].parents()
3464 parents = repo[None].parents()
3465 else:
3465 else:
3466 parents = [repo[node]]
3466 parents = [repo[node]]
3467 if rej:
3467 if rej:
3468 ui.write_err(_("patch applied partially\n"))
3468 ui.write_err(_("patch applied partially\n"))
3469 ui.write_err(_("(fix the .rej files and run "
3469 ui.write_err(_("(fix the .rej files and run "
3470 "`hg commit --amend`)\n"))
3470 "`hg commit --amend`)\n"))
3471 ret = 1
3471 ret = 1
3472 break
3472 break
3473
3473
3474 if not haspatch:
3474 if not haspatch:
3475 raise error.Abort(_('%s: no diffs found') % patchurl)
3475 raise error.Abort(_('%s: no diffs found') % patchurl)
3476
3476
3477 if msgs:
3477 if msgs:
3478 repo.savecommitmessage('\n* * *\n'.join(msgs))
3478 repo.savecommitmessage('\n* * *\n'.join(msgs))
3479 return ret
3479 return ret
3480
3480
3481 @command('incoming|in',
3481 @command('incoming|in',
3482 [('f', 'force', None,
3482 [('f', 'force', None,
3483 _('run even if remote repository is unrelated')),
3483 _('run even if remote repository is unrelated')),
3484 ('n', 'newest-first', None, _('show newest record first')),
3484 ('n', 'newest-first', None, _('show newest record first')),
3485 ('', 'bundle', '',
3485 ('', 'bundle', '',
3486 _('file to store the bundles into'), _('FILE')),
3486 _('file to store the bundles into'), _('FILE')),
3487 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3487 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3488 ('B', 'bookmarks', False, _("compare bookmarks")),
3488 ('B', 'bookmarks', False, _("compare bookmarks")),
3489 ('b', 'branch', [],
3489 ('b', 'branch', [],
3490 _('a specific branch you would like to pull'), _('BRANCH')),
3490 _('a specific branch you would like to pull'), _('BRANCH')),
3491 ] + logopts + remoteopts + subrepoopts,
3491 ] + logopts + remoteopts + subrepoopts,
3492 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3492 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3493 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3493 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3494 def incoming(ui, repo, source="default", **opts):
3494 def incoming(ui, repo, source="default", **opts):
3495 """show new changesets found in source
3495 """show new changesets found in source
3496
3496
3497 Show new changesets found in the specified path/URL or the default
3497 Show new changesets found in the specified path/URL or the default
3498 pull location. These are the changesets that would have been pulled
3498 pull location. These are the changesets that would have been pulled
3499 by :hg:`pull` at the time you issued this command.
3499 by :hg:`pull` at the time you issued this command.
3500
3500
3501 See pull for valid source format details.
3501 See pull for valid source format details.
3502
3502
3503 .. container:: verbose
3503 .. container:: verbose
3504
3504
3505 With -B/--bookmarks, the result of bookmark comparison between
3505 With -B/--bookmarks, the result of bookmark comparison between
3506 local and remote repositories is displayed. With -v/--verbose,
3506 local and remote repositories is displayed. With -v/--verbose,
3507 status is also displayed for each bookmark like below::
3507 status is also displayed for each bookmark like below::
3508
3508
3509 BM1 01234567890a added
3509 BM1 01234567890a added
3510 BM2 1234567890ab advanced
3510 BM2 1234567890ab advanced
3511 BM3 234567890abc diverged
3511 BM3 234567890abc diverged
3512 BM4 34567890abcd changed
3512 BM4 34567890abcd changed
3513
3513
3514 The action taken locally when pulling depends on the
3514 The action taken locally when pulling depends on the
3515 status of each bookmark:
3515 status of each bookmark:
3516
3516
3517 :``added``: pull will create it
3517 :``added``: pull will create it
3518 :``advanced``: pull will update it
3518 :``advanced``: pull will update it
3519 :``diverged``: pull will create a divergent bookmark
3519 :``diverged``: pull will create a divergent bookmark
3520 :``changed``: result depends on remote changesets
3520 :``changed``: result depends on remote changesets
3521
3521
3522 From the point of view of pulling behavior, bookmark
3522 From the point of view of pulling behavior, bookmark
3523 existing only in the remote repository are treated as ``added``,
3523 existing only in the remote repository are treated as ``added``,
3524 even if it is in fact locally deleted.
3524 even if it is in fact locally deleted.
3525
3525
3526 .. container:: verbose
3526 .. container:: verbose
3527
3527
3528 For remote repository, using --bundle avoids downloading the
3528 For remote repository, using --bundle avoids downloading the
3529 changesets twice if the incoming is followed by a pull.
3529 changesets twice if the incoming is followed by a pull.
3530
3530
3531 Examples:
3531 Examples:
3532
3532
3533 - show incoming changes with patches and full description::
3533 - show incoming changes with patches and full description::
3534
3534
3535 hg incoming -vp
3535 hg incoming -vp
3536
3536
3537 - show incoming changes excluding merges, store a bundle::
3537 - show incoming changes excluding merges, store a bundle::
3538
3538
3539 hg in -vpM --bundle incoming.hg
3539 hg in -vpM --bundle incoming.hg
3540 hg pull incoming.hg
3540 hg pull incoming.hg
3541
3541
3542 - briefly list changes inside a bundle::
3542 - briefly list changes inside a bundle::
3543
3543
3544 hg in changes.hg -T "{desc|firstline}\\n"
3544 hg in changes.hg -T "{desc|firstline}\\n"
3545
3545
3546 Returns 0 if there are incoming changes, 1 otherwise.
3546 Returns 0 if there are incoming changes, 1 otherwise.
3547 """
3547 """
3548 opts = pycompat.byteskwargs(opts)
3548 opts = pycompat.byteskwargs(opts)
3549 if opts.get('graph'):
3549 if opts.get('graph'):
3550 logcmdutil.checkunsupportedgraphflags([], opts)
3550 logcmdutil.checkunsupportedgraphflags([], opts)
3551 def display(other, chlist, displayer):
3551 def display(other, chlist, displayer):
3552 revdag = logcmdutil.graphrevs(other, chlist, opts)
3552 revdag = logcmdutil.graphrevs(other, chlist, opts)
3553 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3553 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3554 graphmod.asciiedges)
3554 graphmod.asciiedges)
3555
3555
3556 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3556 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3557 return 0
3557 return 0
3558
3558
3559 if opts.get('bundle') and opts.get('subrepos'):
3559 if opts.get('bundle') and opts.get('subrepos'):
3560 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3560 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3561
3561
3562 if opts.get('bookmarks'):
3562 if opts.get('bookmarks'):
3563 source, branches = hg.parseurl(ui.expandpath(source),
3563 source, branches = hg.parseurl(ui.expandpath(source),
3564 opts.get('branch'))
3564 opts.get('branch'))
3565 other = hg.peer(repo, opts, source)
3565 other = hg.peer(repo, opts, source)
3566 if 'bookmarks' not in other.listkeys('namespaces'):
3566 if 'bookmarks' not in other.listkeys('namespaces'):
3567 ui.warn(_("remote doesn't support bookmarks\n"))
3567 ui.warn(_("remote doesn't support bookmarks\n"))
3568 return 0
3568 return 0
3569 ui.pager('incoming')
3569 ui.pager('incoming')
3570 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3570 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3571 return bookmarks.incoming(ui, repo, other)
3571 return bookmarks.incoming(ui, repo, other)
3572
3572
3573 repo._subtoppath = ui.expandpath(source)
3573 repo._subtoppath = ui.expandpath(source)
3574 try:
3574 try:
3575 return hg.incoming(ui, repo, source, opts)
3575 return hg.incoming(ui, repo, source, opts)
3576 finally:
3576 finally:
3577 del repo._subtoppath
3577 del repo._subtoppath
3578
3578
3579
3579
3580 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3580 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3581 helpcategory=command.CATEGORY_REPO_CREATION,
3581 helpcategory=command.CATEGORY_REPO_CREATION,
3582 helpbasic=True, norepo=True)
3582 helpbasic=True, norepo=True)
3583 def init(ui, dest=".", **opts):
3583 def init(ui, dest=".", **opts):
3584 """create a new repository in the given directory
3584 """create a new repository in the given directory
3585
3585
3586 Initialize a new repository in the given directory. If the given
3586 Initialize a new repository in the given directory. If the given
3587 directory does not exist, it will be created.
3587 directory does not exist, it will be created.
3588
3588
3589 If no directory is given, the current directory is used.
3589 If no directory is given, the current directory is used.
3590
3590
3591 It is possible to specify an ``ssh://`` URL as the destination.
3591 It is possible to specify an ``ssh://`` URL as the destination.
3592 See :hg:`help urls` for more information.
3592 See :hg:`help urls` for more information.
3593
3593
3594 Returns 0 on success.
3594 Returns 0 on success.
3595 """
3595 """
3596 opts = pycompat.byteskwargs(opts)
3596 opts = pycompat.byteskwargs(opts)
3597 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3597 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3598
3598
3599 @command('locate',
3599 @command('locate',
3600 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3600 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3601 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3601 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3602 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3602 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3603 ] + walkopts,
3603 ] + walkopts,
3604 _('[OPTION]... [PATTERN]...'),
3604 _('[OPTION]... [PATTERN]...'),
3605 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3605 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3606 def locate(ui, repo, *pats, **opts):
3606 def locate(ui, repo, *pats, **opts):
3607 """locate files matching specific patterns (DEPRECATED)
3607 """locate files matching specific patterns (DEPRECATED)
3608
3608
3609 Print files under Mercurial control in the working directory whose
3609 Print files under Mercurial control in the working directory whose
3610 names match the given patterns.
3610 names match the given patterns.
3611
3611
3612 By default, this command searches all directories in the working
3612 By default, this command searches all directories in the working
3613 directory. To search just the current directory and its
3613 directory. To search just the current directory and its
3614 subdirectories, use "--include .".
3614 subdirectories, use "--include .".
3615
3615
3616 If no patterns are given to match, this command prints the names
3616 If no patterns are given to match, this command prints the names
3617 of all files under Mercurial control in the working directory.
3617 of all files under Mercurial control in the working directory.
3618
3618
3619 If you want to feed the output of this command into the "xargs"
3619 If you want to feed the output of this command into the "xargs"
3620 command, use the -0 option to both this command and "xargs". This
3620 command, use the -0 option to both this command and "xargs". This
3621 will avoid the problem of "xargs" treating single filenames that
3621 will avoid the problem of "xargs" treating single filenames that
3622 contain whitespace as multiple filenames.
3622 contain whitespace as multiple filenames.
3623
3623
3624 See :hg:`help files` for a more versatile command.
3624 See :hg:`help files` for a more versatile command.
3625
3625
3626 Returns 0 if a match is found, 1 otherwise.
3626 Returns 0 if a match is found, 1 otherwise.
3627 """
3627 """
3628 opts = pycompat.byteskwargs(opts)
3628 opts = pycompat.byteskwargs(opts)
3629 if opts.get('print0'):
3629 if opts.get('print0'):
3630 end = '\0'
3630 end = '\0'
3631 else:
3631 else:
3632 end = '\n'
3632 end = '\n'
3633 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3633 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3634
3634
3635 ret = 1
3635 ret = 1
3636 m = scmutil.match(ctx, pats, opts, default='relglob',
3636 m = scmutil.match(ctx, pats, opts, default='relglob',
3637 badfn=lambda x, y: False)
3637 badfn=lambda x, y: False)
3638
3638
3639 ui.pager('locate')
3639 ui.pager('locate')
3640 if ctx.rev() is None:
3640 if ctx.rev() is None:
3641 # When run on the working copy, "locate" includes removed files, so
3641 # When run on the working copy, "locate" includes removed files, so
3642 # we get the list of files from the dirstate.
3642 # we get the list of files from the dirstate.
3643 filesgen = sorted(repo.dirstate.matches(m))
3643 filesgen = sorted(repo.dirstate.matches(m))
3644 else:
3644 else:
3645 filesgen = ctx.matches(m)
3645 filesgen = ctx.matches(m)
3646 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3646 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3647 for abs in filesgen:
3647 for abs in filesgen:
3648 if opts.get('fullpath'):
3648 if opts.get('fullpath'):
3649 ui.write(repo.wjoin(abs), end)
3649 ui.write(repo.wjoin(abs), end)
3650 else:
3650 else:
3651 ui.write(uipathfn(abs), end)
3651 ui.write(uipathfn(abs), end)
3652 ret = 0
3652 ret = 0
3653
3653
3654 return ret
3654 return ret
3655
3655
3656 @command('log|history',
3656 @command('log|history',
3657 [('f', 'follow', None,
3657 [('f', 'follow', None,
3658 _('follow changeset history, or file history across copies and renames')),
3658 _('follow changeset history, or file history across copies and renames')),
3659 ('', 'follow-first', None,
3659 ('', 'follow-first', None,
3660 _('only follow the first parent of merge changesets (DEPRECATED)')),
3660 _('only follow the first parent of merge changesets (DEPRECATED)')),
3661 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3661 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3662 ('C', 'copies', None, _('show copied files')),
3662 ('C', 'copies', None, _('show copied files')),
3663 ('k', 'keyword', [],
3663 ('k', 'keyword', [],
3664 _('do case-insensitive search for a given text'), _('TEXT')),
3664 _('do case-insensitive search for a given text'), _('TEXT')),
3665 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3665 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3666 ('L', 'line-range', [],
3666 ('L', 'line-range', [],
3667 _('follow line range of specified file (EXPERIMENTAL)'),
3667 _('follow line range of specified file (EXPERIMENTAL)'),
3668 _('FILE,RANGE')),
3668 _('FILE,RANGE')),
3669 ('', 'removed', None, _('include revisions where files were removed')),
3669 ('', 'removed', None, _('include revisions where files were removed')),
3670 ('m', 'only-merges', None,
3670 ('m', 'only-merges', None,
3671 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3671 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3672 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3672 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3673 ('', 'only-branch', [],
3673 ('', 'only-branch', [],
3674 _('show only changesets within the given named branch (DEPRECATED)'),
3674 _('show only changesets within the given named branch (DEPRECATED)'),
3675 _('BRANCH')),
3675 _('BRANCH')),
3676 ('b', 'branch', [],
3676 ('b', 'branch', [],
3677 _('show changesets within the given named branch'), _('BRANCH')),
3677 _('show changesets within the given named branch'), _('BRANCH')),
3678 ('P', 'prune', [],
3678 ('P', 'prune', [],
3679 _('do not display revision or any of its ancestors'), _('REV')),
3679 _('do not display revision or any of its ancestors'), _('REV')),
3680 ] + logopts + walkopts,
3680 ] + logopts + walkopts,
3681 _('[OPTION]... [FILE]'),
3681 _('[OPTION]... [FILE]'),
3682 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3682 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3683 helpbasic=True, inferrepo=True,
3683 helpbasic=True, inferrepo=True,
3684 intents={INTENT_READONLY})
3684 intents={INTENT_READONLY})
3685 def log(ui, repo, *pats, **opts):
3685 def log(ui, repo, *pats, **opts):
3686 """show revision history of entire repository or files
3686 """show revision history of entire repository or files
3687
3687
3688 Print the revision history of the specified files or the entire
3688 Print the revision history of the specified files or the entire
3689 project.
3689 project.
3690
3690
3691 If no revision range is specified, the default is ``tip:0`` unless
3691 If no revision range is specified, the default is ``tip:0`` unless
3692 --follow is set, in which case the working directory parent is
3692 --follow is set, in which case the working directory parent is
3693 used as the starting revision.
3693 used as the starting revision.
3694
3694
3695 File history is shown without following rename or copy history of
3695 File history is shown without following rename or copy history of
3696 files. Use -f/--follow with a filename to follow history across
3696 files. Use -f/--follow with a filename to follow history across
3697 renames and copies. --follow without a filename will only show
3697 renames and copies. --follow without a filename will only show
3698 ancestors of the starting revision.
3698 ancestors of the starting revision.
3699
3699
3700 By default this command prints revision number and changeset id,
3700 By default this command prints revision number and changeset id,
3701 tags, non-trivial parents, user, date and time, and a summary for
3701 tags, non-trivial parents, user, date and time, and a summary for
3702 each commit. When the -v/--verbose switch is used, the list of
3702 each commit. When the -v/--verbose switch is used, the list of
3703 changed files and full commit message are shown.
3703 changed files and full commit message are shown.
3704
3704
3705 With --graph the revisions are shown as an ASCII art DAG with the most
3705 With --graph the revisions are shown as an ASCII art DAG with the most
3706 recent changeset at the top.
3706 recent changeset at the top.
3707 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3707 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3708 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3708 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3709 changeset from the lines below is a parent of the 'o' merge on the same
3709 changeset from the lines below is a parent of the 'o' merge on the same
3710 line.
3710 line.
3711 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3711 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3712 of a '|' indicates one or more revisions in a path are omitted.
3712 of a '|' indicates one or more revisions in a path are omitted.
3713
3713
3714 .. container:: verbose
3714 .. container:: verbose
3715
3715
3716 Use -L/--line-range FILE,M:N options to follow the history of lines
3716 Use -L/--line-range FILE,M:N options to follow the history of lines
3717 from M to N in FILE. With -p/--patch only diff hunks affecting
3717 from M to N in FILE. With -p/--patch only diff hunks affecting
3718 specified line range will be shown. This option requires --follow;
3718 specified line range will be shown. This option requires --follow;
3719 it can be specified multiple times. Currently, this option is not
3719 it can be specified multiple times. Currently, this option is not
3720 compatible with --graph. This option is experimental.
3720 compatible with --graph. This option is experimental.
3721
3721
3722 .. note::
3722 .. note::
3723
3723
3724 :hg:`log --patch` may generate unexpected diff output for merge
3724 :hg:`log --patch` may generate unexpected diff output for merge
3725 changesets, as it will only compare the merge changeset against
3725 changesets, as it will only compare the merge changeset against
3726 its first parent. Also, only files different from BOTH parents
3726 its first parent. Also, only files different from BOTH parents
3727 will appear in files:.
3727 will appear in files:.
3728
3728
3729 .. note::
3729 .. note::
3730
3730
3731 For performance reasons, :hg:`log FILE` may omit duplicate changes
3731 For performance reasons, :hg:`log FILE` may omit duplicate changes
3732 made on branches and will not show removals or mode changes. To
3732 made on branches and will not show removals or mode changes. To
3733 see all such changes, use the --removed switch.
3733 see all such changes, use the --removed switch.
3734
3734
3735 .. container:: verbose
3735 .. container:: verbose
3736
3736
3737 .. note::
3737 .. note::
3738
3738
3739 The history resulting from -L/--line-range options depends on diff
3739 The history resulting from -L/--line-range options depends on diff
3740 options; for instance if white-spaces are ignored, respective changes
3740 options; for instance if white-spaces are ignored, respective changes
3741 with only white-spaces in specified line range will not be listed.
3741 with only white-spaces in specified line range will not be listed.
3742
3742
3743 .. container:: verbose
3743 .. container:: verbose
3744
3744
3745 Some examples:
3745 Some examples:
3746
3746
3747 - changesets with full descriptions and file lists::
3747 - changesets with full descriptions and file lists::
3748
3748
3749 hg log -v
3749 hg log -v
3750
3750
3751 - changesets ancestral to the working directory::
3751 - changesets ancestral to the working directory::
3752
3752
3753 hg log -f
3753 hg log -f
3754
3754
3755 - last 10 commits on the current branch::
3755 - last 10 commits on the current branch::
3756
3756
3757 hg log -l 10 -b .
3757 hg log -l 10 -b .
3758
3758
3759 - changesets showing all modifications of a file, including removals::
3759 - changesets showing all modifications of a file, including removals::
3760
3760
3761 hg log --removed file.c
3761 hg log --removed file.c
3762
3762
3763 - all changesets that touch a directory, with diffs, excluding merges::
3763 - all changesets that touch a directory, with diffs, excluding merges::
3764
3764
3765 hg log -Mp lib/
3765 hg log -Mp lib/
3766
3766
3767 - all revision numbers that match a keyword::
3767 - all revision numbers that match a keyword::
3768
3768
3769 hg log -k bug --template "{rev}\\n"
3769 hg log -k bug --template "{rev}\\n"
3770
3770
3771 - the full hash identifier of the working directory parent::
3771 - the full hash identifier of the working directory parent::
3772
3772
3773 hg log -r . --template "{node}\\n"
3773 hg log -r . --template "{node}\\n"
3774
3774
3775 - list available log templates::
3775 - list available log templates::
3776
3776
3777 hg log -T list
3777 hg log -T list
3778
3778
3779 - check if a given changeset is included in a tagged release::
3779 - check if a given changeset is included in a tagged release::
3780
3780
3781 hg log -r "a21ccf and ancestor(1.9)"
3781 hg log -r "a21ccf and ancestor(1.9)"
3782
3782
3783 - find all changesets by some user in a date range::
3783 - find all changesets by some user in a date range::
3784
3784
3785 hg log -k alice -d "may 2008 to jul 2008"
3785 hg log -k alice -d "may 2008 to jul 2008"
3786
3786
3787 - summary of all changesets after the last tag::
3787 - summary of all changesets after the last tag::
3788
3788
3789 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3789 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3790
3790
3791 - changesets touching lines 13 to 23 for file.c::
3791 - changesets touching lines 13 to 23 for file.c::
3792
3792
3793 hg log -L file.c,13:23
3793 hg log -L file.c,13:23
3794
3794
3795 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3795 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3796 main.c with patch::
3796 main.c with patch::
3797
3797
3798 hg log -L file.c,13:23 -L main.c,2:6 -p
3798 hg log -L file.c,13:23 -L main.c,2:6 -p
3799
3799
3800 See :hg:`help dates` for a list of formats valid for -d/--date.
3800 See :hg:`help dates` for a list of formats valid for -d/--date.
3801
3801
3802 See :hg:`help revisions` for more about specifying and ordering
3802 See :hg:`help revisions` for more about specifying and ordering
3803 revisions.
3803 revisions.
3804
3804
3805 See :hg:`help templates` for more about pre-packaged styles and
3805 See :hg:`help templates` for more about pre-packaged styles and
3806 specifying custom templates. The default template used by the log
3806 specifying custom templates. The default template used by the log
3807 command can be customized via the ``ui.logtemplate`` configuration
3807 command can be customized via the ``ui.logtemplate`` configuration
3808 setting.
3808 setting.
3809
3809
3810 Returns 0 on success.
3810 Returns 0 on success.
3811
3811
3812 """
3812 """
3813 opts = pycompat.byteskwargs(opts)
3813 opts = pycompat.byteskwargs(opts)
3814 linerange = opts.get('line_range')
3814 linerange = opts.get('line_range')
3815
3815
3816 if linerange and not opts.get('follow'):
3816 if linerange and not opts.get('follow'):
3817 raise error.Abort(_('--line-range requires --follow'))
3817 raise error.Abort(_('--line-range requires --follow'))
3818
3818
3819 if linerange and pats:
3819 if linerange and pats:
3820 # TODO: take pats as patterns with no line-range filter
3820 # TODO: take pats as patterns with no line-range filter
3821 raise error.Abort(
3821 raise error.Abort(
3822 _('FILE arguments are not compatible with --line-range option')
3822 _('FILE arguments are not compatible with --line-range option')
3823 )
3823 )
3824
3824
3825 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3825 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3826 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3826 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3827 if linerange:
3827 if linerange:
3828 # TODO: should follow file history from logcmdutil._initialrevs(),
3828 # TODO: should follow file history from logcmdutil._initialrevs(),
3829 # then filter the result by logcmdutil._makerevset() and --limit
3829 # then filter the result by logcmdutil._makerevset() and --limit
3830 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3830 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3831
3831
3832 getcopies = None
3832 getcopies = None
3833 if opts.get('copies'):
3833 if opts.get('copies'):
3834 endrev = None
3834 endrev = None
3835 if revs:
3835 if revs:
3836 endrev = revs.max() + 1
3836 endrev = revs.max() + 1
3837 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
3837 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
3838
3838
3839 ui.pager('log')
3839 ui.pager('log')
3840 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3840 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3841 buffered=True)
3841 buffered=True)
3842 if opts.get('graph'):
3842 if opts.get('graph'):
3843 displayfn = logcmdutil.displaygraphrevs
3843 displayfn = logcmdutil.displaygraphrevs
3844 else:
3844 else:
3845 displayfn = logcmdutil.displayrevs
3845 displayfn = logcmdutil.displayrevs
3846 displayfn(ui, repo, revs, displayer, getcopies)
3846 displayfn(ui, repo, revs, displayer, getcopies)
3847
3847
3848 @command('manifest',
3848 @command('manifest',
3849 [('r', 'rev', '', _('revision to display'), _('REV')),
3849 [('r', 'rev', '', _('revision to display'), _('REV')),
3850 ('', 'all', False, _("list files from all revisions"))]
3850 ('', 'all', False, _("list files from all revisions"))]
3851 + formatteropts,
3851 + formatteropts,
3852 _('[-r REV]'),
3852 _('[-r REV]'),
3853 helpcategory=command.CATEGORY_MAINTENANCE,
3853 helpcategory=command.CATEGORY_MAINTENANCE,
3854 intents={INTENT_READONLY})
3854 intents={INTENT_READONLY})
3855 def manifest(ui, repo, node=None, rev=None, **opts):
3855 def manifest(ui, repo, node=None, rev=None, **opts):
3856 """output the current or given revision of the project manifest
3856 """output the current or given revision of the project manifest
3857
3857
3858 Print a list of version controlled files for the given revision.
3858 Print a list of version controlled files for the given revision.
3859 If no revision is given, the first parent of the working directory
3859 If no revision is given, the first parent of the working directory
3860 is used, or the null revision if no revision is checked out.
3860 is used, or the null revision if no revision is checked out.
3861
3861
3862 With -v, print file permissions, symlink and executable bits.
3862 With -v, print file permissions, symlink and executable bits.
3863 With --debug, print file revision hashes.
3863 With --debug, print file revision hashes.
3864
3864
3865 If option --all is specified, the list of all files from all revisions
3865 If option --all is specified, the list of all files from all revisions
3866 is printed. This includes deleted and renamed files.
3866 is printed. This includes deleted and renamed files.
3867
3867
3868 Returns 0 on success.
3868 Returns 0 on success.
3869 """
3869 """
3870 opts = pycompat.byteskwargs(opts)
3870 opts = pycompat.byteskwargs(opts)
3871 fm = ui.formatter('manifest', opts)
3871 fm = ui.formatter('manifest', opts)
3872
3872
3873 if opts.get('all'):
3873 if opts.get('all'):
3874 if rev or node:
3874 if rev or node:
3875 raise error.Abort(_("can't specify a revision with --all"))
3875 raise error.Abort(_("can't specify a revision with --all"))
3876
3876
3877 res = set()
3877 res = set()
3878 for rev in repo:
3878 for rev in repo:
3879 ctx = repo[rev]
3879 ctx = repo[rev]
3880 res |= set(ctx.files())
3880 res |= set(ctx.files())
3881
3881
3882 ui.pager('manifest')
3882 ui.pager('manifest')
3883 for f in sorted(res):
3883 for f in sorted(res):
3884 fm.startitem()
3884 fm.startitem()
3885 fm.write("path", '%s\n', f)
3885 fm.write("path", '%s\n', f)
3886 fm.end()
3886 fm.end()
3887 return
3887 return
3888
3888
3889 if rev and node:
3889 if rev and node:
3890 raise error.Abort(_("please specify just one revision"))
3890 raise error.Abort(_("please specify just one revision"))
3891
3891
3892 if not node:
3892 if not node:
3893 node = rev
3893 node = rev
3894
3894
3895 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3895 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3896 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3896 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3897 if node:
3897 if node:
3898 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3898 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3899 ctx = scmutil.revsingle(repo, node)
3899 ctx = scmutil.revsingle(repo, node)
3900 mf = ctx.manifest()
3900 mf = ctx.manifest()
3901 ui.pager('manifest')
3901 ui.pager('manifest')
3902 for f in ctx:
3902 for f in ctx:
3903 fm.startitem()
3903 fm.startitem()
3904 fm.context(ctx=ctx)
3904 fm.context(ctx=ctx)
3905 fl = ctx[f].flags()
3905 fl = ctx[f].flags()
3906 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3906 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3907 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3907 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3908 fm.write('path', '%s\n', f)
3908 fm.write('path', '%s\n', f)
3909 fm.end()
3909 fm.end()
3910
3910
3911 @command('merge',
3911 @command('merge',
3912 [('f', 'force', None,
3912 [('f', 'force', None,
3913 _('force a merge including outstanding changes (DEPRECATED)')),
3913 _('force a merge including outstanding changes (DEPRECATED)')),
3914 ('r', 'rev', '', _('revision to merge'), _('REV')),
3914 ('r', 'rev', '', _('revision to merge'), _('REV')),
3915 ('P', 'preview', None,
3915 ('P', 'preview', None,
3916 _('review revisions to merge (no merge is performed)')),
3916 _('review revisions to merge (no merge is performed)')),
3917 ('', 'abort', None, _('abort the ongoing merge')),
3917 ('', 'abort', None, _('abort the ongoing merge')),
3918 ] + mergetoolopts,
3918 ] + mergetoolopts,
3919 _('[-P] [[-r] REV]'),
3919 _('[-P] [[-r] REV]'),
3920 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3920 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3921 def merge(ui, repo, node=None, **opts):
3921 def merge(ui, repo, node=None, **opts):
3922 """merge another revision into working directory
3922 """merge another revision into working directory
3923
3923
3924 The current working directory is updated with all changes made in
3924 The current working directory is updated with all changes made in
3925 the requested revision since the last common predecessor revision.
3925 the requested revision since the last common predecessor revision.
3926
3926
3927 Files that changed between either parent are marked as changed for
3927 Files that changed between either parent are marked as changed for
3928 the next commit and a commit must be performed before any further
3928 the next commit and a commit must be performed before any further
3929 updates to the repository are allowed. The next commit will have
3929 updates to the repository are allowed. The next commit will have
3930 two parents.
3930 two parents.
3931
3931
3932 ``--tool`` can be used to specify the merge tool used for file
3932 ``--tool`` can be used to specify the merge tool used for file
3933 merges. It overrides the HGMERGE environment variable and your
3933 merges. It overrides the HGMERGE environment variable and your
3934 configuration files. See :hg:`help merge-tools` for options.
3934 configuration files. See :hg:`help merge-tools` for options.
3935
3935
3936 If no revision is specified, the working directory's parent is a
3936 If no revision is specified, the working directory's parent is a
3937 head revision, and the current branch contains exactly one other
3937 head revision, and the current branch contains exactly one other
3938 head, the other head is merged with by default. Otherwise, an
3938 head, the other head is merged with by default. Otherwise, an
3939 explicit revision with which to merge must be provided.
3939 explicit revision with which to merge must be provided.
3940
3940
3941 See :hg:`help resolve` for information on handling file conflicts.
3941 See :hg:`help resolve` for information on handling file conflicts.
3942
3942
3943 To undo an uncommitted merge, use :hg:`merge --abort` which
3943 To undo an uncommitted merge, use :hg:`merge --abort` which
3944 will check out a clean copy of the original merge parent, losing
3944 will check out a clean copy of the original merge parent, losing
3945 all changes.
3945 all changes.
3946
3946
3947 Returns 0 on success, 1 if there are unresolved files.
3947 Returns 0 on success, 1 if there are unresolved files.
3948 """
3948 """
3949
3949
3950 opts = pycompat.byteskwargs(opts)
3950 opts = pycompat.byteskwargs(opts)
3951 abort = opts.get('abort')
3951 abort = opts.get('abort')
3952 if abort and repo.dirstate.p2() == nullid:
3952 if abort and repo.dirstate.p2() == nullid:
3953 cmdutil.wrongtooltocontinue(repo, _('merge'))
3953 cmdutil.wrongtooltocontinue(repo, _('merge'))
3954 if abort:
3954 if abort:
3955 state = cmdutil.getunfinishedstate(repo)
3956 if state and state._opname != 'merge':
3957 raise error.Abort(_('cannot abort merge with %s in progress') %
3958 (state._opname), hint=state.hint())
3955 if node:
3959 if node:
3956 raise error.Abort(_("cannot specify a node with --abort"))
3960 raise error.Abort(_("cannot specify a node with --abort"))
3957 if opts.get('rev'):
3961 if opts.get('rev'):
3958 raise error.Abort(_("cannot specify both --rev and --abort"))
3962 raise error.Abort(_("cannot specify both --rev and --abort"))
3959 if opts.get('preview'):
3963 if opts.get('preview'):
3960 raise error.Abort(_("cannot specify --preview with --abort"))
3964 raise error.Abort(_("cannot specify --preview with --abort"))
3961 if opts.get('rev') and node:
3965 if opts.get('rev') and node:
3962 raise error.Abort(_("please specify just one revision"))
3966 raise error.Abort(_("please specify just one revision"))
3963 if not node:
3967 if not node:
3964 node = opts.get('rev')
3968 node = opts.get('rev')
3965
3969
3966 if node:
3970 if node:
3967 node = scmutil.revsingle(repo, node).node()
3971 node = scmutil.revsingle(repo, node).node()
3968
3972
3969 if not node and not abort:
3973 if not node and not abort:
3970 node = repo[destutil.destmerge(repo)].node()
3974 node = repo[destutil.destmerge(repo)].node()
3971
3975
3972 if opts.get('preview'):
3976 if opts.get('preview'):
3973 # find nodes that are ancestors of p2 but not of p1
3977 # find nodes that are ancestors of p2 but not of p1
3974 p1 = repo.lookup('.')
3978 p1 = repo.lookup('.')
3975 p2 = node
3979 p2 = node
3976 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3980 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3977
3981
3978 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3982 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3979 for node in nodes:
3983 for node in nodes:
3980 displayer.show(repo[node])
3984 displayer.show(repo[node])
3981 displayer.close()
3985 displayer.close()
3982 return 0
3986 return 0
3983
3987
3984 # ui.forcemerge is an internal variable, do not document
3988 # ui.forcemerge is an internal variable, do not document
3985 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
3989 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
3986 with ui.configoverride(overrides, 'merge'):
3990 with ui.configoverride(overrides, 'merge'):
3987 force = opts.get('force')
3991 force = opts.get('force')
3988 labels = ['working copy', 'merge rev']
3992 labels = ['working copy', 'merge rev']
3989 return hg.merge(repo, node, force=force, mergeforce=force,
3993 return hg.merge(repo, node, force=force, mergeforce=force,
3990 labels=labels, abort=abort)
3994 labels=labels, abort=abort)
3991
3995
3992 @command('outgoing|out',
3996 @command('outgoing|out',
3993 [('f', 'force', None, _('run even when the destination is unrelated')),
3997 [('f', 'force', None, _('run even when the destination is unrelated')),
3994 ('r', 'rev', [],
3998 ('r', 'rev', [],
3995 _('a changeset intended to be included in the destination'), _('REV')),
3999 _('a changeset intended to be included in the destination'), _('REV')),
3996 ('n', 'newest-first', None, _('show newest record first')),
4000 ('n', 'newest-first', None, _('show newest record first')),
3997 ('B', 'bookmarks', False, _('compare bookmarks')),
4001 ('B', 'bookmarks', False, _('compare bookmarks')),
3998 ('b', 'branch', [], _('a specific branch you would like to push'),
4002 ('b', 'branch', [], _('a specific branch you would like to push'),
3999 _('BRANCH')),
4003 _('BRANCH')),
4000 ] + logopts + remoteopts + subrepoopts,
4004 ] + logopts + remoteopts + subrepoopts,
4001 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4005 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4002 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4006 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4003 def outgoing(ui, repo, dest=None, **opts):
4007 def outgoing(ui, repo, dest=None, **opts):
4004 """show changesets not found in the destination
4008 """show changesets not found in the destination
4005
4009
4006 Show changesets not found in the specified destination repository
4010 Show changesets not found in the specified destination repository
4007 or the default push location. These are the changesets that would
4011 or the default push location. These are the changesets that would
4008 be pushed if a push was requested.
4012 be pushed if a push was requested.
4009
4013
4010 See pull for details of valid destination formats.
4014 See pull for details of valid destination formats.
4011
4015
4012 .. container:: verbose
4016 .. container:: verbose
4013
4017
4014 With -B/--bookmarks, the result of bookmark comparison between
4018 With -B/--bookmarks, the result of bookmark comparison between
4015 local and remote repositories is displayed. With -v/--verbose,
4019 local and remote repositories is displayed. With -v/--verbose,
4016 status is also displayed for each bookmark like below::
4020 status is also displayed for each bookmark like below::
4017
4021
4018 BM1 01234567890a added
4022 BM1 01234567890a added
4019 BM2 deleted
4023 BM2 deleted
4020 BM3 234567890abc advanced
4024 BM3 234567890abc advanced
4021 BM4 34567890abcd diverged
4025 BM4 34567890abcd diverged
4022 BM5 4567890abcde changed
4026 BM5 4567890abcde changed
4023
4027
4024 The action taken when pushing depends on the
4028 The action taken when pushing depends on the
4025 status of each bookmark:
4029 status of each bookmark:
4026
4030
4027 :``added``: push with ``-B`` will create it
4031 :``added``: push with ``-B`` will create it
4028 :``deleted``: push with ``-B`` will delete it
4032 :``deleted``: push with ``-B`` will delete it
4029 :``advanced``: push will update it
4033 :``advanced``: push will update it
4030 :``diverged``: push with ``-B`` will update it
4034 :``diverged``: push with ``-B`` will update it
4031 :``changed``: push with ``-B`` will update it
4035 :``changed``: push with ``-B`` will update it
4032
4036
4033 From the point of view of pushing behavior, bookmarks
4037 From the point of view of pushing behavior, bookmarks
4034 existing only in the remote repository are treated as
4038 existing only in the remote repository are treated as
4035 ``deleted``, even if it is in fact added remotely.
4039 ``deleted``, even if it is in fact added remotely.
4036
4040
4037 Returns 0 if there are outgoing changes, 1 otherwise.
4041 Returns 0 if there are outgoing changes, 1 otherwise.
4038 """
4042 """
4039 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4043 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4040 # style URLs, so don't overwrite dest.
4044 # style URLs, so don't overwrite dest.
4041 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4045 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4042 if not path:
4046 if not path:
4043 raise error.Abort(_('default repository not configured!'),
4047 raise error.Abort(_('default repository not configured!'),
4044 hint=_("see 'hg help config.paths'"))
4048 hint=_("see 'hg help config.paths'"))
4045
4049
4046 opts = pycompat.byteskwargs(opts)
4050 opts = pycompat.byteskwargs(opts)
4047 if opts.get('graph'):
4051 if opts.get('graph'):
4048 logcmdutil.checkunsupportedgraphflags([], opts)
4052 logcmdutil.checkunsupportedgraphflags([], opts)
4049 o, other = hg._outgoing(ui, repo, dest, opts)
4053 o, other = hg._outgoing(ui, repo, dest, opts)
4050 if not o:
4054 if not o:
4051 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4055 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4052 return
4056 return
4053
4057
4054 revdag = logcmdutil.graphrevs(repo, o, opts)
4058 revdag = logcmdutil.graphrevs(repo, o, opts)
4055 ui.pager('outgoing')
4059 ui.pager('outgoing')
4056 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4060 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4057 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4061 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4058 graphmod.asciiedges)
4062 graphmod.asciiedges)
4059 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4063 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4060 return 0
4064 return 0
4061
4065
4062 if opts.get('bookmarks'):
4066 if opts.get('bookmarks'):
4063 dest = path.pushloc or path.loc
4067 dest = path.pushloc or path.loc
4064 other = hg.peer(repo, opts, dest)
4068 other = hg.peer(repo, opts, dest)
4065 if 'bookmarks' not in other.listkeys('namespaces'):
4069 if 'bookmarks' not in other.listkeys('namespaces'):
4066 ui.warn(_("remote doesn't support bookmarks\n"))
4070 ui.warn(_("remote doesn't support bookmarks\n"))
4067 return 0
4071 return 0
4068 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4072 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4069 ui.pager('outgoing')
4073 ui.pager('outgoing')
4070 return bookmarks.outgoing(ui, repo, other)
4074 return bookmarks.outgoing(ui, repo, other)
4071
4075
4072 repo._subtoppath = path.pushloc or path.loc
4076 repo._subtoppath = path.pushloc or path.loc
4073 try:
4077 try:
4074 return hg.outgoing(ui, repo, dest, opts)
4078 return hg.outgoing(ui, repo, dest, opts)
4075 finally:
4079 finally:
4076 del repo._subtoppath
4080 del repo._subtoppath
4077
4081
4078 @command('parents',
4082 @command('parents',
4079 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4083 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4080 ] + templateopts,
4084 ] + templateopts,
4081 _('[-r REV] [FILE]'),
4085 _('[-r REV] [FILE]'),
4082 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4086 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4083 inferrepo=True)
4087 inferrepo=True)
4084 def parents(ui, repo, file_=None, **opts):
4088 def parents(ui, repo, file_=None, **opts):
4085 """show the parents of the working directory or revision (DEPRECATED)
4089 """show the parents of the working directory or revision (DEPRECATED)
4086
4090
4087 Print the working directory's parent revisions. If a revision is
4091 Print the working directory's parent revisions. If a revision is
4088 given via -r/--rev, the parent of that revision will be printed.
4092 given via -r/--rev, the parent of that revision will be printed.
4089 If a file argument is given, the revision in which the file was
4093 If a file argument is given, the revision in which the file was
4090 last changed (before the working directory revision or the
4094 last changed (before the working directory revision or the
4091 argument to --rev if given) is printed.
4095 argument to --rev if given) is printed.
4092
4096
4093 This command is equivalent to::
4097 This command is equivalent to::
4094
4098
4095 hg log -r "p1()+p2()" or
4099 hg log -r "p1()+p2()" or
4096 hg log -r "p1(REV)+p2(REV)" or
4100 hg log -r "p1(REV)+p2(REV)" or
4097 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4101 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4098 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4102 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4099
4103
4100 See :hg:`summary` and :hg:`help revsets` for related information.
4104 See :hg:`summary` and :hg:`help revsets` for related information.
4101
4105
4102 Returns 0 on success.
4106 Returns 0 on success.
4103 """
4107 """
4104
4108
4105 opts = pycompat.byteskwargs(opts)
4109 opts = pycompat.byteskwargs(opts)
4106 rev = opts.get('rev')
4110 rev = opts.get('rev')
4107 if rev:
4111 if rev:
4108 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4112 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4109 ctx = scmutil.revsingle(repo, rev, None)
4113 ctx = scmutil.revsingle(repo, rev, None)
4110
4114
4111 if file_:
4115 if file_:
4112 m = scmutil.match(ctx, (file_,), opts)
4116 m = scmutil.match(ctx, (file_,), opts)
4113 if m.anypats() or len(m.files()) != 1:
4117 if m.anypats() or len(m.files()) != 1:
4114 raise error.Abort(_('can only specify an explicit filename'))
4118 raise error.Abort(_('can only specify an explicit filename'))
4115 file_ = m.files()[0]
4119 file_ = m.files()[0]
4116 filenodes = []
4120 filenodes = []
4117 for cp in ctx.parents():
4121 for cp in ctx.parents():
4118 if not cp:
4122 if not cp:
4119 continue
4123 continue
4120 try:
4124 try:
4121 filenodes.append(cp.filenode(file_))
4125 filenodes.append(cp.filenode(file_))
4122 except error.LookupError:
4126 except error.LookupError:
4123 pass
4127 pass
4124 if not filenodes:
4128 if not filenodes:
4125 raise error.Abort(_("'%s' not found in manifest!") % file_)
4129 raise error.Abort(_("'%s' not found in manifest!") % file_)
4126 p = []
4130 p = []
4127 for fn in filenodes:
4131 for fn in filenodes:
4128 fctx = repo.filectx(file_, fileid=fn)
4132 fctx = repo.filectx(file_, fileid=fn)
4129 p.append(fctx.node())
4133 p.append(fctx.node())
4130 else:
4134 else:
4131 p = [cp.node() for cp in ctx.parents()]
4135 p = [cp.node() for cp in ctx.parents()]
4132
4136
4133 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4137 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4134 for n in p:
4138 for n in p:
4135 if n != nullid:
4139 if n != nullid:
4136 displayer.show(repo[n])
4140 displayer.show(repo[n])
4137 displayer.close()
4141 displayer.close()
4138
4142
4139 @command('paths', formatteropts, _('[NAME]'),
4143 @command('paths', formatteropts, _('[NAME]'),
4140 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4144 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4141 optionalrepo=True, intents={INTENT_READONLY})
4145 optionalrepo=True, intents={INTENT_READONLY})
4142 def paths(ui, repo, search=None, **opts):
4146 def paths(ui, repo, search=None, **opts):
4143 """show aliases for remote repositories
4147 """show aliases for remote repositories
4144
4148
4145 Show definition of symbolic path name NAME. If no name is given,
4149 Show definition of symbolic path name NAME. If no name is given,
4146 show definition of all available names.
4150 show definition of all available names.
4147
4151
4148 Option -q/--quiet suppresses all output when searching for NAME
4152 Option -q/--quiet suppresses all output when searching for NAME
4149 and shows only the path names when listing all definitions.
4153 and shows only the path names when listing all definitions.
4150
4154
4151 Path names are defined in the [paths] section of your
4155 Path names are defined in the [paths] section of your
4152 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4156 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4153 repository, ``.hg/hgrc`` is used, too.
4157 repository, ``.hg/hgrc`` is used, too.
4154
4158
4155 The path names ``default`` and ``default-push`` have a special
4159 The path names ``default`` and ``default-push`` have a special
4156 meaning. When performing a push or pull operation, they are used
4160 meaning. When performing a push or pull operation, they are used
4157 as fallbacks if no location is specified on the command-line.
4161 as fallbacks if no location is specified on the command-line.
4158 When ``default-push`` is set, it will be used for push and
4162 When ``default-push`` is set, it will be used for push and
4159 ``default`` will be used for pull; otherwise ``default`` is used
4163 ``default`` will be used for pull; otherwise ``default`` is used
4160 as the fallback for both. When cloning a repository, the clone
4164 as the fallback for both. When cloning a repository, the clone
4161 source is written as ``default`` in ``.hg/hgrc``.
4165 source is written as ``default`` in ``.hg/hgrc``.
4162
4166
4163 .. note::
4167 .. note::
4164
4168
4165 ``default`` and ``default-push`` apply to all inbound (e.g.
4169 ``default`` and ``default-push`` apply to all inbound (e.g.
4166 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4170 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4167 and :hg:`bundle`) operations.
4171 and :hg:`bundle`) operations.
4168
4172
4169 See :hg:`help urls` for more information.
4173 See :hg:`help urls` for more information.
4170
4174
4171 .. container:: verbose
4175 .. container:: verbose
4172
4176
4173 Template:
4177 Template:
4174
4178
4175 The following keywords are supported. See also :hg:`help templates`.
4179 The following keywords are supported. See also :hg:`help templates`.
4176
4180
4177 :name: String. Symbolic name of the path alias.
4181 :name: String. Symbolic name of the path alias.
4178 :pushurl: String. URL for push operations.
4182 :pushurl: String. URL for push operations.
4179 :url: String. URL or directory path for the other operations.
4183 :url: String. URL or directory path for the other operations.
4180
4184
4181 Returns 0 on success.
4185 Returns 0 on success.
4182 """
4186 """
4183
4187
4184 opts = pycompat.byteskwargs(opts)
4188 opts = pycompat.byteskwargs(opts)
4185 ui.pager('paths')
4189 ui.pager('paths')
4186 if search:
4190 if search:
4187 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4191 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4188 if name == search]
4192 if name == search]
4189 else:
4193 else:
4190 pathitems = sorted(ui.paths.iteritems())
4194 pathitems = sorted(ui.paths.iteritems())
4191
4195
4192 fm = ui.formatter('paths', opts)
4196 fm = ui.formatter('paths', opts)
4193 if fm.isplain():
4197 if fm.isplain():
4194 hidepassword = util.hidepassword
4198 hidepassword = util.hidepassword
4195 else:
4199 else:
4196 hidepassword = bytes
4200 hidepassword = bytes
4197 if ui.quiet:
4201 if ui.quiet:
4198 namefmt = '%s\n'
4202 namefmt = '%s\n'
4199 else:
4203 else:
4200 namefmt = '%s = '
4204 namefmt = '%s = '
4201 showsubopts = not search and not ui.quiet
4205 showsubopts = not search and not ui.quiet
4202
4206
4203 for name, path in pathitems:
4207 for name, path in pathitems:
4204 fm.startitem()
4208 fm.startitem()
4205 fm.condwrite(not search, 'name', namefmt, name)
4209 fm.condwrite(not search, 'name', namefmt, name)
4206 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4210 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4207 for subopt, value in sorted(path.suboptions.items()):
4211 for subopt, value in sorted(path.suboptions.items()):
4208 assert subopt not in ('name', 'url')
4212 assert subopt not in ('name', 'url')
4209 if showsubopts:
4213 if showsubopts:
4210 fm.plain('%s:%s = ' % (name, subopt))
4214 fm.plain('%s:%s = ' % (name, subopt))
4211 fm.condwrite(showsubopts, subopt, '%s\n', value)
4215 fm.condwrite(showsubopts, subopt, '%s\n', value)
4212
4216
4213 fm.end()
4217 fm.end()
4214
4218
4215 if search and not pathitems:
4219 if search and not pathitems:
4216 if not ui.quiet:
4220 if not ui.quiet:
4217 ui.warn(_("not found!\n"))
4221 ui.warn(_("not found!\n"))
4218 return 1
4222 return 1
4219 else:
4223 else:
4220 return 0
4224 return 0
4221
4225
4222 @command('phase',
4226 @command('phase',
4223 [('p', 'public', False, _('set changeset phase to public')),
4227 [('p', 'public', False, _('set changeset phase to public')),
4224 ('d', 'draft', False, _('set changeset phase to draft')),
4228 ('d', 'draft', False, _('set changeset phase to draft')),
4225 ('s', 'secret', False, _('set changeset phase to secret')),
4229 ('s', 'secret', False, _('set changeset phase to secret')),
4226 ('f', 'force', False, _('allow to move boundary backward')),
4230 ('f', 'force', False, _('allow to move boundary backward')),
4227 ('r', 'rev', [], _('target revision'), _('REV')),
4231 ('r', 'rev', [], _('target revision'), _('REV')),
4228 ],
4232 ],
4229 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4233 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4230 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4234 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4231 def phase(ui, repo, *revs, **opts):
4235 def phase(ui, repo, *revs, **opts):
4232 """set or show the current phase name
4236 """set or show the current phase name
4233
4237
4234 With no argument, show the phase name of the current revision(s).
4238 With no argument, show the phase name of the current revision(s).
4235
4239
4236 With one of -p/--public, -d/--draft or -s/--secret, change the
4240 With one of -p/--public, -d/--draft or -s/--secret, change the
4237 phase value of the specified revisions.
4241 phase value of the specified revisions.
4238
4242
4239 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4243 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4240 lower phase to a higher phase. Phases are ordered as follows::
4244 lower phase to a higher phase. Phases are ordered as follows::
4241
4245
4242 public < draft < secret
4246 public < draft < secret
4243
4247
4244 Returns 0 on success, 1 if some phases could not be changed.
4248 Returns 0 on success, 1 if some phases could not be changed.
4245
4249
4246 (For more information about the phases concept, see :hg:`help phases`.)
4250 (For more information about the phases concept, see :hg:`help phases`.)
4247 """
4251 """
4248 opts = pycompat.byteskwargs(opts)
4252 opts = pycompat.byteskwargs(opts)
4249 # search for a unique phase argument
4253 # search for a unique phase argument
4250 targetphase = None
4254 targetphase = None
4251 for idx, name in enumerate(phases.cmdphasenames):
4255 for idx, name in enumerate(phases.cmdphasenames):
4252 if opts[name]:
4256 if opts[name]:
4253 if targetphase is not None:
4257 if targetphase is not None:
4254 raise error.Abort(_('only one phase can be specified'))
4258 raise error.Abort(_('only one phase can be specified'))
4255 targetphase = idx
4259 targetphase = idx
4256
4260
4257 # look for specified revision
4261 # look for specified revision
4258 revs = list(revs)
4262 revs = list(revs)
4259 revs.extend(opts['rev'])
4263 revs.extend(opts['rev'])
4260 if not revs:
4264 if not revs:
4261 # display both parents as the second parent phase can influence
4265 # display both parents as the second parent phase can influence
4262 # the phase of a merge commit
4266 # the phase of a merge commit
4263 revs = [c.rev() for c in repo[None].parents()]
4267 revs = [c.rev() for c in repo[None].parents()]
4264
4268
4265 revs = scmutil.revrange(repo, revs)
4269 revs = scmutil.revrange(repo, revs)
4266
4270
4267 ret = 0
4271 ret = 0
4268 if targetphase is None:
4272 if targetphase is None:
4269 # display
4273 # display
4270 for r in revs:
4274 for r in revs:
4271 ctx = repo[r]
4275 ctx = repo[r]
4272 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4276 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4273 else:
4277 else:
4274 with repo.lock(), repo.transaction("phase") as tr:
4278 with repo.lock(), repo.transaction("phase") as tr:
4275 # set phase
4279 # set phase
4276 if not revs:
4280 if not revs:
4277 raise error.Abort(_('empty revision set'))
4281 raise error.Abort(_('empty revision set'))
4278 nodes = [repo[r].node() for r in revs]
4282 nodes = [repo[r].node() for r in revs]
4279 # moving revision from public to draft may hide them
4283 # moving revision from public to draft may hide them
4280 # We have to check result on an unfiltered repository
4284 # We have to check result on an unfiltered repository
4281 unfi = repo.unfiltered()
4285 unfi = repo.unfiltered()
4282 getphase = unfi._phasecache.phase
4286 getphase = unfi._phasecache.phase
4283 olddata = [getphase(unfi, r) for r in unfi]
4287 olddata = [getphase(unfi, r) for r in unfi]
4284 phases.advanceboundary(repo, tr, targetphase, nodes)
4288 phases.advanceboundary(repo, tr, targetphase, nodes)
4285 if opts['force']:
4289 if opts['force']:
4286 phases.retractboundary(repo, tr, targetphase, nodes)
4290 phases.retractboundary(repo, tr, targetphase, nodes)
4287 getphase = unfi._phasecache.phase
4291 getphase = unfi._phasecache.phase
4288 newdata = [getphase(unfi, r) for r in unfi]
4292 newdata = [getphase(unfi, r) for r in unfi]
4289 changes = sum(newdata[r] != olddata[r] for r in unfi)
4293 changes = sum(newdata[r] != olddata[r] for r in unfi)
4290 cl = unfi.changelog
4294 cl = unfi.changelog
4291 rejected = [n for n in nodes
4295 rejected = [n for n in nodes
4292 if newdata[cl.rev(n)] < targetphase]
4296 if newdata[cl.rev(n)] < targetphase]
4293 if rejected:
4297 if rejected:
4294 ui.warn(_('cannot move %i changesets to a higher '
4298 ui.warn(_('cannot move %i changesets to a higher '
4295 'phase, use --force\n') % len(rejected))
4299 'phase, use --force\n') % len(rejected))
4296 ret = 1
4300 ret = 1
4297 if changes:
4301 if changes:
4298 msg = _('phase changed for %i changesets\n') % changes
4302 msg = _('phase changed for %i changesets\n') % changes
4299 if ret:
4303 if ret:
4300 ui.status(msg)
4304 ui.status(msg)
4301 else:
4305 else:
4302 ui.note(msg)
4306 ui.note(msg)
4303 else:
4307 else:
4304 ui.warn(_('no phases changed\n'))
4308 ui.warn(_('no phases changed\n'))
4305 return ret
4309 return ret
4306
4310
4307 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4311 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4308 """Run after a changegroup has been added via pull/unbundle
4312 """Run after a changegroup has been added via pull/unbundle
4309
4313
4310 This takes arguments below:
4314 This takes arguments below:
4311
4315
4312 :modheads: change of heads by pull/unbundle
4316 :modheads: change of heads by pull/unbundle
4313 :optupdate: updating working directory is needed or not
4317 :optupdate: updating working directory is needed or not
4314 :checkout: update destination revision (or None to default destination)
4318 :checkout: update destination revision (or None to default destination)
4315 :brev: a name, which might be a bookmark to be activated after updating
4319 :brev: a name, which might be a bookmark to be activated after updating
4316 """
4320 """
4317 if modheads == 0:
4321 if modheads == 0:
4318 return
4322 return
4319 if optupdate:
4323 if optupdate:
4320 try:
4324 try:
4321 return hg.updatetotally(ui, repo, checkout, brev)
4325 return hg.updatetotally(ui, repo, checkout, brev)
4322 except error.UpdateAbort as inst:
4326 except error.UpdateAbort as inst:
4323 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4327 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4324 hint = inst.hint
4328 hint = inst.hint
4325 raise error.UpdateAbort(msg, hint=hint)
4329 raise error.UpdateAbort(msg, hint=hint)
4326 if modheads is not None and modheads > 1:
4330 if modheads is not None and modheads > 1:
4327 currentbranchheads = len(repo.branchheads())
4331 currentbranchheads = len(repo.branchheads())
4328 if currentbranchheads == modheads:
4332 if currentbranchheads == modheads:
4329 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4333 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4330 elif currentbranchheads > 1:
4334 elif currentbranchheads > 1:
4331 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4335 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4332 "merge)\n"))
4336 "merge)\n"))
4333 else:
4337 else:
4334 ui.status(_("(run 'hg heads' to see heads)\n"))
4338 ui.status(_("(run 'hg heads' to see heads)\n"))
4335 elif not ui.configbool('commands', 'update.requiredest'):
4339 elif not ui.configbool('commands', 'update.requiredest'):
4336 ui.status(_("(run 'hg update' to get a working copy)\n"))
4340 ui.status(_("(run 'hg update' to get a working copy)\n"))
4337
4341
4338 @command('pull',
4342 @command('pull',
4339 [('u', 'update', None,
4343 [('u', 'update', None,
4340 _('update to new branch head if new descendants were pulled')),
4344 _('update to new branch head if new descendants were pulled')),
4341 ('f', 'force', None, _('run even when remote repository is unrelated')),
4345 ('f', 'force', None, _('run even when remote repository is unrelated')),
4342 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4346 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4343 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4347 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4344 ('b', 'branch', [], _('a specific branch you would like to pull'),
4348 ('b', 'branch', [], _('a specific branch you would like to pull'),
4345 _('BRANCH')),
4349 _('BRANCH')),
4346 ] + remoteopts,
4350 ] + remoteopts,
4347 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4351 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4348 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4352 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4349 helpbasic=True)
4353 helpbasic=True)
4350 def pull(ui, repo, source="default", **opts):
4354 def pull(ui, repo, source="default", **opts):
4351 """pull changes from the specified source
4355 """pull changes from the specified source
4352
4356
4353 Pull changes from a remote repository to a local one.
4357 Pull changes from a remote repository to a local one.
4354
4358
4355 This finds all changes from the repository at the specified path
4359 This finds all changes from the repository at the specified path
4356 or URL and adds them to a local repository (the current one unless
4360 or URL and adds them to a local repository (the current one unless
4357 -R is specified). By default, this does not update the copy of the
4361 -R is specified). By default, this does not update the copy of the
4358 project in the working directory.
4362 project in the working directory.
4359
4363
4360 When cloning from servers that support it, Mercurial may fetch
4364 When cloning from servers that support it, Mercurial may fetch
4361 pre-generated data. When this is done, hooks operating on incoming
4365 pre-generated data. When this is done, hooks operating on incoming
4362 changesets and changegroups may fire more than once, once for each
4366 changesets and changegroups may fire more than once, once for each
4363 pre-generated bundle and as well as for any additional remaining
4367 pre-generated bundle and as well as for any additional remaining
4364 data. See :hg:`help -e clonebundles` for more.
4368 data. See :hg:`help -e clonebundles` for more.
4365
4369
4366 Use :hg:`incoming` if you want to see what would have been added
4370 Use :hg:`incoming` if you want to see what would have been added
4367 by a pull at the time you issued this command. If you then decide
4371 by a pull at the time you issued this command. If you then decide
4368 to add those changes to the repository, you should use :hg:`pull
4372 to add those changes to the repository, you should use :hg:`pull
4369 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4373 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4370
4374
4371 If SOURCE is omitted, the 'default' path will be used.
4375 If SOURCE is omitted, the 'default' path will be used.
4372 See :hg:`help urls` for more information.
4376 See :hg:`help urls` for more information.
4373
4377
4374 Specifying bookmark as ``.`` is equivalent to specifying the active
4378 Specifying bookmark as ``.`` is equivalent to specifying the active
4375 bookmark's name.
4379 bookmark's name.
4376
4380
4377 Returns 0 on success, 1 if an update had unresolved files.
4381 Returns 0 on success, 1 if an update had unresolved files.
4378 """
4382 """
4379
4383
4380 opts = pycompat.byteskwargs(opts)
4384 opts = pycompat.byteskwargs(opts)
4381 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4385 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4382 msg = _('update destination required by configuration')
4386 msg = _('update destination required by configuration')
4383 hint = _('use hg pull followed by hg update DEST')
4387 hint = _('use hg pull followed by hg update DEST')
4384 raise error.Abort(msg, hint=hint)
4388 raise error.Abort(msg, hint=hint)
4385
4389
4386 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4390 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4387 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4391 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4388 other = hg.peer(repo, opts, source)
4392 other = hg.peer(repo, opts, source)
4389 try:
4393 try:
4390 revs, checkout = hg.addbranchrevs(repo, other, branches,
4394 revs, checkout = hg.addbranchrevs(repo, other, branches,
4391 opts.get('rev'))
4395 opts.get('rev'))
4392
4396
4393 pullopargs = {}
4397 pullopargs = {}
4394
4398
4395 nodes = None
4399 nodes = None
4396 if opts.get('bookmark') or revs:
4400 if opts.get('bookmark') or revs:
4397 # The list of bookmark used here is the same used to actually update
4401 # The list of bookmark used here is the same used to actually update
4398 # the bookmark names, to avoid the race from issue 4689 and we do
4402 # the bookmark names, to avoid the race from issue 4689 and we do
4399 # all lookup and bookmark queries in one go so they see the same
4403 # all lookup and bookmark queries in one go so they see the same
4400 # version of the server state (issue 4700).
4404 # version of the server state (issue 4700).
4401 nodes = []
4405 nodes = []
4402 fnodes = []
4406 fnodes = []
4403 revs = revs or []
4407 revs = revs or []
4404 if revs and not other.capable('lookup'):
4408 if revs and not other.capable('lookup'):
4405 err = _("other repository doesn't support revision lookup, "
4409 err = _("other repository doesn't support revision lookup, "
4406 "so a rev cannot be specified.")
4410 "so a rev cannot be specified.")
4407 raise error.Abort(err)
4411 raise error.Abort(err)
4408 with other.commandexecutor() as e:
4412 with other.commandexecutor() as e:
4409 fremotebookmarks = e.callcommand('listkeys', {
4413 fremotebookmarks = e.callcommand('listkeys', {
4410 'namespace': 'bookmarks'
4414 'namespace': 'bookmarks'
4411 })
4415 })
4412 for r in revs:
4416 for r in revs:
4413 fnodes.append(e.callcommand('lookup', {'key': r}))
4417 fnodes.append(e.callcommand('lookup', {'key': r}))
4414 remotebookmarks = fremotebookmarks.result()
4418 remotebookmarks = fremotebookmarks.result()
4415 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4419 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4416 pullopargs['remotebookmarks'] = remotebookmarks
4420 pullopargs['remotebookmarks'] = remotebookmarks
4417 for b in opts.get('bookmark', []):
4421 for b in opts.get('bookmark', []):
4418 b = repo._bookmarks.expandname(b)
4422 b = repo._bookmarks.expandname(b)
4419 if b not in remotebookmarks:
4423 if b not in remotebookmarks:
4420 raise error.Abort(_('remote bookmark %s not found!') % b)
4424 raise error.Abort(_('remote bookmark %s not found!') % b)
4421 nodes.append(remotebookmarks[b])
4425 nodes.append(remotebookmarks[b])
4422 for i, rev in enumerate(revs):
4426 for i, rev in enumerate(revs):
4423 node = fnodes[i].result()
4427 node = fnodes[i].result()
4424 nodes.append(node)
4428 nodes.append(node)
4425 if rev == checkout:
4429 if rev == checkout:
4426 checkout = node
4430 checkout = node
4427
4431
4428 wlock = util.nullcontextmanager()
4432 wlock = util.nullcontextmanager()
4429 if opts.get('update'):
4433 if opts.get('update'):
4430 wlock = repo.wlock()
4434 wlock = repo.wlock()
4431 with wlock:
4435 with wlock:
4432 pullopargs.update(opts.get('opargs', {}))
4436 pullopargs.update(opts.get('opargs', {}))
4433 modheads = exchange.pull(repo, other, heads=nodes,
4437 modheads = exchange.pull(repo, other, heads=nodes,
4434 force=opts.get('force'),
4438 force=opts.get('force'),
4435 bookmarks=opts.get('bookmark', ()),
4439 bookmarks=opts.get('bookmark', ()),
4436 opargs=pullopargs).cgresult
4440 opargs=pullopargs).cgresult
4437
4441
4438 # brev is a name, which might be a bookmark to be activated at
4442 # brev is a name, which might be a bookmark to be activated at
4439 # the end of the update. In other words, it is an explicit
4443 # the end of the update. In other words, it is an explicit
4440 # destination of the update
4444 # destination of the update
4441 brev = None
4445 brev = None
4442
4446
4443 if checkout:
4447 if checkout:
4444 checkout = repo.unfiltered().changelog.rev(checkout)
4448 checkout = repo.unfiltered().changelog.rev(checkout)
4445
4449
4446 # order below depends on implementation of
4450 # order below depends on implementation of
4447 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4451 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4448 # because 'checkout' is determined without it.
4452 # because 'checkout' is determined without it.
4449 if opts.get('rev'):
4453 if opts.get('rev'):
4450 brev = opts['rev'][0]
4454 brev = opts['rev'][0]
4451 elif opts.get('branch'):
4455 elif opts.get('branch'):
4452 brev = opts['branch'][0]
4456 brev = opts['branch'][0]
4453 else:
4457 else:
4454 brev = branches[0]
4458 brev = branches[0]
4455 repo._subtoppath = source
4459 repo._subtoppath = source
4456 try:
4460 try:
4457 ret = postincoming(ui, repo, modheads, opts.get('update'),
4461 ret = postincoming(ui, repo, modheads, opts.get('update'),
4458 checkout, brev)
4462 checkout, brev)
4459 except error.FilteredRepoLookupError as exc:
4463 except error.FilteredRepoLookupError as exc:
4460 msg = _('cannot update to target: %s') % exc.args[0]
4464 msg = _('cannot update to target: %s') % exc.args[0]
4461 exc.args = (msg,) + exc.args[1:]
4465 exc.args = (msg,) + exc.args[1:]
4462 raise
4466 raise
4463 finally:
4467 finally:
4464 del repo._subtoppath
4468 del repo._subtoppath
4465
4469
4466 finally:
4470 finally:
4467 other.close()
4471 other.close()
4468 return ret
4472 return ret
4469
4473
4470 @command('push',
4474 @command('push',
4471 [('f', 'force', None, _('force push')),
4475 [('f', 'force', None, _('force push')),
4472 ('r', 'rev', [],
4476 ('r', 'rev', [],
4473 _('a changeset intended to be included in the destination'),
4477 _('a changeset intended to be included in the destination'),
4474 _('REV')),
4478 _('REV')),
4475 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4479 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4476 ('b', 'branch', [],
4480 ('b', 'branch', [],
4477 _('a specific branch you would like to push'), _('BRANCH')),
4481 _('a specific branch you would like to push'), _('BRANCH')),
4478 ('', 'new-branch', False, _('allow pushing a new branch')),
4482 ('', 'new-branch', False, _('allow pushing a new branch')),
4479 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4483 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4480 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4484 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4481 ] + remoteopts,
4485 ] + remoteopts,
4482 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4486 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4483 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4487 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4484 helpbasic=True)
4488 helpbasic=True)
4485 def push(ui, repo, dest=None, **opts):
4489 def push(ui, repo, dest=None, **opts):
4486 """push changes to the specified destination
4490 """push changes to the specified destination
4487
4491
4488 Push changesets from the local repository to the specified
4492 Push changesets from the local repository to the specified
4489 destination.
4493 destination.
4490
4494
4491 This operation is symmetrical to pull: it is identical to a pull
4495 This operation is symmetrical to pull: it is identical to a pull
4492 in the destination repository from the current one.
4496 in the destination repository from the current one.
4493
4497
4494 By default, push will not allow creation of new heads at the
4498 By default, push will not allow creation of new heads at the
4495 destination, since multiple heads would make it unclear which head
4499 destination, since multiple heads would make it unclear which head
4496 to use. In this situation, it is recommended to pull and merge
4500 to use. In this situation, it is recommended to pull and merge
4497 before pushing.
4501 before pushing.
4498
4502
4499 Use --new-branch if you want to allow push to create a new named
4503 Use --new-branch if you want to allow push to create a new named
4500 branch that is not present at the destination. This allows you to
4504 branch that is not present at the destination. This allows you to
4501 only create a new branch without forcing other changes.
4505 only create a new branch without forcing other changes.
4502
4506
4503 .. note::
4507 .. note::
4504
4508
4505 Extra care should be taken with the -f/--force option,
4509 Extra care should be taken with the -f/--force option,
4506 which will push all new heads on all branches, an action which will
4510 which will push all new heads on all branches, an action which will
4507 almost always cause confusion for collaborators.
4511 almost always cause confusion for collaborators.
4508
4512
4509 If -r/--rev is used, the specified revision and all its ancestors
4513 If -r/--rev is used, the specified revision and all its ancestors
4510 will be pushed to the remote repository.
4514 will be pushed to the remote repository.
4511
4515
4512 If -B/--bookmark is used, the specified bookmarked revision, its
4516 If -B/--bookmark is used, the specified bookmarked revision, its
4513 ancestors, and the bookmark will be pushed to the remote
4517 ancestors, and the bookmark will be pushed to the remote
4514 repository. Specifying ``.`` is equivalent to specifying the active
4518 repository. Specifying ``.`` is equivalent to specifying the active
4515 bookmark's name.
4519 bookmark's name.
4516
4520
4517 Please see :hg:`help urls` for important details about ``ssh://``
4521 Please see :hg:`help urls` for important details about ``ssh://``
4518 URLs. If DESTINATION is omitted, a default path will be used.
4522 URLs. If DESTINATION is omitted, a default path will be used.
4519
4523
4520 .. container:: verbose
4524 .. container:: verbose
4521
4525
4522 The --pushvars option sends strings to the server that become
4526 The --pushvars option sends strings to the server that become
4523 environment variables prepended with ``HG_USERVAR_``. For example,
4527 environment variables prepended with ``HG_USERVAR_``. For example,
4524 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4528 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4525 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4529 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4526
4530
4527 pushvars can provide for user-overridable hooks as well as set debug
4531 pushvars can provide for user-overridable hooks as well as set debug
4528 levels. One example is having a hook that blocks commits containing
4532 levels. One example is having a hook that blocks commits containing
4529 conflict markers, but enables the user to override the hook if the file
4533 conflict markers, but enables the user to override the hook if the file
4530 is using conflict markers for testing purposes or the file format has
4534 is using conflict markers for testing purposes or the file format has
4531 strings that look like conflict markers.
4535 strings that look like conflict markers.
4532
4536
4533 By default, servers will ignore `--pushvars`. To enable it add the
4537 By default, servers will ignore `--pushvars`. To enable it add the
4534 following to your configuration file::
4538 following to your configuration file::
4535
4539
4536 [push]
4540 [push]
4537 pushvars.server = true
4541 pushvars.server = true
4538
4542
4539 Returns 0 if push was successful, 1 if nothing to push.
4543 Returns 0 if push was successful, 1 if nothing to push.
4540 """
4544 """
4541
4545
4542 opts = pycompat.byteskwargs(opts)
4546 opts = pycompat.byteskwargs(opts)
4543 if opts.get('bookmark'):
4547 if opts.get('bookmark'):
4544 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4548 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4545 for b in opts['bookmark']:
4549 for b in opts['bookmark']:
4546 # translate -B options to -r so changesets get pushed
4550 # translate -B options to -r so changesets get pushed
4547 b = repo._bookmarks.expandname(b)
4551 b = repo._bookmarks.expandname(b)
4548 if b in repo._bookmarks:
4552 if b in repo._bookmarks:
4549 opts.setdefault('rev', []).append(b)
4553 opts.setdefault('rev', []).append(b)
4550 else:
4554 else:
4551 # if we try to push a deleted bookmark, translate it to null
4555 # if we try to push a deleted bookmark, translate it to null
4552 # this lets simultaneous -r, -b options continue working
4556 # this lets simultaneous -r, -b options continue working
4553 opts.setdefault('rev', []).append("null")
4557 opts.setdefault('rev', []).append("null")
4554
4558
4555 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4559 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4556 if not path:
4560 if not path:
4557 raise error.Abort(_('default repository not configured!'),
4561 raise error.Abort(_('default repository not configured!'),
4558 hint=_("see 'hg help config.paths'"))
4562 hint=_("see 'hg help config.paths'"))
4559 dest = path.pushloc or path.loc
4563 dest = path.pushloc or path.loc
4560 branches = (path.branch, opts.get('branch') or [])
4564 branches = (path.branch, opts.get('branch') or [])
4561 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4565 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4562 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4566 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4563 other = hg.peer(repo, opts, dest)
4567 other = hg.peer(repo, opts, dest)
4564
4568
4565 if revs:
4569 if revs:
4566 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4570 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4567 if not revs:
4571 if not revs:
4568 raise error.Abort(_("specified revisions evaluate to an empty set"),
4572 raise error.Abort(_("specified revisions evaluate to an empty set"),
4569 hint=_("use different revision arguments"))
4573 hint=_("use different revision arguments"))
4570 elif path.pushrev:
4574 elif path.pushrev:
4571 # It doesn't make any sense to specify ancestor revisions. So limit
4575 # It doesn't make any sense to specify ancestor revisions. So limit
4572 # to DAG heads to make discovery simpler.
4576 # to DAG heads to make discovery simpler.
4573 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4577 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4574 revs = scmutil.revrange(repo, [expr])
4578 revs = scmutil.revrange(repo, [expr])
4575 revs = [repo[rev].node() for rev in revs]
4579 revs = [repo[rev].node() for rev in revs]
4576 if not revs:
4580 if not revs:
4577 raise error.Abort(_('default push revset for path evaluates to an '
4581 raise error.Abort(_('default push revset for path evaluates to an '
4578 'empty set'))
4582 'empty set'))
4579
4583
4580 repo._subtoppath = dest
4584 repo._subtoppath = dest
4581 try:
4585 try:
4582 # push subrepos depth-first for coherent ordering
4586 # push subrepos depth-first for coherent ordering
4583 c = repo['.']
4587 c = repo['.']
4584 subs = c.substate # only repos that are committed
4588 subs = c.substate # only repos that are committed
4585 for s in sorted(subs):
4589 for s in sorted(subs):
4586 result = c.sub(s).push(opts)
4590 result = c.sub(s).push(opts)
4587 if result == 0:
4591 if result == 0:
4588 return not result
4592 return not result
4589 finally:
4593 finally:
4590 del repo._subtoppath
4594 del repo._subtoppath
4591
4595
4592 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4596 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4593 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4597 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4594
4598
4595 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4599 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4596 newbranch=opts.get('new_branch'),
4600 newbranch=opts.get('new_branch'),
4597 bookmarks=opts.get('bookmark', ()),
4601 bookmarks=opts.get('bookmark', ()),
4598 publish=opts.get('publish'),
4602 publish=opts.get('publish'),
4599 opargs=opargs)
4603 opargs=opargs)
4600
4604
4601 result = not pushop.cgresult
4605 result = not pushop.cgresult
4602
4606
4603 if pushop.bkresult is not None:
4607 if pushop.bkresult is not None:
4604 if pushop.bkresult == 2:
4608 if pushop.bkresult == 2:
4605 result = 2
4609 result = 2
4606 elif not result and pushop.bkresult:
4610 elif not result and pushop.bkresult:
4607 result = 2
4611 result = 2
4608
4612
4609 return result
4613 return result
4610
4614
4611 @command('recover',
4615 @command('recover',
4612 [('','verify', True, "run `hg verify` after succesful recover"),
4616 [('','verify', True, "run `hg verify` after succesful recover"),
4613 ],
4617 ],
4614 helpcategory=command.CATEGORY_MAINTENANCE)
4618 helpcategory=command.CATEGORY_MAINTENANCE)
4615 def recover(ui, repo, **opts):
4619 def recover(ui, repo, **opts):
4616 """roll back an interrupted transaction
4620 """roll back an interrupted transaction
4617
4621
4618 Recover from an interrupted commit or pull.
4622 Recover from an interrupted commit or pull.
4619
4623
4620 This command tries to fix the repository status after an
4624 This command tries to fix the repository status after an
4621 interrupted operation. It should only be necessary when Mercurial
4625 interrupted operation. It should only be necessary when Mercurial
4622 suggests it.
4626 suggests it.
4623
4627
4624 Returns 0 if successful, 1 if nothing to recover or verify fails.
4628 Returns 0 if successful, 1 if nothing to recover or verify fails.
4625 """
4629 """
4626 ret = repo.recover()
4630 ret = repo.recover()
4627 if ret:
4631 if ret:
4628 if opts[r'verify']:
4632 if opts[r'verify']:
4629 return hg.verify(repo)
4633 return hg.verify(repo)
4630 else:
4634 else:
4631 msg = _("(verify step skipped, run `hg verify` to check your "
4635 msg = _("(verify step skipped, run `hg verify` to check your "
4632 "repository content)\n")
4636 "repository content)\n")
4633 ui.warn(msg)
4637 ui.warn(msg)
4634 return 0
4638 return 0
4635 return 1
4639 return 1
4636
4640
4637 @command('remove|rm',
4641 @command('remove|rm',
4638 [('A', 'after', None, _('record delete for missing files')),
4642 [('A', 'after', None, _('record delete for missing files')),
4639 ('f', 'force', None,
4643 ('f', 'force', None,
4640 _('forget added files, delete modified files')),
4644 _('forget added files, delete modified files')),
4641 ] + subrepoopts + walkopts + dryrunopts,
4645 ] + subrepoopts + walkopts + dryrunopts,
4642 _('[OPTION]... FILE...'),
4646 _('[OPTION]... FILE...'),
4643 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4647 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4644 helpbasic=True, inferrepo=True)
4648 helpbasic=True, inferrepo=True)
4645 def remove(ui, repo, *pats, **opts):
4649 def remove(ui, repo, *pats, **opts):
4646 """remove the specified files on the next commit
4650 """remove the specified files on the next commit
4647
4651
4648 Schedule the indicated files for removal from the current branch.
4652 Schedule the indicated files for removal from the current branch.
4649
4653
4650 This command schedules the files to be removed at the next commit.
4654 This command schedules the files to be removed at the next commit.
4651 To undo a remove before that, see :hg:`revert`. To undo added
4655 To undo a remove before that, see :hg:`revert`. To undo added
4652 files, see :hg:`forget`.
4656 files, see :hg:`forget`.
4653
4657
4654 .. container:: verbose
4658 .. container:: verbose
4655
4659
4656 -A/--after can be used to remove only files that have already
4660 -A/--after can be used to remove only files that have already
4657 been deleted, -f/--force can be used to force deletion, and -Af
4661 been deleted, -f/--force can be used to force deletion, and -Af
4658 can be used to remove files from the next revision without
4662 can be used to remove files from the next revision without
4659 deleting them from the working directory.
4663 deleting them from the working directory.
4660
4664
4661 The following table details the behavior of remove for different
4665 The following table details the behavior of remove for different
4662 file states (columns) and option combinations (rows). The file
4666 file states (columns) and option combinations (rows). The file
4663 states are Added [A], Clean [C], Modified [M] and Missing [!]
4667 states are Added [A], Clean [C], Modified [M] and Missing [!]
4664 (as reported by :hg:`status`). The actions are Warn, Remove
4668 (as reported by :hg:`status`). The actions are Warn, Remove
4665 (from branch) and Delete (from disk):
4669 (from branch) and Delete (from disk):
4666
4670
4667 ========= == == == ==
4671 ========= == == == ==
4668 opt/state A C M !
4672 opt/state A C M !
4669 ========= == == == ==
4673 ========= == == == ==
4670 none W RD W R
4674 none W RD W R
4671 -f R RD RD R
4675 -f R RD RD R
4672 -A W W W R
4676 -A W W W R
4673 -Af R R R R
4677 -Af R R R R
4674 ========= == == == ==
4678 ========= == == == ==
4675
4679
4676 .. note::
4680 .. note::
4677
4681
4678 :hg:`remove` never deletes files in Added [A] state from the
4682 :hg:`remove` never deletes files in Added [A] state from the
4679 working directory, not even if ``--force`` is specified.
4683 working directory, not even if ``--force`` is specified.
4680
4684
4681 Returns 0 on success, 1 if any warnings encountered.
4685 Returns 0 on success, 1 if any warnings encountered.
4682 """
4686 """
4683
4687
4684 opts = pycompat.byteskwargs(opts)
4688 opts = pycompat.byteskwargs(opts)
4685 after, force = opts.get('after'), opts.get('force')
4689 after, force = opts.get('after'), opts.get('force')
4686 dryrun = opts.get('dry_run')
4690 dryrun = opts.get('dry_run')
4687 if not pats and not after:
4691 if not pats and not after:
4688 raise error.Abort(_('no files specified'))
4692 raise error.Abort(_('no files specified'))
4689
4693
4690 m = scmutil.match(repo[None], pats, opts)
4694 m = scmutil.match(repo[None], pats, opts)
4691 subrepos = opts.get('subrepos')
4695 subrepos = opts.get('subrepos')
4692 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4696 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4693 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4697 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4694 dryrun=dryrun)
4698 dryrun=dryrun)
4695
4699
4696 @command('rename|move|mv',
4700 @command('rename|move|mv',
4697 [('A', 'after', None, _('record a rename that has already occurred')),
4701 [('A', 'after', None, _('record a rename that has already occurred')),
4698 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4702 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4699 ] + walkopts + dryrunopts,
4703 ] + walkopts + dryrunopts,
4700 _('[OPTION]... SOURCE... DEST'),
4704 _('[OPTION]... SOURCE... DEST'),
4701 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4705 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4702 def rename(ui, repo, *pats, **opts):
4706 def rename(ui, repo, *pats, **opts):
4703 """rename files; equivalent of copy + remove
4707 """rename files; equivalent of copy + remove
4704
4708
4705 Mark dest as copies of sources; mark sources for deletion. If dest
4709 Mark dest as copies of sources; mark sources for deletion. If dest
4706 is a directory, copies are put in that directory. If dest is a
4710 is a directory, copies are put in that directory. If dest is a
4707 file, there can only be one source.
4711 file, there can only be one source.
4708
4712
4709 By default, this command copies the contents of files as they
4713 By default, this command copies the contents of files as they
4710 exist in the working directory. If invoked with -A/--after, the
4714 exist in the working directory. If invoked with -A/--after, the
4711 operation is recorded, but no copying is performed.
4715 operation is recorded, but no copying is performed.
4712
4716
4713 This command takes effect at the next commit. To undo a rename
4717 This command takes effect at the next commit. To undo a rename
4714 before that, see :hg:`revert`.
4718 before that, see :hg:`revert`.
4715
4719
4716 Returns 0 on success, 1 if errors are encountered.
4720 Returns 0 on success, 1 if errors are encountered.
4717 """
4721 """
4718 opts = pycompat.byteskwargs(opts)
4722 opts = pycompat.byteskwargs(opts)
4719 with repo.wlock(False):
4723 with repo.wlock(False):
4720 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4724 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4721
4725
4722 @command('resolve',
4726 @command('resolve',
4723 [('a', 'all', None, _('select all unresolved files')),
4727 [('a', 'all', None, _('select all unresolved files')),
4724 ('l', 'list', None, _('list state of files needing merge')),
4728 ('l', 'list', None, _('list state of files needing merge')),
4725 ('m', 'mark', None, _('mark files as resolved')),
4729 ('m', 'mark', None, _('mark files as resolved')),
4726 ('u', 'unmark', None, _('mark files as unresolved')),
4730 ('u', 'unmark', None, _('mark files as unresolved')),
4727 ('n', 'no-status', None, _('hide status prefix')),
4731 ('n', 'no-status', None, _('hide status prefix')),
4728 ('', 're-merge', None, _('re-merge files'))]
4732 ('', 're-merge', None, _('re-merge files'))]
4729 + mergetoolopts + walkopts + formatteropts,
4733 + mergetoolopts + walkopts + formatteropts,
4730 _('[OPTION]... [FILE]...'),
4734 _('[OPTION]... [FILE]...'),
4731 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4735 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4732 inferrepo=True)
4736 inferrepo=True)
4733 def resolve(ui, repo, *pats, **opts):
4737 def resolve(ui, repo, *pats, **opts):
4734 """redo merges or set/view the merge status of files
4738 """redo merges or set/view the merge status of files
4735
4739
4736 Merges with unresolved conflicts are often the result of
4740 Merges with unresolved conflicts are often the result of
4737 non-interactive merging using the ``internal:merge`` configuration
4741 non-interactive merging using the ``internal:merge`` configuration
4738 setting, or a command-line merge tool like ``diff3``. The resolve
4742 setting, or a command-line merge tool like ``diff3``. The resolve
4739 command is used to manage the files involved in a merge, after
4743 command is used to manage the files involved in a merge, after
4740 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4744 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4741 working directory must have two parents). See :hg:`help
4745 working directory must have two parents). See :hg:`help
4742 merge-tools` for information on configuring merge tools.
4746 merge-tools` for information on configuring merge tools.
4743
4747
4744 The resolve command can be used in the following ways:
4748 The resolve command can be used in the following ways:
4745
4749
4746 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4750 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4747 the specified files, discarding any previous merge attempts. Re-merging
4751 the specified files, discarding any previous merge attempts. Re-merging
4748 is not performed for files already marked as resolved. Use ``--all/-a``
4752 is not performed for files already marked as resolved. Use ``--all/-a``
4749 to select all unresolved files. ``--tool`` can be used to specify
4753 to select all unresolved files. ``--tool`` can be used to specify
4750 the merge tool used for the given files. It overrides the HGMERGE
4754 the merge tool used for the given files. It overrides the HGMERGE
4751 environment variable and your configuration files. Previous file
4755 environment variable and your configuration files. Previous file
4752 contents are saved with a ``.orig`` suffix.
4756 contents are saved with a ``.orig`` suffix.
4753
4757
4754 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4758 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4755 (e.g. after having manually fixed-up the files). The default is
4759 (e.g. after having manually fixed-up the files). The default is
4756 to mark all unresolved files.
4760 to mark all unresolved files.
4757
4761
4758 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4762 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4759 default is to mark all resolved files.
4763 default is to mark all resolved files.
4760
4764
4761 - :hg:`resolve -l`: list files which had or still have conflicts.
4765 - :hg:`resolve -l`: list files which had or still have conflicts.
4762 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4766 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4763 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4767 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4764 the list. See :hg:`help filesets` for details.
4768 the list. See :hg:`help filesets` for details.
4765
4769
4766 .. note::
4770 .. note::
4767
4771
4768 Mercurial will not let you commit files with unresolved merge
4772 Mercurial will not let you commit files with unresolved merge
4769 conflicts. You must use :hg:`resolve -m ...` before you can
4773 conflicts. You must use :hg:`resolve -m ...` before you can
4770 commit after a conflicting merge.
4774 commit after a conflicting merge.
4771
4775
4772 .. container:: verbose
4776 .. container:: verbose
4773
4777
4774 Template:
4778 Template:
4775
4779
4776 The following keywords are supported in addition to the common template
4780 The following keywords are supported in addition to the common template
4777 keywords and functions. See also :hg:`help templates`.
4781 keywords and functions. See also :hg:`help templates`.
4778
4782
4779 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4783 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4780 :path: String. Repository-absolute path of the file.
4784 :path: String. Repository-absolute path of the file.
4781
4785
4782 Returns 0 on success, 1 if any files fail a resolve attempt.
4786 Returns 0 on success, 1 if any files fail a resolve attempt.
4783 """
4787 """
4784
4788
4785 opts = pycompat.byteskwargs(opts)
4789 opts = pycompat.byteskwargs(opts)
4786 confirm = ui.configbool('commands', 'resolve.confirm')
4790 confirm = ui.configbool('commands', 'resolve.confirm')
4787 flaglist = 'all mark unmark list no_status re_merge'.split()
4791 flaglist = 'all mark unmark list no_status re_merge'.split()
4788 all, mark, unmark, show, nostatus, remerge = [
4792 all, mark, unmark, show, nostatus, remerge = [
4789 opts.get(o) for o in flaglist]
4793 opts.get(o) for o in flaglist]
4790
4794
4791 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4795 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4792 if actioncount > 1:
4796 if actioncount > 1:
4793 raise error.Abort(_("too many actions specified"))
4797 raise error.Abort(_("too many actions specified"))
4794 elif (actioncount == 0
4798 elif (actioncount == 0
4795 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4799 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4796 hint = _('use --mark, --unmark, --list or --re-merge')
4800 hint = _('use --mark, --unmark, --list or --re-merge')
4797 raise error.Abort(_('no action specified'), hint=hint)
4801 raise error.Abort(_('no action specified'), hint=hint)
4798 if pats and all:
4802 if pats and all:
4799 raise error.Abort(_("can't specify --all and patterns"))
4803 raise error.Abort(_("can't specify --all and patterns"))
4800 if not (all or pats or show or mark or unmark):
4804 if not (all or pats or show or mark or unmark):
4801 raise error.Abort(_('no files or directories specified'),
4805 raise error.Abort(_('no files or directories specified'),
4802 hint=('use --all to re-merge all unresolved files'))
4806 hint=('use --all to re-merge all unresolved files'))
4803
4807
4804 if confirm:
4808 if confirm:
4805 if all:
4809 if all:
4806 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4810 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4807 b'$$ &Yes $$ &No')):
4811 b'$$ &Yes $$ &No')):
4808 raise error.Abort(_('user quit'))
4812 raise error.Abort(_('user quit'))
4809 if mark and not pats:
4813 if mark and not pats:
4810 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4814 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4811 b'$$ &Yes $$ &No')):
4815 b'$$ &Yes $$ &No')):
4812 raise error.Abort(_('user quit'))
4816 raise error.Abort(_('user quit'))
4813 if unmark and not pats:
4817 if unmark and not pats:
4814 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4818 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4815 b'$$ &Yes $$ &No')):
4819 b'$$ &Yes $$ &No')):
4816 raise error.Abort(_('user quit'))
4820 raise error.Abort(_('user quit'))
4817
4821
4818 uipathfn = scmutil.getuipathfn(repo)
4822 uipathfn = scmutil.getuipathfn(repo)
4819
4823
4820 if show:
4824 if show:
4821 ui.pager('resolve')
4825 ui.pager('resolve')
4822 fm = ui.formatter('resolve', opts)
4826 fm = ui.formatter('resolve', opts)
4823 ms = mergemod.mergestate.read(repo)
4827 ms = mergemod.mergestate.read(repo)
4824 wctx = repo[None]
4828 wctx = repo[None]
4825 m = scmutil.match(wctx, pats, opts)
4829 m = scmutil.match(wctx, pats, opts)
4826
4830
4827 # Labels and keys based on merge state. Unresolved path conflicts show
4831 # Labels and keys based on merge state. Unresolved path conflicts show
4828 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4832 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4829 # resolved conflicts.
4833 # resolved conflicts.
4830 mergestateinfo = {
4834 mergestateinfo = {
4831 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4835 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4832 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4836 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4833 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4837 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4834 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4838 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4835 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4839 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4836 'D'),
4840 'D'),
4837 }
4841 }
4838
4842
4839 for f in ms:
4843 for f in ms:
4840 if not m(f):
4844 if not m(f):
4841 continue
4845 continue
4842
4846
4843 label, key = mergestateinfo[ms[f]]
4847 label, key = mergestateinfo[ms[f]]
4844 fm.startitem()
4848 fm.startitem()
4845 fm.context(ctx=wctx)
4849 fm.context(ctx=wctx)
4846 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4850 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4847 fm.data(path=f)
4851 fm.data(path=f)
4848 fm.plain('%s\n' % uipathfn(f), label=label)
4852 fm.plain('%s\n' % uipathfn(f), label=label)
4849 fm.end()
4853 fm.end()
4850 return 0
4854 return 0
4851
4855
4852 with repo.wlock():
4856 with repo.wlock():
4853 ms = mergemod.mergestate.read(repo)
4857 ms = mergemod.mergestate.read(repo)
4854
4858
4855 if not (ms.active() or repo.dirstate.p2() != nullid):
4859 if not (ms.active() or repo.dirstate.p2() != nullid):
4856 raise error.Abort(
4860 raise error.Abort(
4857 _('resolve command not applicable when not merging'))
4861 _('resolve command not applicable when not merging'))
4858
4862
4859 wctx = repo[None]
4863 wctx = repo[None]
4860
4864
4861 if (ms.mergedriver
4865 if (ms.mergedriver
4862 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4866 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4863 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4867 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4864 ms.commit()
4868 ms.commit()
4865 # allow mark and unmark to go through
4869 # allow mark and unmark to go through
4866 if not mark and not unmark and not proceed:
4870 if not mark and not unmark and not proceed:
4867 return 1
4871 return 1
4868
4872
4869 m = scmutil.match(wctx, pats, opts)
4873 m = scmutil.match(wctx, pats, opts)
4870 ret = 0
4874 ret = 0
4871 didwork = False
4875 didwork = False
4872 runconclude = False
4876 runconclude = False
4873
4877
4874 tocomplete = []
4878 tocomplete = []
4875 hasconflictmarkers = []
4879 hasconflictmarkers = []
4876 if mark:
4880 if mark:
4877 markcheck = ui.config('commands', 'resolve.mark-check')
4881 markcheck = ui.config('commands', 'resolve.mark-check')
4878 if markcheck not in ['warn', 'abort']:
4882 if markcheck not in ['warn', 'abort']:
4879 # Treat all invalid / unrecognized values as 'none'.
4883 # Treat all invalid / unrecognized values as 'none'.
4880 markcheck = False
4884 markcheck = False
4881 for f in ms:
4885 for f in ms:
4882 if not m(f):
4886 if not m(f):
4883 continue
4887 continue
4884
4888
4885 didwork = True
4889 didwork = True
4886
4890
4887 # don't let driver-resolved files be marked, and run the conclude
4891 # don't let driver-resolved files be marked, and run the conclude
4888 # step if asked to resolve
4892 # step if asked to resolve
4889 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4893 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4890 exact = m.exact(f)
4894 exact = m.exact(f)
4891 if mark:
4895 if mark:
4892 if exact:
4896 if exact:
4893 ui.warn(_('not marking %s as it is driver-resolved\n')
4897 ui.warn(_('not marking %s as it is driver-resolved\n')
4894 % uipathfn(f))
4898 % uipathfn(f))
4895 elif unmark:
4899 elif unmark:
4896 if exact:
4900 if exact:
4897 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4901 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4898 % uipathfn(f))
4902 % uipathfn(f))
4899 else:
4903 else:
4900 runconclude = True
4904 runconclude = True
4901 continue
4905 continue
4902
4906
4903 # path conflicts must be resolved manually
4907 # path conflicts must be resolved manually
4904 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4908 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4905 mergemod.MERGE_RECORD_RESOLVED_PATH):
4909 mergemod.MERGE_RECORD_RESOLVED_PATH):
4906 if mark:
4910 if mark:
4907 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4911 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4908 elif unmark:
4912 elif unmark:
4909 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4913 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4910 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4914 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4911 ui.warn(_('%s: path conflict must be resolved manually\n')
4915 ui.warn(_('%s: path conflict must be resolved manually\n')
4912 % uipathfn(f))
4916 % uipathfn(f))
4913 continue
4917 continue
4914
4918
4915 if mark:
4919 if mark:
4916 if markcheck:
4920 if markcheck:
4917 fdata = repo.wvfs.tryread(f)
4921 fdata = repo.wvfs.tryread(f)
4918 if (filemerge.hasconflictmarkers(fdata) and
4922 if (filemerge.hasconflictmarkers(fdata) and
4919 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4923 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4920 hasconflictmarkers.append(f)
4924 hasconflictmarkers.append(f)
4921 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4925 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4922 elif unmark:
4926 elif unmark:
4923 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4927 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4924 else:
4928 else:
4925 # backup pre-resolve (merge uses .orig for its own purposes)
4929 # backup pre-resolve (merge uses .orig for its own purposes)
4926 a = repo.wjoin(f)
4930 a = repo.wjoin(f)
4927 try:
4931 try:
4928 util.copyfile(a, a + ".resolve")
4932 util.copyfile(a, a + ".resolve")
4929 except (IOError, OSError) as inst:
4933 except (IOError, OSError) as inst:
4930 if inst.errno != errno.ENOENT:
4934 if inst.errno != errno.ENOENT:
4931 raise
4935 raise
4932
4936
4933 try:
4937 try:
4934 # preresolve file
4938 # preresolve file
4935 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4939 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4936 with ui.configoverride(overrides, 'resolve'):
4940 with ui.configoverride(overrides, 'resolve'):
4937 complete, r = ms.preresolve(f, wctx)
4941 complete, r = ms.preresolve(f, wctx)
4938 if not complete:
4942 if not complete:
4939 tocomplete.append(f)
4943 tocomplete.append(f)
4940 elif r:
4944 elif r:
4941 ret = 1
4945 ret = 1
4942 finally:
4946 finally:
4943 ms.commit()
4947 ms.commit()
4944
4948
4945 # replace filemerge's .orig file with our resolve file, but only
4949 # replace filemerge's .orig file with our resolve file, but only
4946 # for merges that are complete
4950 # for merges that are complete
4947 if complete:
4951 if complete:
4948 try:
4952 try:
4949 util.rename(a + ".resolve",
4953 util.rename(a + ".resolve",
4950 scmutil.backuppath(ui, repo, f))
4954 scmutil.backuppath(ui, repo, f))
4951 except OSError as inst:
4955 except OSError as inst:
4952 if inst.errno != errno.ENOENT:
4956 if inst.errno != errno.ENOENT:
4953 raise
4957 raise
4954
4958
4955 if hasconflictmarkers:
4959 if hasconflictmarkers:
4956 ui.warn(_('warning: the following files still have conflict '
4960 ui.warn(_('warning: the following files still have conflict '
4957 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4961 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4958 for f in hasconflictmarkers))
4962 for f in hasconflictmarkers))
4959 if markcheck == 'abort' and not all and not pats:
4963 if markcheck == 'abort' and not all and not pats:
4960 raise error.Abort(_('conflict markers detected'),
4964 raise error.Abort(_('conflict markers detected'),
4961 hint=_('use --all to mark anyway'))
4965 hint=_('use --all to mark anyway'))
4962
4966
4963 for f in tocomplete:
4967 for f in tocomplete:
4964 try:
4968 try:
4965 # resolve file
4969 # resolve file
4966 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4970 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4967 with ui.configoverride(overrides, 'resolve'):
4971 with ui.configoverride(overrides, 'resolve'):
4968 r = ms.resolve(f, wctx)
4972 r = ms.resolve(f, wctx)
4969 if r:
4973 if r:
4970 ret = 1
4974 ret = 1
4971 finally:
4975 finally:
4972 ms.commit()
4976 ms.commit()
4973
4977
4974 # replace filemerge's .orig file with our resolve file
4978 # replace filemerge's .orig file with our resolve file
4975 a = repo.wjoin(f)
4979 a = repo.wjoin(f)
4976 try:
4980 try:
4977 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
4981 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
4978 except OSError as inst:
4982 except OSError as inst:
4979 if inst.errno != errno.ENOENT:
4983 if inst.errno != errno.ENOENT:
4980 raise
4984 raise
4981
4985
4982 ms.commit()
4986 ms.commit()
4983 ms.recordactions()
4987 ms.recordactions()
4984
4988
4985 if not didwork and pats:
4989 if not didwork and pats:
4986 hint = None
4990 hint = None
4987 if not any([p for p in pats if p.find(':') >= 0]):
4991 if not any([p for p in pats if p.find(':') >= 0]):
4988 pats = ['path:%s' % p for p in pats]
4992 pats = ['path:%s' % p for p in pats]
4989 m = scmutil.match(wctx, pats, opts)
4993 m = scmutil.match(wctx, pats, opts)
4990 for f in ms:
4994 for f in ms:
4991 if not m(f):
4995 if not m(f):
4992 continue
4996 continue
4993 def flag(o):
4997 def flag(o):
4994 if o == 're_merge':
4998 if o == 're_merge':
4995 return '--re-merge '
4999 return '--re-merge '
4996 return '-%s ' % o[0:1]
5000 return '-%s ' % o[0:1]
4997 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5001 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
4998 hint = _("(try: hg resolve %s%s)\n") % (
5002 hint = _("(try: hg resolve %s%s)\n") % (
4999 flags,
5003 flags,
5000 ' '.join(pats))
5004 ' '.join(pats))
5001 break
5005 break
5002 ui.warn(_("arguments do not match paths that need resolving\n"))
5006 ui.warn(_("arguments do not match paths that need resolving\n"))
5003 if hint:
5007 if hint:
5004 ui.warn(hint)
5008 ui.warn(hint)
5005 elif ms.mergedriver and ms.mdstate() != 's':
5009 elif ms.mergedriver and ms.mdstate() != 's':
5006 # run conclude step when either a driver-resolved file is requested
5010 # run conclude step when either a driver-resolved file is requested
5007 # or there are no driver-resolved files
5011 # or there are no driver-resolved files
5008 # we can't use 'ret' to determine whether any files are unresolved
5012 # we can't use 'ret' to determine whether any files are unresolved
5009 # because we might not have tried to resolve some
5013 # because we might not have tried to resolve some
5010 if ((runconclude or not list(ms.driverresolved()))
5014 if ((runconclude or not list(ms.driverresolved()))
5011 and not list(ms.unresolved())):
5015 and not list(ms.unresolved())):
5012 proceed = mergemod.driverconclude(repo, ms, wctx)
5016 proceed = mergemod.driverconclude(repo, ms, wctx)
5013 ms.commit()
5017 ms.commit()
5014 if not proceed:
5018 if not proceed:
5015 return 1
5019 return 1
5016
5020
5017 # Nudge users into finishing an unfinished operation
5021 # Nudge users into finishing an unfinished operation
5018 unresolvedf = list(ms.unresolved())
5022 unresolvedf = list(ms.unresolved())
5019 driverresolvedf = list(ms.driverresolved())
5023 driverresolvedf = list(ms.driverresolved())
5020 if not unresolvedf and not driverresolvedf:
5024 if not unresolvedf and not driverresolvedf:
5021 ui.status(_('(no more unresolved files)\n'))
5025 ui.status(_('(no more unresolved files)\n'))
5022 cmdutil.checkafterresolved(repo)
5026 cmdutil.checkafterresolved(repo)
5023 elif not unresolvedf:
5027 elif not unresolvedf:
5024 ui.status(_('(no more unresolved files -- '
5028 ui.status(_('(no more unresolved files -- '
5025 'run "hg resolve --all" to conclude)\n'))
5029 'run "hg resolve --all" to conclude)\n'))
5026
5030
5027 return ret
5031 return ret
5028
5032
5029 @command('revert',
5033 @command('revert',
5030 [('a', 'all', None, _('revert all changes when no arguments given')),
5034 [('a', 'all', None, _('revert all changes when no arguments given')),
5031 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5035 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5032 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5036 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5033 ('C', 'no-backup', None, _('do not save backup copies of files')),
5037 ('C', 'no-backup', None, _('do not save backup copies of files')),
5034 ('i', 'interactive', None, _('interactively select the changes')),
5038 ('i', 'interactive', None, _('interactively select the changes')),
5035 ] + walkopts + dryrunopts,
5039 ] + walkopts + dryrunopts,
5036 _('[OPTION]... [-r REV] [NAME]...'),
5040 _('[OPTION]... [-r REV] [NAME]...'),
5037 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5041 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5038 def revert(ui, repo, *pats, **opts):
5042 def revert(ui, repo, *pats, **opts):
5039 """restore files to their checkout state
5043 """restore files to their checkout state
5040
5044
5041 .. note::
5045 .. note::
5042
5046
5043 To check out earlier revisions, you should use :hg:`update REV`.
5047 To check out earlier revisions, you should use :hg:`update REV`.
5044 To cancel an uncommitted merge (and lose your changes),
5048 To cancel an uncommitted merge (and lose your changes),
5045 use :hg:`merge --abort`.
5049 use :hg:`merge --abort`.
5046
5050
5047 With no revision specified, revert the specified files or directories
5051 With no revision specified, revert the specified files or directories
5048 to the contents they had in the parent of the working directory.
5052 to the contents they had in the parent of the working directory.
5049 This restores the contents of files to an unmodified
5053 This restores the contents of files to an unmodified
5050 state and unschedules adds, removes, copies, and renames. If the
5054 state and unschedules adds, removes, copies, and renames. If the
5051 working directory has two parents, you must explicitly specify a
5055 working directory has two parents, you must explicitly specify a
5052 revision.
5056 revision.
5053
5057
5054 Using the -r/--rev or -d/--date options, revert the given files or
5058 Using the -r/--rev or -d/--date options, revert the given files or
5055 directories to their states as of a specific revision. Because
5059 directories to their states as of a specific revision. Because
5056 revert does not change the working directory parents, this will
5060 revert does not change the working directory parents, this will
5057 cause these files to appear modified. This can be helpful to "back
5061 cause these files to appear modified. This can be helpful to "back
5058 out" some or all of an earlier change. See :hg:`backout` for a
5062 out" some or all of an earlier change. See :hg:`backout` for a
5059 related method.
5063 related method.
5060
5064
5061 Modified files are saved with a .orig suffix before reverting.
5065 Modified files are saved with a .orig suffix before reverting.
5062 To disable these backups, use --no-backup. It is possible to store
5066 To disable these backups, use --no-backup. It is possible to store
5063 the backup files in a custom directory relative to the root of the
5067 the backup files in a custom directory relative to the root of the
5064 repository by setting the ``ui.origbackuppath`` configuration
5068 repository by setting the ``ui.origbackuppath`` configuration
5065 option.
5069 option.
5066
5070
5067 See :hg:`help dates` for a list of formats valid for -d/--date.
5071 See :hg:`help dates` for a list of formats valid for -d/--date.
5068
5072
5069 See :hg:`help backout` for a way to reverse the effect of an
5073 See :hg:`help backout` for a way to reverse the effect of an
5070 earlier changeset.
5074 earlier changeset.
5071
5075
5072 Returns 0 on success.
5076 Returns 0 on success.
5073 """
5077 """
5074
5078
5075 opts = pycompat.byteskwargs(opts)
5079 opts = pycompat.byteskwargs(opts)
5076 if opts.get("date"):
5080 if opts.get("date"):
5077 if opts.get("rev"):
5081 if opts.get("rev"):
5078 raise error.Abort(_("you can't specify a revision and a date"))
5082 raise error.Abort(_("you can't specify a revision and a date"))
5079 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5083 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5080
5084
5081 parent, p2 = repo.dirstate.parents()
5085 parent, p2 = repo.dirstate.parents()
5082 if not opts.get('rev') and p2 != nullid:
5086 if not opts.get('rev') and p2 != nullid:
5083 # revert after merge is a trap for new users (issue2915)
5087 # revert after merge is a trap for new users (issue2915)
5084 raise error.Abort(_('uncommitted merge with no revision specified'),
5088 raise error.Abort(_('uncommitted merge with no revision specified'),
5085 hint=_("use 'hg update' or see 'hg help revert'"))
5089 hint=_("use 'hg update' or see 'hg help revert'"))
5086
5090
5087 rev = opts.get('rev')
5091 rev = opts.get('rev')
5088 if rev:
5092 if rev:
5089 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5093 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5090 ctx = scmutil.revsingle(repo, rev)
5094 ctx = scmutil.revsingle(repo, rev)
5091
5095
5092 if (not (pats or opts.get('include') or opts.get('exclude') or
5096 if (not (pats or opts.get('include') or opts.get('exclude') or
5093 opts.get('all') or opts.get('interactive'))):
5097 opts.get('all') or opts.get('interactive'))):
5094 msg = _("no files or directories specified")
5098 msg = _("no files or directories specified")
5095 if p2 != nullid:
5099 if p2 != nullid:
5096 hint = _("uncommitted merge, use --all to discard all changes,"
5100 hint = _("uncommitted merge, use --all to discard all changes,"
5097 " or 'hg update -C .' to abort the merge")
5101 " or 'hg update -C .' to abort the merge")
5098 raise error.Abort(msg, hint=hint)
5102 raise error.Abort(msg, hint=hint)
5099 dirty = any(repo.status())
5103 dirty = any(repo.status())
5100 node = ctx.node()
5104 node = ctx.node()
5101 if node != parent:
5105 if node != parent:
5102 if dirty:
5106 if dirty:
5103 hint = _("uncommitted changes, use --all to discard all"
5107 hint = _("uncommitted changes, use --all to discard all"
5104 " changes, or 'hg update %d' to update") % ctx.rev()
5108 " changes, or 'hg update %d' to update") % ctx.rev()
5105 else:
5109 else:
5106 hint = _("use --all to revert all files,"
5110 hint = _("use --all to revert all files,"
5107 " or 'hg update %d' to update") % ctx.rev()
5111 " or 'hg update %d' to update") % ctx.rev()
5108 elif dirty:
5112 elif dirty:
5109 hint = _("uncommitted changes, use --all to discard all changes")
5113 hint = _("uncommitted changes, use --all to discard all changes")
5110 else:
5114 else:
5111 hint = _("use --all to revert all files")
5115 hint = _("use --all to revert all files")
5112 raise error.Abort(msg, hint=hint)
5116 raise error.Abort(msg, hint=hint)
5113
5117
5114 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5118 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5115 **pycompat.strkwargs(opts))
5119 **pycompat.strkwargs(opts))
5116
5120
5117 @command(
5121 @command(
5118 'rollback',
5122 'rollback',
5119 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5123 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5120 helpcategory=command.CATEGORY_MAINTENANCE)
5124 helpcategory=command.CATEGORY_MAINTENANCE)
5121 def rollback(ui, repo, **opts):
5125 def rollback(ui, repo, **opts):
5122 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5126 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5123
5127
5124 Please use :hg:`commit --amend` instead of rollback to correct
5128 Please use :hg:`commit --amend` instead of rollback to correct
5125 mistakes in the last commit.
5129 mistakes in the last commit.
5126
5130
5127 This command should be used with care. There is only one level of
5131 This command should be used with care. There is only one level of
5128 rollback, and there is no way to undo a rollback. It will also
5132 rollback, and there is no way to undo a rollback. It will also
5129 restore the dirstate at the time of the last transaction, losing
5133 restore the dirstate at the time of the last transaction, losing
5130 any dirstate changes since that time. This command does not alter
5134 any dirstate changes since that time. This command does not alter
5131 the working directory.
5135 the working directory.
5132
5136
5133 Transactions are used to encapsulate the effects of all commands
5137 Transactions are used to encapsulate the effects of all commands
5134 that create new changesets or propagate existing changesets into a
5138 that create new changesets or propagate existing changesets into a
5135 repository.
5139 repository.
5136
5140
5137 .. container:: verbose
5141 .. container:: verbose
5138
5142
5139 For example, the following commands are transactional, and their
5143 For example, the following commands are transactional, and their
5140 effects can be rolled back:
5144 effects can be rolled back:
5141
5145
5142 - commit
5146 - commit
5143 - import
5147 - import
5144 - pull
5148 - pull
5145 - push (with this repository as the destination)
5149 - push (with this repository as the destination)
5146 - unbundle
5150 - unbundle
5147
5151
5148 To avoid permanent data loss, rollback will refuse to rollback a
5152 To avoid permanent data loss, rollback will refuse to rollback a
5149 commit transaction if it isn't checked out. Use --force to
5153 commit transaction if it isn't checked out. Use --force to
5150 override this protection.
5154 override this protection.
5151
5155
5152 The rollback command can be entirely disabled by setting the
5156 The rollback command can be entirely disabled by setting the
5153 ``ui.rollback`` configuration setting to false. If you're here
5157 ``ui.rollback`` configuration setting to false. If you're here
5154 because you want to use rollback and it's disabled, you can
5158 because you want to use rollback and it's disabled, you can
5155 re-enable the command by setting ``ui.rollback`` to true.
5159 re-enable the command by setting ``ui.rollback`` to true.
5156
5160
5157 This command is not intended for use on public repositories. Once
5161 This command is not intended for use on public repositories. Once
5158 changes are visible for pull by other users, rolling a transaction
5162 changes are visible for pull by other users, rolling a transaction
5159 back locally is ineffective (someone else may already have pulled
5163 back locally is ineffective (someone else may already have pulled
5160 the changes). Furthermore, a race is possible with readers of the
5164 the changes). Furthermore, a race is possible with readers of the
5161 repository; for example an in-progress pull from the repository
5165 repository; for example an in-progress pull from the repository
5162 may fail if a rollback is performed.
5166 may fail if a rollback is performed.
5163
5167
5164 Returns 0 on success, 1 if no rollback data is available.
5168 Returns 0 on success, 1 if no rollback data is available.
5165 """
5169 """
5166 if not ui.configbool('ui', 'rollback'):
5170 if not ui.configbool('ui', 'rollback'):
5167 raise error.Abort(_('rollback is disabled because it is unsafe'),
5171 raise error.Abort(_('rollback is disabled because it is unsafe'),
5168 hint=('see `hg help -v rollback` for information'))
5172 hint=('see `hg help -v rollback` for information'))
5169 return repo.rollback(dryrun=opts.get(r'dry_run'),
5173 return repo.rollback(dryrun=opts.get(r'dry_run'),
5170 force=opts.get(r'force'))
5174 force=opts.get(r'force'))
5171
5175
5172 @command(
5176 @command(
5173 'root', [] + formatteropts, intents={INTENT_READONLY},
5177 'root', [] + formatteropts, intents={INTENT_READONLY},
5174 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5178 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5175 def root(ui, repo, **opts):
5179 def root(ui, repo, **opts):
5176 """print the root (top) of the current working directory
5180 """print the root (top) of the current working directory
5177
5181
5178 Print the root directory of the current repository.
5182 Print the root directory of the current repository.
5179
5183
5180 .. container:: verbose
5184 .. container:: verbose
5181
5185
5182 Template:
5186 Template:
5183
5187
5184 The following keywords are supported in addition to the common template
5188 The following keywords are supported in addition to the common template
5185 keywords and functions. See also :hg:`help templates`.
5189 keywords and functions. See also :hg:`help templates`.
5186
5190
5187 :hgpath: String. Path to the .hg directory.
5191 :hgpath: String. Path to the .hg directory.
5188 :storepath: String. Path to the directory holding versioned data.
5192 :storepath: String. Path to the directory holding versioned data.
5189
5193
5190 Returns 0 on success.
5194 Returns 0 on success.
5191 """
5195 """
5192 opts = pycompat.byteskwargs(opts)
5196 opts = pycompat.byteskwargs(opts)
5193 with ui.formatter('root', opts) as fm:
5197 with ui.formatter('root', opts) as fm:
5194 fm.startitem()
5198 fm.startitem()
5195 fm.write('reporoot', '%s\n', repo.root)
5199 fm.write('reporoot', '%s\n', repo.root)
5196 fm.data(hgpath=repo.path, storepath=repo.spath)
5200 fm.data(hgpath=repo.path, storepath=repo.spath)
5197
5201
5198 @command('serve',
5202 @command('serve',
5199 [('A', 'accesslog', '', _('name of access log file to write to'),
5203 [('A', 'accesslog', '', _('name of access log file to write to'),
5200 _('FILE')),
5204 _('FILE')),
5201 ('d', 'daemon', None, _('run server in background')),
5205 ('d', 'daemon', None, _('run server in background')),
5202 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5206 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5203 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5207 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5204 # use string type, then we can check if something was passed
5208 # use string type, then we can check if something was passed
5205 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5209 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5206 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5210 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5207 _('ADDR')),
5211 _('ADDR')),
5208 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5212 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5209 _('PREFIX')),
5213 _('PREFIX')),
5210 ('n', 'name', '',
5214 ('n', 'name', '',
5211 _('name to show in web pages (default: working directory)'), _('NAME')),
5215 _('name to show in web pages (default: working directory)'), _('NAME')),
5212 ('', 'web-conf', '',
5216 ('', 'web-conf', '',
5213 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5217 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5214 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5218 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5215 _('FILE')),
5219 _('FILE')),
5216 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5220 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5217 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5221 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5218 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5222 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5219 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5223 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5220 ('', 'style', '', _('template style to use'), _('STYLE')),
5224 ('', 'style', '', _('template style to use'), _('STYLE')),
5221 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5225 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5222 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5226 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5223 ('', 'print-url', None, _('start and print only the URL'))]
5227 ('', 'print-url', None, _('start and print only the URL'))]
5224 + subrepoopts,
5228 + subrepoopts,
5225 _('[OPTION]...'),
5229 _('[OPTION]...'),
5226 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5230 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5227 helpbasic=True, optionalrepo=True)
5231 helpbasic=True, optionalrepo=True)
5228 def serve(ui, repo, **opts):
5232 def serve(ui, repo, **opts):
5229 """start stand-alone webserver
5233 """start stand-alone webserver
5230
5234
5231 Start a local HTTP repository browser and pull server. You can use
5235 Start a local HTTP repository browser and pull server. You can use
5232 this for ad-hoc sharing and browsing of repositories. It is
5236 this for ad-hoc sharing and browsing of repositories. It is
5233 recommended to use a real web server to serve a repository for
5237 recommended to use a real web server to serve a repository for
5234 longer periods of time.
5238 longer periods of time.
5235
5239
5236 Please note that the server does not implement access control.
5240 Please note that the server does not implement access control.
5237 This means that, by default, anybody can read from the server and
5241 This means that, by default, anybody can read from the server and
5238 nobody can write to it by default. Set the ``web.allow-push``
5242 nobody can write to it by default. Set the ``web.allow-push``
5239 option to ``*`` to allow everybody to push to the server. You
5243 option to ``*`` to allow everybody to push to the server. You
5240 should use a real web server if you need to authenticate users.
5244 should use a real web server if you need to authenticate users.
5241
5245
5242 By default, the server logs accesses to stdout and errors to
5246 By default, the server logs accesses to stdout and errors to
5243 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5247 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5244 files.
5248 files.
5245
5249
5246 To have the server choose a free port number to listen on, specify
5250 To have the server choose a free port number to listen on, specify
5247 a port number of 0; in this case, the server will print the port
5251 a port number of 0; in this case, the server will print the port
5248 number it uses.
5252 number it uses.
5249
5253
5250 Returns 0 on success.
5254 Returns 0 on success.
5251 """
5255 """
5252
5256
5253 opts = pycompat.byteskwargs(opts)
5257 opts = pycompat.byteskwargs(opts)
5254 if opts["stdio"] and opts["cmdserver"]:
5258 if opts["stdio"] and opts["cmdserver"]:
5255 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5259 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5256 if opts["print_url"] and ui.verbose:
5260 if opts["print_url"] and ui.verbose:
5257 raise error.Abort(_("cannot use --print-url with --verbose"))
5261 raise error.Abort(_("cannot use --print-url with --verbose"))
5258
5262
5259 if opts["stdio"]:
5263 if opts["stdio"]:
5260 if repo is None:
5264 if repo is None:
5261 raise error.RepoError(_("there is no Mercurial repository here"
5265 raise error.RepoError(_("there is no Mercurial repository here"
5262 " (.hg not found)"))
5266 " (.hg not found)"))
5263 s = wireprotoserver.sshserver(ui, repo)
5267 s = wireprotoserver.sshserver(ui, repo)
5264 s.serve_forever()
5268 s.serve_forever()
5265
5269
5266 service = server.createservice(ui, repo, opts)
5270 service = server.createservice(ui, repo, opts)
5267 return server.runservice(opts, initfn=service.init, runfn=service.run)
5271 return server.runservice(opts, initfn=service.init, runfn=service.run)
5268
5272
5269 @command('shelve',
5273 @command('shelve',
5270 [('A', 'addremove', None,
5274 [('A', 'addremove', None,
5271 _('mark new/missing files as added/removed before shelving')),
5275 _('mark new/missing files as added/removed before shelving')),
5272 ('u', 'unknown', None,
5276 ('u', 'unknown', None,
5273 _('store unknown files in the shelve')),
5277 _('store unknown files in the shelve')),
5274 ('', 'cleanup', None,
5278 ('', 'cleanup', None,
5275 _('delete all shelved changes')),
5279 _('delete all shelved changes')),
5276 ('', 'date', '',
5280 ('', 'date', '',
5277 _('shelve with the specified commit date'), _('DATE')),
5281 _('shelve with the specified commit date'), _('DATE')),
5278 ('d', 'delete', None,
5282 ('d', 'delete', None,
5279 _('delete the named shelved change(s)')),
5283 _('delete the named shelved change(s)')),
5280 ('e', 'edit', False,
5284 ('e', 'edit', False,
5281 _('invoke editor on commit messages')),
5285 _('invoke editor on commit messages')),
5282 ('k', 'keep', False,
5286 ('k', 'keep', False,
5283 _('shelve, but keep changes in the working directory')),
5287 _('shelve, but keep changes in the working directory')),
5284 ('l', 'list', None,
5288 ('l', 'list', None,
5285 _('list current shelves')),
5289 _('list current shelves')),
5286 ('m', 'message', '',
5290 ('m', 'message', '',
5287 _('use text as shelve message'), _('TEXT')),
5291 _('use text as shelve message'), _('TEXT')),
5288 ('n', 'name', '',
5292 ('n', 'name', '',
5289 _('use the given name for the shelved commit'), _('NAME')),
5293 _('use the given name for the shelved commit'), _('NAME')),
5290 ('p', 'patch', None,
5294 ('p', 'patch', None,
5291 _('output patches for changes (provide the names of the shelved '
5295 _('output patches for changes (provide the names of the shelved '
5292 'changes as positional arguments)')),
5296 'changes as positional arguments)')),
5293 ('i', 'interactive', None,
5297 ('i', 'interactive', None,
5294 _('interactive mode, only works while creating a shelve')),
5298 _('interactive mode, only works while creating a shelve')),
5295 ('', 'stat', None,
5299 ('', 'stat', None,
5296 _('output diffstat-style summary of changes (provide the names of '
5300 _('output diffstat-style summary of changes (provide the names of '
5297 'the shelved changes as positional arguments)')
5301 'the shelved changes as positional arguments)')
5298 )] + cmdutil.walkopts,
5302 )] + cmdutil.walkopts,
5299 _('hg shelve [OPTION]... [FILE]...'),
5303 _('hg shelve [OPTION]... [FILE]...'),
5300 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5304 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5301 def shelve(ui, repo, *pats, **opts):
5305 def shelve(ui, repo, *pats, **opts):
5302 '''save and set aside changes from the working directory
5306 '''save and set aside changes from the working directory
5303
5307
5304 Shelving takes files that "hg status" reports as not clean, saves
5308 Shelving takes files that "hg status" reports as not clean, saves
5305 the modifications to a bundle (a shelved change), and reverts the
5309 the modifications to a bundle (a shelved change), and reverts the
5306 files so that their state in the working directory becomes clean.
5310 files so that their state in the working directory becomes clean.
5307
5311
5308 To restore these changes to the working directory, using "hg
5312 To restore these changes to the working directory, using "hg
5309 unshelve"; this will work even if you switch to a different
5313 unshelve"; this will work even if you switch to a different
5310 commit.
5314 commit.
5311
5315
5312 When no files are specified, "hg shelve" saves all not-clean
5316 When no files are specified, "hg shelve" saves all not-clean
5313 files. If specific files or directories are named, only changes to
5317 files. If specific files or directories are named, only changes to
5314 those files are shelved.
5318 those files are shelved.
5315
5319
5316 In bare shelve (when no files are specified, without interactive,
5320 In bare shelve (when no files are specified, without interactive,
5317 include and exclude option), shelving remembers information if the
5321 include and exclude option), shelving remembers information if the
5318 working directory was on newly created branch, in other words working
5322 working directory was on newly created branch, in other words working
5319 directory was on different branch than its first parent. In this
5323 directory was on different branch than its first parent. In this
5320 situation unshelving restores branch information to the working directory.
5324 situation unshelving restores branch information to the working directory.
5321
5325
5322 Each shelved change has a name that makes it easier to find later.
5326 Each shelved change has a name that makes it easier to find later.
5323 The name of a shelved change defaults to being based on the active
5327 The name of a shelved change defaults to being based on the active
5324 bookmark, or if there is no active bookmark, the current named
5328 bookmark, or if there is no active bookmark, the current named
5325 branch. To specify a different name, use ``--name``.
5329 branch. To specify a different name, use ``--name``.
5326
5330
5327 To see a list of existing shelved changes, use the ``--list``
5331 To see a list of existing shelved changes, use the ``--list``
5328 option. For each shelved change, this will print its name, age,
5332 option. For each shelved change, this will print its name, age,
5329 and description; use ``--patch`` or ``--stat`` for more details.
5333 and description; use ``--patch`` or ``--stat`` for more details.
5330
5334
5331 To delete specific shelved changes, use ``--delete``. To delete
5335 To delete specific shelved changes, use ``--delete``. To delete
5332 all shelved changes, use ``--cleanup``.
5336 all shelved changes, use ``--cleanup``.
5333 '''
5337 '''
5334 opts = pycompat.byteskwargs(opts)
5338 opts = pycompat.byteskwargs(opts)
5335 allowables = [
5339 allowables = [
5336 ('addremove', {'create'}), # 'create' is pseudo action
5340 ('addremove', {'create'}), # 'create' is pseudo action
5337 ('unknown', {'create'}),
5341 ('unknown', {'create'}),
5338 ('cleanup', {'cleanup'}),
5342 ('cleanup', {'cleanup'}),
5339 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
5343 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
5340 ('delete', {'delete'}),
5344 ('delete', {'delete'}),
5341 ('edit', {'create'}),
5345 ('edit', {'create'}),
5342 ('keep', {'create'}),
5346 ('keep', {'create'}),
5343 ('list', {'list'}),
5347 ('list', {'list'}),
5344 ('message', {'create'}),
5348 ('message', {'create'}),
5345 ('name', {'create'}),
5349 ('name', {'create'}),
5346 ('patch', {'patch', 'list'}),
5350 ('patch', {'patch', 'list'}),
5347 ('stat', {'stat', 'list'}),
5351 ('stat', {'stat', 'list'}),
5348 ]
5352 ]
5349 def checkopt(opt):
5353 def checkopt(opt):
5350 if opts.get(opt):
5354 if opts.get(opt):
5351 for i, allowable in allowables:
5355 for i, allowable in allowables:
5352 if opts[i] and opt not in allowable:
5356 if opts[i] and opt not in allowable:
5353 raise error.Abort(_("options '--%s' and '--%s' may not be "
5357 raise error.Abort(_("options '--%s' and '--%s' may not be "
5354 "used together") % (opt, i))
5358 "used together") % (opt, i))
5355 return True
5359 return True
5356 if checkopt('cleanup'):
5360 if checkopt('cleanup'):
5357 if pats:
5361 if pats:
5358 raise error.Abort(_("cannot specify names when using '--cleanup'"))
5362 raise error.Abort(_("cannot specify names when using '--cleanup'"))
5359 return shelvemod.cleanupcmd(ui, repo)
5363 return shelvemod.cleanupcmd(ui, repo)
5360 elif checkopt('delete'):
5364 elif checkopt('delete'):
5361 return shelvemod.deletecmd(ui, repo, pats)
5365 return shelvemod.deletecmd(ui, repo, pats)
5362 elif checkopt('list'):
5366 elif checkopt('list'):
5363 return shelvemod.listcmd(ui, repo, pats, opts)
5367 return shelvemod.listcmd(ui, repo, pats, opts)
5364 elif checkopt('patch') or checkopt('stat'):
5368 elif checkopt('patch') or checkopt('stat'):
5365 return shelvemod.patchcmds(ui, repo, pats, opts)
5369 return shelvemod.patchcmds(ui, repo, pats, opts)
5366 else:
5370 else:
5367 return shelvemod.createcmd(ui, repo, pats, opts)
5371 return shelvemod.createcmd(ui, repo, pats, opts)
5368
5372
5369 _NOTTERSE = 'nothing'
5373 _NOTTERSE = 'nothing'
5370
5374
5371 @command('status|st',
5375 @command('status|st',
5372 [('A', 'all', None, _('show status of all files')),
5376 [('A', 'all', None, _('show status of all files')),
5373 ('m', 'modified', None, _('show only modified files')),
5377 ('m', 'modified', None, _('show only modified files')),
5374 ('a', 'added', None, _('show only added files')),
5378 ('a', 'added', None, _('show only added files')),
5375 ('r', 'removed', None, _('show only removed files')),
5379 ('r', 'removed', None, _('show only removed files')),
5376 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5380 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5377 ('c', 'clean', None, _('show only files without changes')),
5381 ('c', 'clean', None, _('show only files without changes')),
5378 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5382 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5379 ('i', 'ignored', None, _('show only ignored files')),
5383 ('i', 'ignored', None, _('show only ignored files')),
5380 ('n', 'no-status', None, _('hide status prefix')),
5384 ('n', 'no-status', None, _('hide status prefix')),
5381 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5385 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5382 ('C', 'copies', None, _('show source of copied files')),
5386 ('C', 'copies', None, _('show source of copied files')),
5383 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5387 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5384 ('', 'rev', [], _('show difference from revision'), _('REV')),
5388 ('', 'rev', [], _('show difference from revision'), _('REV')),
5385 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5389 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5386 ] + walkopts + subrepoopts + formatteropts,
5390 ] + walkopts + subrepoopts + formatteropts,
5387 _('[OPTION]... [FILE]...'),
5391 _('[OPTION]... [FILE]...'),
5388 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5392 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5389 helpbasic=True, inferrepo=True,
5393 helpbasic=True, inferrepo=True,
5390 intents={INTENT_READONLY})
5394 intents={INTENT_READONLY})
5391 def status(ui, repo, *pats, **opts):
5395 def status(ui, repo, *pats, **opts):
5392 """show changed files in the working directory
5396 """show changed files in the working directory
5393
5397
5394 Show status of files in the repository. If names are given, only
5398 Show status of files in the repository. If names are given, only
5395 files that match are shown. Files that are clean or ignored or
5399 files that match are shown. Files that are clean or ignored or
5396 the source of a copy/move operation, are not listed unless
5400 the source of a copy/move operation, are not listed unless
5397 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5401 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5398 Unless options described with "show only ..." are given, the
5402 Unless options described with "show only ..." are given, the
5399 options -mardu are used.
5403 options -mardu are used.
5400
5404
5401 Option -q/--quiet hides untracked (unknown and ignored) files
5405 Option -q/--quiet hides untracked (unknown and ignored) files
5402 unless explicitly requested with -u/--unknown or -i/--ignored.
5406 unless explicitly requested with -u/--unknown or -i/--ignored.
5403
5407
5404 .. note::
5408 .. note::
5405
5409
5406 :hg:`status` may appear to disagree with diff if permissions have
5410 :hg:`status` may appear to disagree with diff if permissions have
5407 changed or a merge has occurred. The standard diff format does
5411 changed or a merge has occurred. The standard diff format does
5408 not report permission changes and diff only reports changes
5412 not report permission changes and diff only reports changes
5409 relative to one merge parent.
5413 relative to one merge parent.
5410
5414
5411 If one revision is given, it is used as the base revision.
5415 If one revision is given, it is used as the base revision.
5412 If two revisions are given, the differences between them are
5416 If two revisions are given, the differences between them are
5413 shown. The --change option can also be used as a shortcut to list
5417 shown. The --change option can also be used as a shortcut to list
5414 the changed files of a revision from its first parent.
5418 the changed files of a revision from its first parent.
5415
5419
5416 The codes used to show the status of files are::
5420 The codes used to show the status of files are::
5417
5421
5418 M = modified
5422 M = modified
5419 A = added
5423 A = added
5420 R = removed
5424 R = removed
5421 C = clean
5425 C = clean
5422 ! = missing (deleted by non-hg command, but still tracked)
5426 ! = missing (deleted by non-hg command, but still tracked)
5423 ? = not tracked
5427 ? = not tracked
5424 I = ignored
5428 I = ignored
5425 = origin of the previous file (with --copies)
5429 = origin of the previous file (with --copies)
5426
5430
5427 .. container:: verbose
5431 .. container:: verbose
5428
5432
5429 The -t/--terse option abbreviates the output by showing only the directory
5433 The -t/--terse option abbreviates the output by showing only the directory
5430 name if all the files in it share the same status. The option takes an
5434 name if all the files in it share the same status. The option takes an
5431 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5435 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5432 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5436 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5433 for 'ignored' and 'c' for clean.
5437 for 'ignored' and 'c' for clean.
5434
5438
5435 It abbreviates only those statuses which are passed. Note that clean and
5439 It abbreviates only those statuses which are passed. Note that clean and
5436 ignored files are not displayed with '--terse ic' unless the -c/--clean
5440 ignored files are not displayed with '--terse ic' unless the -c/--clean
5437 and -i/--ignored options are also used.
5441 and -i/--ignored options are also used.
5438
5442
5439 The -v/--verbose option shows information when the repository is in an
5443 The -v/--verbose option shows information when the repository is in an
5440 unfinished merge, shelve, rebase state etc. You can have this behavior
5444 unfinished merge, shelve, rebase state etc. You can have this behavior
5441 turned on by default by enabling the ``commands.status.verbose`` option.
5445 turned on by default by enabling the ``commands.status.verbose`` option.
5442
5446
5443 You can skip displaying some of these states by setting
5447 You can skip displaying some of these states by setting
5444 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5448 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5445 'histedit', 'merge', 'rebase', or 'unshelve'.
5449 'histedit', 'merge', 'rebase', or 'unshelve'.
5446
5450
5447 Template:
5451 Template:
5448
5452
5449 The following keywords are supported in addition to the common template
5453 The following keywords are supported in addition to the common template
5450 keywords and functions. See also :hg:`help templates`.
5454 keywords and functions. See also :hg:`help templates`.
5451
5455
5452 :path: String. Repository-absolute path of the file.
5456 :path: String. Repository-absolute path of the file.
5453 :source: String. Repository-absolute path of the file originated from.
5457 :source: String. Repository-absolute path of the file originated from.
5454 Available if ``--copies`` is specified.
5458 Available if ``--copies`` is specified.
5455 :status: String. Character denoting file's status.
5459 :status: String. Character denoting file's status.
5456
5460
5457 Examples:
5461 Examples:
5458
5462
5459 - show changes in the working directory relative to a
5463 - show changes in the working directory relative to a
5460 changeset::
5464 changeset::
5461
5465
5462 hg status --rev 9353
5466 hg status --rev 9353
5463
5467
5464 - show changes in the working directory relative to the
5468 - show changes in the working directory relative to the
5465 current directory (see :hg:`help patterns` for more information)::
5469 current directory (see :hg:`help patterns` for more information)::
5466
5470
5467 hg status re:
5471 hg status re:
5468
5472
5469 - show all changes including copies in an existing changeset::
5473 - show all changes including copies in an existing changeset::
5470
5474
5471 hg status --copies --change 9353
5475 hg status --copies --change 9353
5472
5476
5473 - get a NUL separated list of added files, suitable for xargs::
5477 - get a NUL separated list of added files, suitable for xargs::
5474
5478
5475 hg status -an0
5479 hg status -an0
5476
5480
5477 - show more information about the repository status, abbreviating
5481 - show more information about the repository status, abbreviating
5478 added, removed, modified, deleted, and untracked paths::
5482 added, removed, modified, deleted, and untracked paths::
5479
5483
5480 hg status -v -t mardu
5484 hg status -v -t mardu
5481
5485
5482 Returns 0 on success.
5486 Returns 0 on success.
5483
5487
5484 """
5488 """
5485
5489
5486 opts = pycompat.byteskwargs(opts)
5490 opts = pycompat.byteskwargs(opts)
5487 revs = opts.get('rev')
5491 revs = opts.get('rev')
5488 change = opts.get('change')
5492 change = opts.get('change')
5489 terse = opts.get('terse')
5493 terse = opts.get('terse')
5490 if terse is _NOTTERSE:
5494 if terse is _NOTTERSE:
5491 if revs:
5495 if revs:
5492 terse = ''
5496 terse = ''
5493 else:
5497 else:
5494 terse = ui.config('commands', 'status.terse')
5498 terse = ui.config('commands', 'status.terse')
5495
5499
5496 if revs and change:
5500 if revs and change:
5497 msg = _('cannot specify --rev and --change at the same time')
5501 msg = _('cannot specify --rev and --change at the same time')
5498 raise error.Abort(msg)
5502 raise error.Abort(msg)
5499 elif revs and terse:
5503 elif revs and terse:
5500 msg = _('cannot use --terse with --rev')
5504 msg = _('cannot use --terse with --rev')
5501 raise error.Abort(msg)
5505 raise error.Abort(msg)
5502 elif change:
5506 elif change:
5503 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5507 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5504 ctx2 = scmutil.revsingle(repo, change, None)
5508 ctx2 = scmutil.revsingle(repo, change, None)
5505 ctx1 = ctx2.p1()
5509 ctx1 = ctx2.p1()
5506 else:
5510 else:
5507 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5511 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5508 ctx1, ctx2 = scmutil.revpair(repo, revs)
5512 ctx1, ctx2 = scmutil.revpair(repo, revs)
5509
5513
5510 forcerelativevalue = None
5514 forcerelativevalue = None
5511 if ui.hasconfig('commands', 'status.relative'):
5515 if ui.hasconfig('commands', 'status.relative'):
5512 forcerelativevalue = ui.configbool('commands', 'status.relative')
5516 forcerelativevalue = ui.configbool('commands', 'status.relative')
5513 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5517 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5514 forcerelativevalue=forcerelativevalue)
5518 forcerelativevalue=forcerelativevalue)
5515
5519
5516 if opts.get('print0'):
5520 if opts.get('print0'):
5517 end = '\0'
5521 end = '\0'
5518 else:
5522 else:
5519 end = '\n'
5523 end = '\n'
5520 copy = {}
5524 copy = {}
5521 states = 'modified added removed deleted unknown ignored clean'.split()
5525 states = 'modified added removed deleted unknown ignored clean'.split()
5522 show = [k for k in states if opts.get(k)]
5526 show = [k for k in states if opts.get(k)]
5523 if opts.get('all'):
5527 if opts.get('all'):
5524 show += ui.quiet and (states[:4] + ['clean']) or states
5528 show += ui.quiet and (states[:4] + ['clean']) or states
5525
5529
5526 if not show:
5530 if not show:
5527 if ui.quiet:
5531 if ui.quiet:
5528 show = states[:4]
5532 show = states[:4]
5529 else:
5533 else:
5530 show = states[:5]
5534 show = states[:5]
5531
5535
5532 m = scmutil.match(ctx2, pats, opts)
5536 m = scmutil.match(ctx2, pats, opts)
5533 if terse:
5537 if terse:
5534 # we need to compute clean and unknown to terse
5538 # we need to compute clean and unknown to terse
5535 stat = repo.status(ctx1.node(), ctx2.node(), m,
5539 stat = repo.status(ctx1.node(), ctx2.node(), m,
5536 'ignored' in show or 'i' in terse,
5540 'ignored' in show or 'i' in terse,
5537 clean=True, unknown=True,
5541 clean=True, unknown=True,
5538 listsubrepos=opts.get('subrepos'))
5542 listsubrepos=opts.get('subrepos'))
5539
5543
5540 stat = cmdutil.tersedir(stat, terse)
5544 stat = cmdutil.tersedir(stat, terse)
5541 else:
5545 else:
5542 stat = repo.status(ctx1.node(), ctx2.node(), m,
5546 stat = repo.status(ctx1.node(), ctx2.node(), m,
5543 'ignored' in show, 'clean' in show,
5547 'ignored' in show, 'clean' in show,
5544 'unknown' in show, opts.get('subrepos'))
5548 'unknown' in show, opts.get('subrepos'))
5545
5549
5546 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5550 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5547
5551
5548 if (opts.get('all') or opts.get('copies')
5552 if (opts.get('all') or opts.get('copies')
5549 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5553 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5550 copy = copies.pathcopies(ctx1, ctx2, m)
5554 copy = copies.pathcopies(ctx1, ctx2, m)
5551
5555
5552 ui.pager('status')
5556 ui.pager('status')
5553 fm = ui.formatter('status', opts)
5557 fm = ui.formatter('status', opts)
5554 fmt = '%s' + end
5558 fmt = '%s' + end
5555 showchar = not opts.get('no_status')
5559 showchar = not opts.get('no_status')
5556
5560
5557 for state, char, files in changestates:
5561 for state, char, files in changestates:
5558 if state in show:
5562 if state in show:
5559 label = 'status.' + state
5563 label = 'status.' + state
5560 for f in files:
5564 for f in files:
5561 fm.startitem()
5565 fm.startitem()
5562 fm.context(ctx=ctx2)
5566 fm.context(ctx=ctx2)
5563 fm.data(path=f)
5567 fm.data(path=f)
5564 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5568 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5565 fm.plain(fmt % uipathfn(f), label=label)
5569 fm.plain(fmt % uipathfn(f), label=label)
5566 if f in copy:
5570 if f in copy:
5567 fm.data(source=copy[f])
5571 fm.data(source=copy[f])
5568 fm.plain((' %s' + end) % uipathfn(copy[f]),
5572 fm.plain((' %s' + end) % uipathfn(copy[f]),
5569 label='status.copied')
5573 label='status.copied')
5570
5574
5571 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5575 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5572 and not ui.plain()):
5576 and not ui.plain()):
5573 cmdutil.morestatus(repo, fm)
5577 cmdutil.morestatus(repo, fm)
5574 fm.end()
5578 fm.end()
5575
5579
5576 @command('summary|sum',
5580 @command('summary|sum',
5577 [('', 'remote', None, _('check for push and pull'))],
5581 [('', 'remote', None, _('check for push and pull'))],
5578 '[--remote]',
5582 '[--remote]',
5579 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5583 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5580 helpbasic=True,
5584 helpbasic=True,
5581 intents={INTENT_READONLY})
5585 intents={INTENT_READONLY})
5582 def summary(ui, repo, **opts):
5586 def summary(ui, repo, **opts):
5583 """summarize working directory state
5587 """summarize working directory state
5584
5588
5585 This generates a brief summary of the working directory state,
5589 This generates a brief summary of the working directory state,
5586 including parents, branch, commit status, phase and available updates.
5590 including parents, branch, commit status, phase and available updates.
5587
5591
5588 With the --remote option, this will check the default paths for
5592 With the --remote option, this will check the default paths for
5589 incoming and outgoing changes. This can be time-consuming.
5593 incoming and outgoing changes. This can be time-consuming.
5590
5594
5591 Returns 0 on success.
5595 Returns 0 on success.
5592 """
5596 """
5593
5597
5594 opts = pycompat.byteskwargs(opts)
5598 opts = pycompat.byteskwargs(opts)
5595 ui.pager('summary')
5599 ui.pager('summary')
5596 ctx = repo[None]
5600 ctx = repo[None]
5597 parents = ctx.parents()
5601 parents = ctx.parents()
5598 pnode = parents[0].node()
5602 pnode = parents[0].node()
5599 marks = []
5603 marks = []
5600
5604
5601 try:
5605 try:
5602 ms = mergemod.mergestate.read(repo)
5606 ms = mergemod.mergestate.read(repo)
5603 except error.UnsupportedMergeRecords as e:
5607 except error.UnsupportedMergeRecords as e:
5604 s = ' '.join(e.recordtypes)
5608 s = ' '.join(e.recordtypes)
5605 ui.warn(
5609 ui.warn(
5606 _('warning: merge state has unsupported record types: %s\n') % s)
5610 _('warning: merge state has unsupported record types: %s\n') % s)
5607 unresolved = []
5611 unresolved = []
5608 else:
5612 else:
5609 unresolved = list(ms.unresolved())
5613 unresolved = list(ms.unresolved())
5610
5614
5611 for p in parents:
5615 for p in parents:
5612 # label with log.changeset (instead of log.parent) since this
5616 # label with log.changeset (instead of log.parent) since this
5613 # shows a working directory parent *changeset*:
5617 # shows a working directory parent *changeset*:
5614 # i18n: column positioning for "hg summary"
5618 # i18n: column positioning for "hg summary"
5615 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5619 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5616 label=logcmdutil.changesetlabels(p))
5620 label=logcmdutil.changesetlabels(p))
5617 ui.write(' '.join(p.tags()), label='log.tag')
5621 ui.write(' '.join(p.tags()), label='log.tag')
5618 if p.bookmarks():
5622 if p.bookmarks():
5619 marks.extend(p.bookmarks())
5623 marks.extend(p.bookmarks())
5620 if p.rev() == -1:
5624 if p.rev() == -1:
5621 if not len(repo):
5625 if not len(repo):
5622 ui.write(_(' (empty repository)'))
5626 ui.write(_(' (empty repository)'))
5623 else:
5627 else:
5624 ui.write(_(' (no revision checked out)'))
5628 ui.write(_(' (no revision checked out)'))
5625 if p.obsolete():
5629 if p.obsolete():
5626 ui.write(_(' (obsolete)'))
5630 ui.write(_(' (obsolete)'))
5627 if p.isunstable():
5631 if p.isunstable():
5628 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5632 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5629 for instability in p.instabilities())
5633 for instability in p.instabilities())
5630 ui.write(' ('
5634 ui.write(' ('
5631 + ', '.join(instabilities)
5635 + ', '.join(instabilities)
5632 + ')')
5636 + ')')
5633 ui.write('\n')
5637 ui.write('\n')
5634 if p.description():
5638 if p.description():
5635 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5639 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5636 label='log.summary')
5640 label='log.summary')
5637
5641
5638 branch = ctx.branch()
5642 branch = ctx.branch()
5639 bheads = repo.branchheads(branch)
5643 bheads = repo.branchheads(branch)
5640 # i18n: column positioning for "hg summary"
5644 # i18n: column positioning for "hg summary"
5641 m = _('branch: %s\n') % branch
5645 m = _('branch: %s\n') % branch
5642 if branch != 'default':
5646 if branch != 'default':
5643 ui.write(m, label='log.branch')
5647 ui.write(m, label='log.branch')
5644 else:
5648 else:
5645 ui.status(m, label='log.branch')
5649 ui.status(m, label='log.branch')
5646
5650
5647 if marks:
5651 if marks:
5648 active = repo._activebookmark
5652 active = repo._activebookmark
5649 # i18n: column positioning for "hg summary"
5653 # i18n: column positioning for "hg summary"
5650 ui.write(_('bookmarks:'), label='log.bookmark')
5654 ui.write(_('bookmarks:'), label='log.bookmark')
5651 if active is not None:
5655 if active is not None:
5652 if active in marks:
5656 if active in marks:
5653 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5657 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5654 marks.remove(active)
5658 marks.remove(active)
5655 else:
5659 else:
5656 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5660 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5657 for m in marks:
5661 for m in marks:
5658 ui.write(' ' + m, label='log.bookmark')
5662 ui.write(' ' + m, label='log.bookmark')
5659 ui.write('\n', label='log.bookmark')
5663 ui.write('\n', label='log.bookmark')
5660
5664
5661 status = repo.status(unknown=True)
5665 status = repo.status(unknown=True)
5662
5666
5663 c = repo.dirstate.copies()
5667 c = repo.dirstate.copies()
5664 copied, renamed = [], []
5668 copied, renamed = [], []
5665 for d, s in c.iteritems():
5669 for d, s in c.iteritems():
5666 if s in status.removed:
5670 if s in status.removed:
5667 status.removed.remove(s)
5671 status.removed.remove(s)
5668 renamed.append(d)
5672 renamed.append(d)
5669 else:
5673 else:
5670 copied.append(d)
5674 copied.append(d)
5671 if d in status.added:
5675 if d in status.added:
5672 status.added.remove(d)
5676 status.added.remove(d)
5673
5677
5674 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5678 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5675
5679
5676 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5680 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5677 (ui.label(_('%d added'), 'status.added'), status.added),
5681 (ui.label(_('%d added'), 'status.added'), status.added),
5678 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5682 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5679 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5683 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5680 (ui.label(_('%d copied'), 'status.copied'), copied),
5684 (ui.label(_('%d copied'), 'status.copied'), copied),
5681 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5685 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5682 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5686 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5683 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5687 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5684 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5688 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5685 t = []
5689 t = []
5686 for l, s in labels:
5690 for l, s in labels:
5687 if s:
5691 if s:
5688 t.append(l % len(s))
5692 t.append(l % len(s))
5689
5693
5690 t = ', '.join(t)
5694 t = ', '.join(t)
5691 cleanworkdir = False
5695 cleanworkdir = False
5692
5696
5693 if repo.vfs.exists('graftstate'):
5697 if repo.vfs.exists('graftstate'):
5694 t += _(' (graft in progress)')
5698 t += _(' (graft in progress)')
5695 if repo.vfs.exists('updatestate'):
5699 if repo.vfs.exists('updatestate'):
5696 t += _(' (interrupted update)')
5700 t += _(' (interrupted update)')
5697 elif len(parents) > 1:
5701 elif len(parents) > 1:
5698 t += _(' (merge)')
5702 t += _(' (merge)')
5699 elif branch != parents[0].branch():
5703 elif branch != parents[0].branch():
5700 t += _(' (new branch)')
5704 t += _(' (new branch)')
5701 elif (parents[0].closesbranch() and
5705 elif (parents[0].closesbranch() and
5702 pnode in repo.branchheads(branch, closed=True)):
5706 pnode in repo.branchheads(branch, closed=True)):
5703 t += _(' (head closed)')
5707 t += _(' (head closed)')
5704 elif not (status.modified or status.added or status.removed or renamed or
5708 elif not (status.modified or status.added or status.removed or renamed or
5705 copied or subs):
5709 copied or subs):
5706 t += _(' (clean)')
5710 t += _(' (clean)')
5707 cleanworkdir = True
5711 cleanworkdir = True
5708 elif pnode not in bheads:
5712 elif pnode not in bheads:
5709 t += _(' (new branch head)')
5713 t += _(' (new branch head)')
5710
5714
5711 if parents:
5715 if parents:
5712 pendingphase = max(p.phase() for p in parents)
5716 pendingphase = max(p.phase() for p in parents)
5713 else:
5717 else:
5714 pendingphase = phases.public
5718 pendingphase = phases.public
5715
5719
5716 if pendingphase > phases.newcommitphase(ui):
5720 if pendingphase > phases.newcommitphase(ui):
5717 t += ' (%s)' % phases.phasenames[pendingphase]
5721 t += ' (%s)' % phases.phasenames[pendingphase]
5718
5722
5719 if cleanworkdir:
5723 if cleanworkdir:
5720 # i18n: column positioning for "hg summary"
5724 # i18n: column positioning for "hg summary"
5721 ui.status(_('commit: %s\n') % t.strip())
5725 ui.status(_('commit: %s\n') % t.strip())
5722 else:
5726 else:
5723 # i18n: column positioning for "hg summary"
5727 # i18n: column positioning for "hg summary"
5724 ui.write(_('commit: %s\n') % t.strip())
5728 ui.write(_('commit: %s\n') % t.strip())
5725
5729
5726 # all ancestors of branch heads - all ancestors of parent = new csets
5730 # all ancestors of branch heads - all ancestors of parent = new csets
5727 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5731 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5728 bheads))
5732 bheads))
5729
5733
5730 if new == 0:
5734 if new == 0:
5731 # i18n: column positioning for "hg summary"
5735 # i18n: column positioning for "hg summary"
5732 ui.status(_('update: (current)\n'))
5736 ui.status(_('update: (current)\n'))
5733 elif pnode not in bheads:
5737 elif pnode not in bheads:
5734 # i18n: column positioning for "hg summary"
5738 # i18n: column positioning for "hg summary"
5735 ui.write(_('update: %d new changesets (update)\n') % new)
5739 ui.write(_('update: %d new changesets (update)\n') % new)
5736 else:
5740 else:
5737 # i18n: column positioning for "hg summary"
5741 # i18n: column positioning for "hg summary"
5738 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5742 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5739 (new, len(bheads)))
5743 (new, len(bheads)))
5740
5744
5741 t = []
5745 t = []
5742 draft = len(repo.revs('draft()'))
5746 draft = len(repo.revs('draft()'))
5743 if draft:
5747 if draft:
5744 t.append(_('%d draft') % draft)
5748 t.append(_('%d draft') % draft)
5745 secret = len(repo.revs('secret()'))
5749 secret = len(repo.revs('secret()'))
5746 if secret:
5750 if secret:
5747 t.append(_('%d secret') % secret)
5751 t.append(_('%d secret') % secret)
5748
5752
5749 if draft or secret:
5753 if draft or secret:
5750 ui.status(_('phases: %s\n') % ', '.join(t))
5754 ui.status(_('phases: %s\n') % ', '.join(t))
5751
5755
5752 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5756 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5753 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5757 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5754 numtrouble = len(repo.revs(trouble + "()"))
5758 numtrouble = len(repo.revs(trouble + "()"))
5755 # We write all the possibilities to ease translation
5759 # We write all the possibilities to ease translation
5756 troublemsg = {
5760 troublemsg = {
5757 "orphan": _("orphan: %d changesets"),
5761 "orphan": _("orphan: %d changesets"),
5758 "contentdivergent": _("content-divergent: %d changesets"),
5762 "contentdivergent": _("content-divergent: %d changesets"),
5759 "phasedivergent": _("phase-divergent: %d changesets"),
5763 "phasedivergent": _("phase-divergent: %d changesets"),
5760 }
5764 }
5761 if numtrouble > 0:
5765 if numtrouble > 0:
5762 ui.status(troublemsg[trouble] % numtrouble + "\n")
5766 ui.status(troublemsg[trouble] % numtrouble + "\n")
5763
5767
5764 cmdutil.summaryhooks(ui, repo)
5768 cmdutil.summaryhooks(ui, repo)
5765
5769
5766 if opts.get('remote'):
5770 if opts.get('remote'):
5767 needsincoming, needsoutgoing = True, True
5771 needsincoming, needsoutgoing = True, True
5768 else:
5772 else:
5769 needsincoming, needsoutgoing = False, False
5773 needsincoming, needsoutgoing = False, False
5770 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5774 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5771 if i:
5775 if i:
5772 needsincoming = True
5776 needsincoming = True
5773 if o:
5777 if o:
5774 needsoutgoing = True
5778 needsoutgoing = True
5775 if not needsincoming and not needsoutgoing:
5779 if not needsincoming and not needsoutgoing:
5776 return
5780 return
5777
5781
5778 def getincoming():
5782 def getincoming():
5779 source, branches = hg.parseurl(ui.expandpath('default'))
5783 source, branches = hg.parseurl(ui.expandpath('default'))
5780 sbranch = branches[0]
5784 sbranch = branches[0]
5781 try:
5785 try:
5782 other = hg.peer(repo, {}, source)
5786 other = hg.peer(repo, {}, source)
5783 except error.RepoError:
5787 except error.RepoError:
5784 if opts.get('remote'):
5788 if opts.get('remote'):
5785 raise
5789 raise
5786 return source, sbranch, None, None, None
5790 return source, sbranch, None, None, None
5787 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5791 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5788 if revs:
5792 if revs:
5789 revs = [other.lookup(rev) for rev in revs]
5793 revs = [other.lookup(rev) for rev in revs]
5790 ui.debug('comparing with %s\n' % util.hidepassword(source))
5794 ui.debug('comparing with %s\n' % util.hidepassword(source))
5791 repo.ui.pushbuffer()
5795 repo.ui.pushbuffer()
5792 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5796 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5793 repo.ui.popbuffer()
5797 repo.ui.popbuffer()
5794 return source, sbranch, other, commoninc, commoninc[1]
5798 return source, sbranch, other, commoninc, commoninc[1]
5795
5799
5796 if needsincoming:
5800 if needsincoming:
5797 source, sbranch, sother, commoninc, incoming = getincoming()
5801 source, sbranch, sother, commoninc, incoming = getincoming()
5798 else:
5802 else:
5799 source = sbranch = sother = commoninc = incoming = None
5803 source = sbranch = sother = commoninc = incoming = None
5800
5804
5801 def getoutgoing():
5805 def getoutgoing():
5802 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5806 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5803 dbranch = branches[0]
5807 dbranch = branches[0]
5804 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5808 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5805 if source != dest:
5809 if source != dest:
5806 try:
5810 try:
5807 dother = hg.peer(repo, {}, dest)
5811 dother = hg.peer(repo, {}, dest)
5808 except error.RepoError:
5812 except error.RepoError:
5809 if opts.get('remote'):
5813 if opts.get('remote'):
5810 raise
5814 raise
5811 return dest, dbranch, None, None
5815 return dest, dbranch, None, None
5812 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5816 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5813 elif sother is None:
5817 elif sother is None:
5814 # there is no explicit destination peer, but source one is invalid
5818 # there is no explicit destination peer, but source one is invalid
5815 return dest, dbranch, None, None
5819 return dest, dbranch, None, None
5816 else:
5820 else:
5817 dother = sother
5821 dother = sother
5818 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5822 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5819 common = None
5823 common = None
5820 else:
5824 else:
5821 common = commoninc
5825 common = commoninc
5822 if revs:
5826 if revs:
5823 revs = [repo.lookup(rev) for rev in revs]
5827 revs = [repo.lookup(rev) for rev in revs]
5824 repo.ui.pushbuffer()
5828 repo.ui.pushbuffer()
5825 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5829 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5826 commoninc=common)
5830 commoninc=common)
5827 repo.ui.popbuffer()
5831 repo.ui.popbuffer()
5828 return dest, dbranch, dother, outgoing
5832 return dest, dbranch, dother, outgoing
5829
5833
5830 if needsoutgoing:
5834 if needsoutgoing:
5831 dest, dbranch, dother, outgoing = getoutgoing()
5835 dest, dbranch, dother, outgoing = getoutgoing()
5832 else:
5836 else:
5833 dest = dbranch = dother = outgoing = None
5837 dest = dbranch = dother = outgoing = None
5834
5838
5835 if opts.get('remote'):
5839 if opts.get('remote'):
5836 t = []
5840 t = []
5837 if incoming:
5841 if incoming:
5838 t.append(_('1 or more incoming'))
5842 t.append(_('1 or more incoming'))
5839 o = outgoing.missing
5843 o = outgoing.missing
5840 if o:
5844 if o:
5841 t.append(_('%d outgoing') % len(o))
5845 t.append(_('%d outgoing') % len(o))
5842 other = dother or sother
5846 other = dother or sother
5843 if 'bookmarks' in other.listkeys('namespaces'):
5847 if 'bookmarks' in other.listkeys('namespaces'):
5844 counts = bookmarks.summary(repo, other)
5848 counts = bookmarks.summary(repo, other)
5845 if counts[0] > 0:
5849 if counts[0] > 0:
5846 t.append(_('%d incoming bookmarks') % counts[0])
5850 t.append(_('%d incoming bookmarks') % counts[0])
5847 if counts[1] > 0:
5851 if counts[1] > 0:
5848 t.append(_('%d outgoing bookmarks') % counts[1])
5852 t.append(_('%d outgoing bookmarks') % counts[1])
5849
5853
5850 if t:
5854 if t:
5851 # i18n: column positioning for "hg summary"
5855 # i18n: column positioning for "hg summary"
5852 ui.write(_('remote: %s\n') % (', '.join(t)))
5856 ui.write(_('remote: %s\n') % (', '.join(t)))
5853 else:
5857 else:
5854 # i18n: column positioning for "hg summary"
5858 # i18n: column positioning for "hg summary"
5855 ui.status(_('remote: (synced)\n'))
5859 ui.status(_('remote: (synced)\n'))
5856
5860
5857 cmdutil.summaryremotehooks(ui, repo, opts,
5861 cmdutil.summaryremotehooks(ui, repo, opts,
5858 ((source, sbranch, sother, commoninc),
5862 ((source, sbranch, sother, commoninc),
5859 (dest, dbranch, dother, outgoing)))
5863 (dest, dbranch, dother, outgoing)))
5860
5864
5861 @command('tag',
5865 @command('tag',
5862 [('f', 'force', None, _('force tag')),
5866 [('f', 'force', None, _('force tag')),
5863 ('l', 'local', None, _('make the tag local')),
5867 ('l', 'local', None, _('make the tag local')),
5864 ('r', 'rev', '', _('revision to tag'), _('REV')),
5868 ('r', 'rev', '', _('revision to tag'), _('REV')),
5865 ('', 'remove', None, _('remove a tag')),
5869 ('', 'remove', None, _('remove a tag')),
5866 # -l/--local is already there, commitopts cannot be used
5870 # -l/--local is already there, commitopts cannot be used
5867 ('e', 'edit', None, _('invoke editor on commit messages')),
5871 ('e', 'edit', None, _('invoke editor on commit messages')),
5868 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5872 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5869 ] + commitopts2,
5873 ] + commitopts2,
5870 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5874 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5871 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5875 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5872 def tag(ui, repo, name1, *names, **opts):
5876 def tag(ui, repo, name1, *names, **opts):
5873 """add one or more tags for the current or given revision
5877 """add one or more tags for the current or given revision
5874
5878
5875 Name a particular revision using <name>.
5879 Name a particular revision using <name>.
5876
5880
5877 Tags are used to name particular revisions of the repository and are
5881 Tags are used to name particular revisions of the repository and are
5878 very useful to compare different revisions, to go back to significant
5882 very useful to compare different revisions, to go back to significant
5879 earlier versions or to mark branch points as releases, etc. Changing
5883 earlier versions or to mark branch points as releases, etc. Changing
5880 an existing tag is normally disallowed; use -f/--force to override.
5884 an existing tag is normally disallowed; use -f/--force to override.
5881
5885
5882 If no revision is given, the parent of the working directory is
5886 If no revision is given, the parent of the working directory is
5883 used.
5887 used.
5884
5888
5885 To facilitate version control, distribution, and merging of tags,
5889 To facilitate version control, distribution, and merging of tags,
5886 they are stored as a file named ".hgtags" which is managed similarly
5890 they are stored as a file named ".hgtags" which is managed similarly
5887 to other project files and can be hand-edited if necessary. This
5891 to other project files and can be hand-edited if necessary. This
5888 also means that tagging creates a new commit. The file
5892 also means that tagging creates a new commit. The file
5889 ".hg/localtags" is used for local tags (not shared among
5893 ".hg/localtags" is used for local tags (not shared among
5890 repositories).
5894 repositories).
5891
5895
5892 Tag commits are usually made at the head of a branch. If the parent
5896 Tag commits are usually made at the head of a branch. If the parent
5893 of the working directory is not a branch head, :hg:`tag` aborts; use
5897 of the working directory is not a branch head, :hg:`tag` aborts; use
5894 -f/--force to force the tag commit to be based on a non-head
5898 -f/--force to force the tag commit to be based on a non-head
5895 changeset.
5899 changeset.
5896
5900
5897 See :hg:`help dates` for a list of formats valid for -d/--date.
5901 See :hg:`help dates` for a list of formats valid for -d/--date.
5898
5902
5899 Since tag names have priority over branch names during revision
5903 Since tag names have priority over branch names during revision
5900 lookup, using an existing branch name as a tag name is discouraged.
5904 lookup, using an existing branch name as a tag name is discouraged.
5901
5905
5902 Returns 0 on success.
5906 Returns 0 on success.
5903 """
5907 """
5904 opts = pycompat.byteskwargs(opts)
5908 opts = pycompat.byteskwargs(opts)
5905 with repo.wlock(), repo.lock():
5909 with repo.wlock(), repo.lock():
5906 rev_ = "."
5910 rev_ = "."
5907 names = [t.strip() for t in (name1,) + names]
5911 names = [t.strip() for t in (name1,) + names]
5908 if len(names) != len(set(names)):
5912 if len(names) != len(set(names)):
5909 raise error.Abort(_('tag names must be unique'))
5913 raise error.Abort(_('tag names must be unique'))
5910 for n in names:
5914 for n in names:
5911 scmutil.checknewlabel(repo, n, 'tag')
5915 scmutil.checknewlabel(repo, n, 'tag')
5912 if not n:
5916 if not n:
5913 raise error.Abort(_('tag names cannot consist entirely of '
5917 raise error.Abort(_('tag names cannot consist entirely of '
5914 'whitespace'))
5918 'whitespace'))
5915 if opts.get('rev') and opts.get('remove'):
5919 if opts.get('rev') and opts.get('remove'):
5916 raise error.Abort(_("--rev and --remove are incompatible"))
5920 raise error.Abort(_("--rev and --remove are incompatible"))
5917 if opts.get('rev'):
5921 if opts.get('rev'):
5918 rev_ = opts['rev']
5922 rev_ = opts['rev']
5919 message = opts.get('message')
5923 message = opts.get('message')
5920 if opts.get('remove'):
5924 if opts.get('remove'):
5921 if opts.get('local'):
5925 if opts.get('local'):
5922 expectedtype = 'local'
5926 expectedtype = 'local'
5923 else:
5927 else:
5924 expectedtype = 'global'
5928 expectedtype = 'global'
5925
5929
5926 for n in names:
5930 for n in names:
5927 if repo.tagtype(n) == 'global':
5931 if repo.tagtype(n) == 'global':
5928 alltags = tagsmod.findglobaltags(ui, repo)
5932 alltags = tagsmod.findglobaltags(ui, repo)
5929 if alltags[n][0] == nullid:
5933 if alltags[n][0] == nullid:
5930 raise error.Abort(_("tag '%s' is already removed") % n)
5934 raise error.Abort(_("tag '%s' is already removed") % n)
5931 if not repo.tagtype(n):
5935 if not repo.tagtype(n):
5932 raise error.Abort(_("tag '%s' does not exist") % n)
5936 raise error.Abort(_("tag '%s' does not exist") % n)
5933 if repo.tagtype(n) != expectedtype:
5937 if repo.tagtype(n) != expectedtype:
5934 if expectedtype == 'global':
5938 if expectedtype == 'global':
5935 raise error.Abort(_("tag '%s' is not a global tag") % n)
5939 raise error.Abort(_("tag '%s' is not a global tag") % n)
5936 else:
5940 else:
5937 raise error.Abort(_("tag '%s' is not a local tag") % n)
5941 raise error.Abort(_("tag '%s' is not a local tag") % n)
5938 rev_ = 'null'
5942 rev_ = 'null'
5939 if not message:
5943 if not message:
5940 # we don't translate commit messages
5944 # we don't translate commit messages
5941 message = 'Removed tag %s' % ', '.join(names)
5945 message = 'Removed tag %s' % ', '.join(names)
5942 elif not opts.get('force'):
5946 elif not opts.get('force'):
5943 for n in names:
5947 for n in names:
5944 if n in repo.tags():
5948 if n in repo.tags():
5945 raise error.Abort(_("tag '%s' already exists "
5949 raise error.Abort(_("tag '%s' already exists "
5946 "(use -f to force)") % n)
5950 "(use -f to force)") % n)
5947 if not opts.get('local'):
5951 if not opts.get('local'):
5948 p1, p2 = repo.dirstate.parents()
5952 p1, p2 = repo.dirstate.parents()
5949 if p2 != nullid:
5953 if p2 != nullid:
5950 raise error.Abort(_('uncommitted merge'))
5954 raise error.Abort(_('uncommitted merge'))
5951 bheads = repo.branchheads()
5955 bheads = repo.branchheads()
5952 if not opts.get('force') and bheads and p1 not in bheads:
5956 if not opts.get('force') and bheads and p1 not in bheads:
5953 raise error.Abort(_('working directory is not at a branch head '
5957 raise error.Abort(_('working directory is not at a branch head '
5954 '(use -f to force)'))
5958 '(use -f to force)'))
5955 node = scmutil.revsingle(repo, rev_).node()
5959 node = scmutil.revsingle(repo, rev_).node()
5956
5960
5957 if not message:
5961 if not message:
5958 # we don't translate commit messages
5962 # we don't translate commit messages
5959 message = ('Added tag %s for changeset %s' %
5963 message = ('Added tag %s for changeset %s' %
5960 (', '.join(names), short(node)))
5964 (', '.join(names), short(node)))
5961
5965
5962 date = opts.get('date')
5966 date = opts.get('date')
5963 if date:
5967 if date:
5964 date = dateutil.parsedate(date)
5968 date = dateutil.parsedate(date)
5965
5969
5966 if opts.get('remove'):
5970 if opts.get('remove'):
5967 editform = 'tag.remove'
5971 editform = 'tag.remove'
5968 else:
5972 else:
5969 editform = 'tag.add'
5973 editform = 'tag.add'
5970 editor = cmdutil.getcommiteditor(editform=editform,
5974 editor = cmdutil.getcommiteditor(editform=editform,
5971 **pycompat.strkwargs(opts))
5975 **pycompat.strkwargs(opts))
5972
5976
5973 # don't allow tagging the null rev
5977 # don't allow tagging the null rev
5974 if (not opts.get('remove') and
5978 if (not opts.get('remove') and
5975 scmutil.revsingle(repo, rev_).rev() == nullrev):
5979 scmutil.revsingle(repo, rev_).rev() == nullrev):
5976 raise error.Abort(_("cannot tag null revision"))
5980 raise error.Abort(_("cannot tag null revision"))
5977
5981
5978 tagsmod.tag(repo, names, node, message, opts.get('local'),
5982 tagsmod.tag(repo, names, node, message, opts.get('local'),
5979 opts.get('user'), date, editor=editor)
5983 opts.get('user'), date, editor=editor)
5980
5984
5981 @command(
5985 @command(
5982 'tags', formatteropts, '',
5986 'tags', formatteropts, '',
5983 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5987 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5984 intents={INTENT_READONLY})
5988 intents={INTENT_READONLY})
5985 def tags(ui, repo, **opts):
5989 def tags(ui, repo, **opts):
5986 """list repository tags
5990 """list repository tags
5987
5991
5988 This lists both regular and local tags. When the -v/--verbose
5992 This lists both regular and local tags. When the -v/--verbose
5989 switch is used, a third column "local" is printed for local tags.
5993 switch is used, a third column "local" is printed for local tags.
5990 When the -q/--quiet switch is used, only the tag name is printed.
5994 When the -q/--quiet switch is used, only the tag name is printed.
5991
5995
5992 .. container:: verbose
5996 .. container:: verbose
5993
5997
5994 Template:
5998 Template:
5995
5999
5996 The following keywords are supported in addition to the common template
6000 The following keywords are supported in addition to the common template
5997 keywords and functions such as ``{tag}``. See also
6001 keywords and functions such as ``{tag}``. See also
5998 :hg:`help templates`.
6002 :hg:`help templates`.
5999
6003
6000 :type: String. ``local`` for local tags.
6004 :type: String. ``local`` for local tags.
6001
6005
6002 Returns 0 on success.
6006 Returns 0 on success.
6003 """
6007 """
6004
6008
6005 opts = pycompat.byteskwargs(opts)
6009 opts = pycompat.byteskwargs(opts)
6006 ui.pager('tags')
6010 ui.pager('tags')
6007 fm = ui.formatter('tags', opts)
6011 fm = ui.formatter('tags', opts)
6008 hexfunc = fm.hexfunc
6012 hexfunc = fm.hexfunc
6009
6013
6010 for t, n in reversed(repo.tagslist()):
6014 for t, n in reversed(repo.tagslist()):
6011 hn = hexfunc(n)
6015 hn = hexfunc(n)
6012 label = 'tags.normal'
6016 label = 'tags.normal'
6013 tagtype = ''
6017 tagtype = ''
6014 if repo.tagtype(t) == 'local':
6018 if repo.tagtype(t) == 'local':
6015 label = 'tags.local'
6019 label = 'tags.local'
6016 tagtype = 'local'
6020 tagtype = 'local'
6017
6021
6018 fm.startitem()
6022 fm.startitem()
6019 fm.context(repo=repo)
6023 fm.context(repo=repo)
6020 fm.write('tag', '%s', t, label=label)
6024 fm.write('tag', '%s', t, label=label)
6021 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6025 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6022 fm.condwrite(not ui.quiet, 'rev node', fmt,
6026 fm.condwrite(not ui.quiet, 'rev node', fmt,
6023 repo.changelog.rev(n), hn, label=label)
6027 repo.changelog.rev(n), hn, label=label)
6024 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6028 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6025 tagtype, label=label)
6029 tagtype, label=label)
6026 fm.plain('\n')
6030 fm.plain('\n')
6027 fm.end()
6031 fm.end()
6028
6032
6029 @command('tip',
6033 @command('tip',
6030 [('p', 'patch', None, _('show patch')),
6034 [('p', 'patch', None, _('show patch')),
6031 ('g', 'git', None, _('use git extended diff format')),
6035 ('g', 'git', None, _('use git extended diff format')),
6032 ] + templateopts,
6036 ] + templateopts,
6033 _('[-p] [-g]'),
6037 _('[-p] [-g]'),
6034 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
6038 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
6035 def tip(ui, repo, **opts):
6039 def tip(ui, repo, **opts):
6036 """show the tip revision (DEPRECATED)
6040 """show the tip revision (DEPRECATED)
6037
6041
6038 The tip revision (usually just called the tip) is the changeset
6042 The tip revision (usually just called the tip) is the changeset
6039 most recently added to the repository (and therefore the most
6043 most recently added to the repository (and therefore the most
6040 recently changed head).
6044 recently changed head).
6041
6045
6042 If you have just made a commit, that commit will be the tip. If
6046 If you have just made a commit, that commit will be the tip. If
6043 you have just pulled changes from another repository, the tip of
6047 you have just pulled changes from another repository, the tip of
6044 that repository becomes the current tip. The "tip" tag is special
6048 that repository becomes the current tip. The "tip" tag is special
6045 and cannot be renamed or assigned to a different changeset.
6049 and cannot be renamed or assigned to a different changeset.
6046
6050
6047 This command is deprecated, please use :hg:`heads` instead.
6051 This command is deprecated, please use :hg:`heads` instead.
6048
6052
6049 Returns 0 on success.
6053 Returns 0 on success.
6050 """
6054 """
6051 opts = pycompat.byteskwargs(opts)
6055 opts = pycompat.byteskwargs(opts)
6052 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6056 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6053 displayer.show(repo['tip'])
6057 displayer.show(repo['tip'])
6054 displayer.close()
6058 displayer.close()
6055
6059
6056 @command('unbundle',
6060 @command('unbundle',
6057 [('u', 'update', None,
6061 [('u', 'update', None,
6058 _('update to new branch head if changesets were unbundled'))],
6062 _('update to new branch head if changesets were unbundled'))],
6059 _('[-u] FILE...'),
6063 _('[-u] FILE...'),
6060 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6064 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6061 def unbundle(ui, repo, fname1, *fnames, **opts):
6065 def unbundle(ui, repo, fname1, *fnames, **opts):
6062 """apply one or more bundle files
6066 """apply one or more bundle files
6063
6067
6064 Apply one or more bundle files generated by :hg:`bundle`.
6068 Apply one or more bundle files generated by :hg:`bundle`.
6065
6069
6066 Returns 0 on success, 1 if an update has unresolved files.
6070 Returns 0 on success, 1 if an update has unresolved files.
6067 """
6071 """
6068 fnames = (fname1,) + fnames
6072 fnames = (fname1,) + fnames
6069
6073
6070 with repo.lock():
6074 with repo.lock():
6071 for fname in fnames:
6075 for fname in fnames:
6072 f = hg.openpath(ui, fname)
6076 f = hg.openpath(ui, fname)
6073 gen = exchange.readbundle(ui, f, fname)
6077 gen = exchange.readbundle(ui, f, fname)
6074 if isinstance(gen, streamclone.streamcloneapplier):
6078 if isinstance(gen, streamclone.streamcloneapplier):
6075 raise error.Abort(
6079 raise error.Abort(
6076 _('packed bundles cannot be applied with '
6080 _('packed bundles cannot be applied with '
6077 '"hg unbundle"'),
6081 '"hg unbundle"'),
6078 hint=_('use "hg debugapplystreamclonebundle"'))
6082 hint=_('use "hg debugapplystreamclonebundle"'))
6079 url = 'bundle:' + fname
6083 url = 'bundle:' + fname
6080 try:
6084 try:
6081 txnname = 'unbundle'
6085 txnname = 'unbundle'
6082 if not isinstance(gen, bundle2.unbundle20):
6086 if not isinstance(gen, bundle2.unbundle20):
6083 txnname = 'unbundle\n%s' % util.hidepassword(url)
6087 txnname = 'unbundle\n%s' % util.hidepassword(url)
6084 with repo.transaction(txnname) as tr:
6088 with repo.transaction(txnname) as tr:
6085 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6089 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6086 url=url)
6090 url=url)
6087 except error.BundleUnknownFeatureError as exc:
6091 except error.BundleUnknownFeatureError as exc:
6088 raise error.Abort(
6092 raise error.Abort(
6089 _('%s: unknown bundle feature, %s') % (fname, exc),
6093 _('%s: unknown bundle feature, %s') % (fname, exc),
6090 hint=_("see https://mercurial-scm.org/"
6094 hint=_("see https://mercurial-scm.org/"
6091 "wiki/BundleFeature for more "
6095 "wiki/BundleFeature for more "
6092 "information"))
6096 "information"))
6093 modheads = bundle2.combinechangegroupresults(op)
6097 modheads = bundle2.combinechangegroupresults(op)
6094
6098
6095 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6099 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6096
6100
6097 @command('unshelve',
6101 @command('unshelve',
6098 [('a', 'abort', None,
6102 [('a', 'abort', None,
6099 _('abort an incomplete unshelve operation')),
6103 _('abort an incomplete unshelve operation')),
6100 ('c', 'continue', None,
6104 ('c', 'continue', None,
6101 _('continue an incomplete unshelve operation')),
6105 _('continue an incomplete unshelve operation')),
6102 ('k', 'keep', None,
6106 ('k', 'keep', None,
6103 _('keep shelve after unshelving')),
6107 _('keep shelve after unshelving')),
6104 ('n', 'name', '',
6108 ('n', 'name', '',
6105 _('restore shelved change with given name'), _('NAME')),
6109 _('restore shelved change with given name'), _('NAME')),
6106 ('t', 'tool', '', _('specify merge tool')),
6110 ('t', 'tool', '', _('specify merge tool')),
6107 ('', 'date', '',
6111 ('', 'date', '',
6108 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
6112 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
6109 _('hg unshelve [[-n] SHELVED]'),
6113 _('hg unshelve [[-n] SHELVED]'),
6110 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
6114 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
6111 def unshelve(ui, repo, *shelved, **opts):
6115 def unshelve(ui, repo, *shelved, **opts):
6112 """restore a shelved change to the working directory
6116 """restore a shelved change to the working directory
6113
6117
6114 This command accepts an optional name of a shelved change to
6118 This command accepts an optional name of a shelved change to
6115 restore. If none is given, the most recent shelved change is used.
6119 restore. If none is given, the most recent shelved change is used.
6116
6120
6117 If a shelved change is applied successfully, the bundle that
6121 If a shelved change is applied successfully, the bundle that
6118 contains the shelved changes is moved to a backup location
6122 contains the shelved changes is moved to a backup location
6119 (.hg/shelve-backup).
6123 (.hg/shelve-backup).
6120
6124
6121 Since you can restore a shelved change on top of an arbitrary
6125 Since you can restore a shelved change on top of an arbitrary
6122 commit, it is possible that unshelving will result in a conflict
6126 commit, it is possible that unshelving will result in a conflict
6123 between your changes and the commits you are unshelving onto. If
6127 between your changes and the commits you are unshelving onto. If
6124 this occurs, you must resolve the conflict, then use
6128 this occurs, you must resolve the conflict, then use
6125 ``--continue`` to complete the unshelve operation. (The bundle
6129 ``--continue`` to complete the unshelve operation. (The bundle
6126 will not be moved until you successfully complete the unshelve.)
6130 will not be moved until you successfully complete the unshelve.)
6127
6131
6128 (Alternatively, you can use ``--abort`` to abandon an unshelve
6132 (Alternatively, you can use ``--abort`` to abandon an unshelve
6129 that causes a conflict. This reverts the unshelved changes, and
6133 that causes a conflict. This reverts the unshelved changes, and
6130 leaves the bundle in place.)
6134 leaves the bundle in place.)
6131
6135
6132 If bare shelved change (when no files are specified, without interactive,
6136 If bare shelved change (when no files are specified, without interactive,
6133 include and exclude option) was done on newly created branch it would
6137 include and exclude option) was done on newly created branch it would
6134 restore branch information to the working directory.
6138 restore branch information to the working directory.
6135
6139
6136 After a successful unshelve, the shelved changes are stored in a
6140 After a successful unshelve, the shelved changes are stored in a
6137 backup directory. Only the N most recent backups are kept. N
6141 backup directory. Only the N most recent backups are kept. N
6138 defaults to 10 but can be overridden using the ``shelve.maxbackups``
6142 defaults to 10 but can be overridden using the ``shelve.maxbackups``
6139 configuration option.
6143 configuration option.
6140
6144
6141 .. container:: verbose
6145 .. container:: verbose
6142
6146
6143 Timestamp in seconds is used to decide order of backups. More
6147 Timestamp in seconds is used to decide order of backups. More
6144 than ``maxbackups`` backups are kept, if same timestamp
6148 than ``maxbackups`` backups are kept, if same timestamp
6145 prevents from deciding exact order of them, for safety.
6149 prevents from deciding exact order of them, for safety.
6146 """
6150 """
6147 with repo.wlock():
6151 with repo.wlock():
6148 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
6152 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
6149
6153
6150 @command('update|up|checkout|co',
6154 @command('update|up|checkout|co',
6151 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6155 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6152 ('c', 'check', None, _('require clean working directory')),
6156 ('c', 'check', None, _('require clean working directory')),
6153 ('m', 'merge', None, _('merge uncommitted changes')),
6157 ('m', 'merge', None, _('merge uncommitted changes')),
6154 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6158 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6155 ('r', 'rev', '', _('revision'), _('REV'))
6159 ('r', 'rev', '', _('revision'), _('REV'))
6156 ] + mergetoolopts,
6160 ] + mergetoolopts,
6157 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6161 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6158 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6162 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6159 helpbasic=True)
6163 helpbasic=True)
6160 def update(ui, repo, node=None, **opts):
6164 def update(ui, repo, node=None, **opts):
6161 """update working directory (or switch revisions)
6165 """update working directory (or switch revisions)
6162
6166
6163 Update the repository's working directory to the specified
6167 Update the repository's working directory to the specified
6164 changeset. If no changeset is specified, update to the tip of the
6168 changeset. If no changeset is specified, update to the tip of the
6165 current named branch and move the active bookmark (see :hg:`help
6169 current named branch and move the active bookmark (see :hg:`help
6166 bookmarks`).
6170 bookmarks`).
6167
6171
6168 Update sets the working directory's parent revision to the specified
6172 Update sets the working directory's parent revision to the specified
6169 changeset (see :hg:`help parents`).
6173 changeset (see :hg:`help parents`).
6170
6174
6171 If the changeset is not a descendant or ancestor of the working
6175 If the changeset is not a descendant or ancestor of the working
6172 directory's parent and there are uncommitted changes, the update is
6176 directory's parent and there are uncommitted changes, the update is
6173 aborted. With the -c/--check option, the working directory is checked
6177 aborted. With the -c/--check option, the working directory is checked
6174 for uncommitted changes; if none are found, the working directory is
6178 for uncommitted changes; if none are found, the working directory is
6175 updated to the specified changeset.
6179 updated to the specified changeset.
6176
6180
6177 .. container:: verbose
6181 .. container:: verbose
6178
6182
6179 The -C/--clean, -c/--check, and -m/--merge options control what
6183 The -C/--clean, -c/--check, and -m/--merge options control what
6180 happens if the working directory contains uncommitted changes.
6184 happens if the working directory contains uncommitted changes.
6181 At most of one of them can be specified.
6185 At most of one of them can be specified.
6182
6186
6183 1. If no option is specified, and if
6187 1. If no option is specified, and if
6184 the requested changeset is an ancestor or descendant of
6188 the requested changeset is an ancestor or descendant of
6185 the working directory's parent, the uncommitted changes
6189 the working directory's parent, the uncommitted changes
6186 are merged into the requested changeset and the merged
6190 are merged into the requested changeset and the merged
6187 result is left uncommitted. If the requested changeset is
6191 result is left uncommitted. If the requested changeset is
6188 not an ancestor or descendant (that is, it is on another
6192 not an ancestor or descendant (that is, it is on another
6189 branch), the update is aborted and the uncommitted changes
6193 branch), the update is aborted and the uncommitted changes
6190 are preserved.
6194 are preserved.
6191
6195
6192 2. With the -m/--merge option, the update is allowed even if the
6196 2. With the -m/--merge option, the update is allowed even if the
6193 requested changeset is not an ancestor or descendant of
6197 requested changeset is not an ancestor or descendant of
6194 the working directory's parent.
6198 the working directory's parent.
6195
6199
6196 3. With the -c/--check option, the update is aborted and the
6200 3. With the -c/--check option, the update is aborted and the
6197 uncommitted changes are preserved.
6201 uncommitted changes are preserved.
6198
6202
6199 4. With the -C/--clean option, uncommitted changes are discarded and
6203 4. With the -C/--clean option, uncommitted changes are discarded and
6200 the working directory is updated to the requested changeset.
6204 the working directory is updated to the requested changeset.
6201
6205
6202 To cancel an uncommitted merge (and lose your changes), use
6206 To cancel an uncommitted merge (and lose your changes), use
6203 :hg:`merge --abort`.
6207 :hg:`merge --abort`.
6204
6208
6205 Use null as the changeset to remove the working directory (like
6209 Use null as the changeset to remove the working directory (like
6206 :hg:`clone -U`).
6210 :hg:`clone -U`).
6207
6211
6208 If you want to revert just one file to an older revision, use
6212 If you want to revert just one file to an older revision, use
6209 :hg:`revert [-r REV] NAME`.
6213 :hg:`revert [-r REV] NAME`.
6210
6214
6211 See :hg:`help dates` for a list of formats valid for -d/--date.
6215 See :hg:`help dates` for a list of formats valid for -d/--date.
6212
6216
6213 Returns 0 on success, 1 if there are unresolved files.
6217 Returns 0 on success, 1 if there are unresolved files.
6214 """
6218 """
6215 rev = opts.get(r'rev')
6219 rev = opts.get(r'rev')
6216 date = opts.get(r'date')
6220 date = opts.get(r'date')
6217 clean = opts.get(r'clean')
6221 clean = opts.get(r'clean')
6218 check = opts.get(r'check')
6222 check = opts.get(r'check')
6219 merge = opts.get(r'merge')
6223 merge = opts.get(r'merge')
6220 if rev and node:
6224 if rev and node:
6221 raise error.Abort(_("please specify just one revision"))
6225 raise error.Abort(_("please specify just one revision"))
6222
6226
6223 if ui.configbool('commands', 'update.requiredest'):
6227 if ui.configbool('commands', 'update.requiredest'):
6224 if not node and not rev and not date:
6228 if not node and not rev and not date:
6225 raise error.Abort(_('you must specify a destination'),
6229 raise error.Abort(_('you must specify a destination'),
6226 hint=_('for example: hg update ".::"'))
6230 hint=_('for example: hg update ".::"'))
6227
6231
6228 if rev is None or rev == '':
6232 if rev is None or rev == '':
6229 rev = node
6233 rev = node
6230
6234
6231 if date and rev is not None:
6235 if date and rev is not None:
6232 raise error.Abort(_("you can't specify a revision and a date"))
6236 raise error.Abort(_("you can't specify a revision and a date"))
6233
6237
6234 if len([x for x in (clean, check, merge) if x]) > 1:
6238 if len([x for x in (clean, check, merge) if x]) > 1:
6235 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6239 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6236 "or -m/--merge"))
6240 "or -m/--merge"))
6237
6241
6238 updatecheck = None
6242 updatecheck = None
6239 if check:
6243 if check:
6240 updatecheck = 'abort'
6244 updatecheck = 'abort'
6241 elif merge:
6245 elif merge:
6242 updatecheck = 'none'
6246 updatecheck = 'none'
6243
6247
6244 with repo.wlock():
6248 with repo.wlock():
6245 cmdutil.clearunfinished(repo)
6249 cmdutil.clearunfinished(repo)
6246 if date:
6250 if date:
6247 rev = cmdutil.finddate(ui, repo, date)
6251 rev = cmdutil.finddate(ui, repo, date)
6248
6252
6249 # if we defined a bookmark, we have to remember the original name
6253 # if we defined a bookmark, we have to remember the original name
6250 brev = rev
6254 brev = rev
6251 if rev:
6255 if rev:
6252 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6256 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6253 ctx = scmutil.revsingle(repo, rev, default=None)
6257 ctx = scmutil.revsingle(repo, rev, default=None)
6254 rev = ctx.rev()
6258 rev = ctx.rev()
6255 hidden = ctx.hidden()
6259 hidden = ctx.hidden()
6256 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6260 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6257 with ui.configoverride(overrides, 'update'):
6261 with ui.configoverride(overrides, 'update'):
6258 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6262 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6259 updatecheck=updatecheck)
6263 updatecheck=updatecheck)
6260 if hidden:
6264 if hidden:
6261 ctxstr = ctx.hex()[:12]
6265 ctxstr = ctx.hex()[:12]
6262 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6266 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6263
6267
6264 if ctx.obsolete():
6268 if ctx.obsolete():
6265 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6269 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6266 ui.warn("(%s)\n" % obsfatemsg)
6270 ui.warn("(%s)\n" % obsfatemsg)
6267 return ret
6271 return ret
6268
6272
6269 @command('verify',
6273 @command('verify',
6270 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6274 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6271 helpcategory=command.CATEGORY_MAINTENANCE)
6275 helpcategory=command.CATEGORY_MAINTENANCE)
6272 def verify(ui, repo, **opts):
6276 def verify(ui, repo, **opts):
6273 """verify the integrity of the repository
6277 """verify the integrity of the repository
6274
6278
6275 Verify the integrity of the current repository.
6279 Verify the integrity of the current repository.
6276
6280
6277 This will perform an extensive check of the repository's
6281 This will perform an extensive check of the repository's
6278 integrity, validating the hashes and checksums of each entry in
6282 integrity, validating the hashes and checksums of each entry in
6279 the changelog, manifest, and tracked files, as well as the
6283 the changelog, manifest, and tracked files, as well as the
6280 integrity of their crosslinks and indices.
6284 integrity of their crosslinks and indices.
6281
6285
6282 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6286 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6283 for more information about recovery from corruption of the
6287 for more information about recovery from corruption of the
6284 repository.
6288 repository.
6285
6289
6286 Returns 0 on success, 1 if errors are encountered.
6290 Returns 0 on success, 1 if errors are encountered.
6287 """
6291 """
6288 opts = pycompat.byteskwargs(opts)
6292 opts = pycompat.byteskwargs(opts)
6289
6293
6290 level = None
6294 level = None
6291 if opts['full']:
6295 if opts['full']:
6292 level = verifymod.VERIFY_FULL
6296 level = verifymod.VERIFY_FULL
6293 return hg.verify(repo, level)
6297 return hg.verify(repo, level)
6294
6298
6295 @command(
6299 @command(
6296 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6300 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6297 norepo=True, intents={INTENT_READONLY})
6301 norepo=True, intents={INTENT_READONLY})
6298 def version_(ui, **opts):
6302 def version_(ui, **opts):
6299 """output version and copyright information
6303 """output version and copyright information
6300
6304
6301 .. container:: verbose
6305 .. container:: verbose
6302
6306
6303 Template:
6307 Template:
6304
6308
6305 The following keywords are supported. See also :hg:`help templates`.
6309 The following keywords are supported. See also :hg:`help templates`.
6306
6310
6307 :extensions: List of extensions.
6311 :extensions: List of extensions.
6308 :ver: String. Version number.
6312 :ver: String. Version number.
6309
6313
6310 And each entry of ``{extensions}`` provides the following sub-keywords
6314 And each entry of ``{extensions}`` provides the following sub-keywords
6311 in addition to ``{ver}``.
6315 in addition to ``{ver}``.
6312
6316
6313 :bundled: Boolean. True if included in the release.
6317 :bundled: Boolean. True if included in the release.
6314 :name: String. Extension name.
6318 :name: String. Extension name.
6315 """
6319 """
6316 opts = pycompat.byteskwargs(opts)
6320 opts = pycompat.byteskwargs(opts)
6317 if ui.verbose:
6321 if ui.verbose:
6318 ui.pager('version')
6322 ui.pager('version')
6319 fm = ui.formatter("version", opts)
6323 fm = ui.formatter("version", opts)
6320 fm.startitem()
6324 fm.startitem()
6321 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6325 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6322 util.version())
6326 util.version())
6323 license = _(
6327 license = _(
6324 "(see https://mercurial-scm.org for more information)\n"
6328 "(see https://mercurial-scm.org for more information)\n"
6325 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6329 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6326 "This is free software; see the source for copying conditions. "
6330 "This is free software; see the source for copying conditions. "
6327 "There is NO\nwarranty; "
6331 "There is NO\nwarranty; "
6328 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6332 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6329 )
6333 )
6330 if not ui.quiet:
6334 if not ui.quiet:
6331 fm.plain(license)
6335 fm.plain(license)
6332
6336
6333 if ui.verbose:
6337 if ui.verbose:
6334 fm.plain(_("\nEnabled extensions:\n\n"))
6338 fm.plain(_("\nEnabled extensions:\n\n"))
6335 # format names and versions into columns
6339 # format names and versions into columns
6336 names = []
6340 names = []
6337 vers = []
6341 vers = []
6338 isinternals = []
6342 isinternals = []
6339 for name, module in extensions.extensions():
6343 for name, module in extensions.extensions():
6340 names.append(name)
6344 names.append(name)
6341 vers.append(extensions.moduleversion(module) or None)
6345 vers.append(extensions.moduleversion(module) or None)
6342 isinternals.append(extensions.ismoduleinternal(module))
6346 isinternals.append(extensions.ismoduleinternal(module))
6343 fn = fm.nested("extensions", tmpl='{name}\n')
6347 fn = fm.nested("extensions", tmpl='{name}\n')
6344 if names:
6348 if names:
6345 namefmt = " %%-%ds " % max(len(n) for n in names)
6349 namefmt = " %%-%ds " % max(len(n) for n in names)
6346 places = [_("external"), _("internal")]
6350 places = [_("external"), _("internal")]
6347 for n, v, p in zip(names, vers, isinternals):
6351 for n, v, p in zip(names, vers, isinternals):
6348 fn.startitem()
6352 fn.startitem()
6349 fn.condwrite(ui.verbose, "name", namefmt, n)
6353 fn.condwrite(ui.verbose, "name", namefmt, n)
6350 if ui.verbose:
6354 if ui.verbose:
6351 fn.plain("%s " % places[p])
6355 fn.plain("%s " % places[p])
6352 fn.data(bundled=p)
6356 fn.data(bundled=p)
6353 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6357 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6354 if ui.verbose:
6358 if ui.verbose:
6355 fn.plain("\n")
6359 fn.plain("\n")
6356 fn.end()
6360 fn.end()
6357 fm.end()
6361 fm.end()
6358
6362
6359 def loadcmdtable(ui, name, cmdtable):
6363 def loadcmdtable(ui, name, cmdtable):
6360 """Load command functions from specified cmdtable
6364 """Load command functions from specified cmdtable
6361 """
6365 """
6362 overrides = [cmd for cmd in cmdtable if cmd in table]
6366 overrides = [cmd for cmd in cmdtable if cmd in table]
6363 if overrides:
6367 if overrides:
6364 ui.warn(_("extension '%s' overrides commands: %s\n")
6368 ui.warn(_("extension '%s' overrides commands: %s\n")
6365 % (name, " ".join(overrides)))
6369 % (name, " ".join(overrides)))
6366 table.update(cmdtable)
6370 table.update(cmdtable)
@@ -1,86 +1,90 b''
1 == New Features ==
1 == New Features ==
2
2
3 * New config `commands.commit.post-status` shows status after successful
3 * New config `commands.commit.post-status` shows status after successful
4 commit.
4 commit.
5
5
6 * `hg root` now has templating support, including support for showing
6 * `hg root` now has templating support, including support for showing
7 where a repo share's source is. See `hg help -v root` for details.
7 where a repo share's source is. See `hg help -v root` for details.
8
8
9 * New `--force-close-branch` flag for `hg commit` to forcibly close
9 * New `--force-close-branch` flag for `hg commit` to forcibly close
10 branch from a non-head changeset.
10 branch from a non-head changeset.
11
11
12 * The curses-based interface for commands like `hg commit -i` now supports
12 * The curses-based interface for commands like `hg commit -i` now supports
13 a range-select mechanism. Select the first line using space like before,
13 a range-select mechanism. Select the first line using space like before,
14 navigate to the last line, and press X (capital x) to set all items in
14 navigate to the last line, and press X (capital x) to set all items in
15 the range at once. Lowercase x has been added as a synonym for space to
15 the range at once. Lowercase x has been added as a synonym for space to
16 help reinforce the mechanism, and pressing enter/return continues to be a
16 help reinforce the mechanism, and pressing enter/return continues to be a
17 synonym for "toggle the current line and move down to the next item in
17 synonym for "toggle the current line and move down to the next item in
18 this section."
18 this section."
19
19
20 == New Experimental Features ==
20 == New Experimental Features ==
21
21
22 * New config `experimental.log.topo` makes `hg log -G` use
22 * New config `experimental.log.topo` makes `hg log -G` use
23 topological sorting. This is especially useful for aliases since it
23 topological sorting. This is especially useful for aliases since it
24 lets the alias accept an `-r` option while still using topological
24 lets the alias accept an `-r` option while still using topological
25 sorting with or without the `-r` (unlike if you use the `sort(...,
25 sorting with or without the `-r` (unlike if you use the `sort(...,
26 topo)` revset).
26 topo)` revset).
27
27
28
28
29 == Bug Fixes ==
29 == Bug Fixes ==
30
30
31 * issue4292: "hg log and {files} {file_adds} {file_mods} {file_dels}
31 * issue4292: "hg log and {files} {file_adds} {file_mods} {file_dels}
32 in template show wrong files on merged revision". See details in
32 in template show wrong files on merged revision". See details in
33 "Backwards Compatibility Changes".
33 "Backwards Compatibility Changes".
34
34
35
35
36 == Backwards Compatibility Changes ==
36 == Backwards Compatibility Changes ==
37
37
38 * Removed (experimental) support for log graph lines mixing
38 * Removed (experimental) support for log graph lines mixing
39 parent/grandparent styles. Setting
39 parent/grandparent styles. Setting
40 e.g. `experimental.graphstyle.parent = !` and
40 e.g. `experimental.graphstyle.parent = !` and
41 `experimental.graphstyle.grandparent = 3.` would use `!` for the
41 `experimental.graphstyle.grandparent = 3.` would use `!` for the
42 first three lines of the graph and then `.`. This is no longer
42 first three lines of the graph and then `.`. This is no longer
43 supported.
43 supported.
44
44
45 * If `ui.origbackuppath` had been (incorrectly) configured to point
45 * If `ui.origbackuppath` had been (incorrectly) configured to point
46 to a file, we will now replace that file by a directory and put
46 to a file, we will now replace that file by a directory and put
47 backups in that directory. This is similar to how we would
47 backups in that directory. This is similar to how we would
48 previously replace files *in* the configured directory by
48 previously replace files *in* the configured directory by
49 subdirectories.
49 subdirectories.
50
50
51 * Template keyword `{file_mods}`, `{file_adds}`, and `{file_dels}`
51 * Template keyword `{file_mods}`, `{file_adds}`, and `{file_dels}`
52 have changed behavior on merge commits. They used to be relative to
52 have changed behavior on merge commits. They used to be relative to
53 the first parent, but they now consider both parents. `{file_adds}`
53 the first parent, but they now consider both parents. `{file_adds}`
54 shows files that exists in the commit but did not exist in either
54 shows files that exists in the commit but did not exist in either
55 parent. `{file_dels}` shows files that do not exist in the commit
55 parent. `{file_dels}` shows files that do not exist in the commit
56 but existed in either parent. `{file_mods}` show the remaining
56 but existed in either parent. `{file_mods}` show the remaining
57 files from `{files}` that were not in the other two
57 files from `{files}` that were not in the other two
58 sets.
58 sets.
59
59
60
60
61 == Internal API Changes ==
61 == Internal API Changes ==
62
62
63 * Matchers are no longer iterable. Use `match.files()` instead.
63 * Matchers are no longer iterable. Use `match.files()` instead.
64
64
65 * `match.visitdir()` and `match.visitchildrenset()` now expect the
65 * `match.visitdir()` and `match.visitchildrenset()` now expect the
66 empty string instead of '.' to indicate the root directory.
66 empty string instead of '.' to indicate the root directory.
67
67
68 * `util.dirs()` and `util.finddirs()` now include an entry for the
68 * `util.dirs()` and `util.finddirs()` now include an entry for the
69 root directory (empty string).
69 root directory (empty string).
70
70
71 * shelve is no longer an extension now. it will be turned on by default.
71 * shelve is no longer an extension now. it will be turned on by default.
72
72
73 * New API to manage unfinished operations: Earlier there were distinct APIs
73 * New API to manage unfinished operations: Earlier there were distinct APIs
74 which dealt with unfinished states and separate lists maintaining them
74 which dealt with unfinished states and separate lists maintaining them
75 that are `cmdutil.afterresolvestates`, `cmdutil.unfinishedstates` and
75 that are `cmdutil.afterresolvestates`, `cmdutil.unfinishedstates` and
76 `cmdutil.STATES`. Now these have been unified to a single
76 `cmdutil.STATES`. Now these have been unified to a single
77 API which handles the various states and their utilities. This API
77 API which handles the various states and their utilities. This API
78 has been added to `state.py`. Now instead of adding to these 3 lists
78 has been added to `state.py`. Now instead of adding to these 3 lists
79 independently a state for a new operation can be registered using
79 independently a state for a new operation can be registered using
80 `addunfinished()` in `state` module.
80 `addunfinished()` in `state` module.
81
81
82 * `cmdutil.checkunfinished()` now includes detection for merge too.
82 * `cmdutil.checkunfinished()` now includes detection for merge too.
83
83
84 * merge abort has been disallowed in case an operation of higher
85 precedence is in progress to avoid cases of partial abort of
86 operations.
87
84 * We used to automatically attempt to make extensions compatible with
88 * We used to automatically attempt to make extensions compatible with
85 Python 3 (by translating their source code while loading it). We no
89 Python 3 (by translating their source code while loading it). We no
86 longer do that.
90 longer do that.
@@ -1,849 +1,884 b''
1 #testcases stripbased phasebased
1 #testcases stripbased phasebased
2
2
3 $ cat <<EOF >> $HGRCPATH
3 $ cat <<EOF >> $HGRCPATH
4 > [extensions]
4 > [extensions]
5 > mq =
5 > mq =
6 > [defaults]
6 > [defaults]
7 > diff = --nodates --git
7 > diff = --nodates --git
8 > qnew = --date '0 0'
8 > qnew = --date '0 0'
9 > [shelve]
9 > [shelve]
10 > maxbackups = 2
10 > maxbackups = 2
11 > EOF
11 > EOF
12
12
13 #if phasebased
13 #if phasebased
14
14
15 $ cat <<EOF >> $HGRCPATH
15 $ cat <<EOF >> $HGRCPATH
16 > [format]
16 > [format]
17 > internal-phase = yes
17 > internal-phase = yes
18 > EOF
18 > EOF
19
19
20 #endif
20 #endif
21
21
22 shelve should leave dirstate clean (issue4055)
22 shelve should leave dirstate clean (issue4055)
23
23
24 $ hg init shelverebase
24 $ hg init shelverebase
25 $ cd shelverebase
25 $ cd shelverebase
26 $ printf 'x\ny\n' > x
26 $ printf 'x\ny\n' > x
27 $ echo z > z
27 $ echo z > z
28 $ hg commit -Aqm xy
28 $ hg commit -Aqm xy
29 $ echo z >> x
29 $ echo z >> x
30 $ hg commit -Aqm z
30 $ hg commit -Aqm z
31 $ hg up 5c4c67fb7dce
31 $ hg up 5c4c67fb7dce
32 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 $ printf 'a\nx\ny\nz\n' > x
33 $ printf 'a\nx\ny\nz\n' > x
34 $ hg commit -Aqm xyz
34 $ hg commit -Aqm xyz
35 $ echo c >> z
35 $ echo c >> z
36 $ hg shelve
36 $ hg shelve
37 shelved as default
37 shelved as default
38 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
39
39
40 $ hg rebase -d 6c103be8f4e4 --config extensions.rebase=
40 $ hg rebase -d 6c103be8f4e4 --config extensions.rebase=
41 rebasing 2:323bfa07f744 "xyz"( \(tip\))? (re)
41 rebasing 2:323bfa07f744 "xyz"( \(tip\))? (re)
42 merging x
42 merging x
43 saved backup bundle to \$TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-(78114325|7ae538ef)-rebase.hg (re)
43 saved backup bundle to \$TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-(78114325|7ae538ef)-rebase.hg (re)
44 $ hg unshelve
44 $ hg unshelve
45 unshelving change 'default'
45 unshelving change 'default'
46 rebasing shelved changes
46 rebasing shelved changes
47 $ hg status
47 $ hg status
48 M z
48 M z
49
49
50 $ cd ..
50 $ cd ..
51
51
52 shelve should only unshelve pending changes (issue4068)
52 shelve should only unshelve pending changes (issue4068)
53
53
54 $ hg init onlypendingchanges
54 $ hg init onlypendingchanges
55 $ cd onlypendingchanges
55 $ cd onlypendingchanges
56 $ touch a
56 $ touch a
57 $ hg ci -Aqm a
57 $ hg ci -Aqm a
58 $ touch b
58 $ touch b
59 $ hg ci -Aqm b
59 $ hg ci -Aqm b
60 $ hg up -q 3903775176ed
60 $ hg up -q 3903775176ed
61 $ touch c
61 $ touch c
62 $ hg ci -Aqm c
62 $ hg ci -Aqm c
63
63
64 $ touch d
64 $ touch d
65 $ hg add d
65 $ hg add d
66 $ hg shelve
66 $ hg shelve
67 shelved as default
67 shelved as default
68 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
68 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
69 $ hg up -q 0e067c57feba
69 $ hg up -q 0e067c57feba
70 $ hg unshelve
70 $ hg unshelve
71 unshelving change 'default'
71 unshelving change 'default'
72 rebasing shelved changes
72 rebasing shelved changes
73 $ hg status
73 $ hg status
74 A d
74 A d
75
75
76 unshelve should work on an ancestor of the original commit
76 unshelve should work on an ancestor of the original commit
77
77
78 $ hg shelve
78 $ hg shelve
79 shelved as default
79 shelved as default
80 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
80 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
81 $ hg up 3903775176ed
81 $ hg up 3903775176ed
82 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
82 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
83 $ hg unshelve
83 $ hg unshelve
84 unshelving change 'default'
84 unshelving change 'default'
85 rebasing shelved changes
85 rebasing shelved changes
86 $ hg status
86 $ hg status
87 A d
87 A d
88
88
89 test bug 4073 we need to enable obsolete markers for it
89 test bug 4073 we need to enable obsolete markers for it
90
90
91 $ cat >> $HGRCPATH << EOF
91 $ cat >> $HGRCPATH << EOF
92 > [experimental]
92 > [experimental]
93 > evolution.createmarkers=True
93 > evolution.createmarkers=True
94 > EOF
94 > EOF
95 $ hg shelve
95 $ hg shelve
96 shelved as default
96 shelved as default
97 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
97 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
98 $ hg debugobsolete `hg log -r 0e067c57feba -T '{node}'`
98 $ hg debugobsolete `hg log -r 0e067c57feba -T '{node}'`
99 obsoleted 1 changesets
99 obsoleted 1 changesets
100 $ hg unshelve
100 $ hg unshelve
101 unshelving change 'default'
101 unshelving change 'default'
102
102
103 unshelve should leave unknown files alone (issue4113)
103 unshelve should leave unknown files alone (issue4113)
104
104
105 $ echo e > e
105 $ echo e > e
106 $ hg shelve
106 $ hg shelve
107 shelved as default
107 shelved as default
108 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
108 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
109 $ hg status
109 $ hg status
110 ? e
110 ? e
111 $ hg unshelve
111 $ hg unshelve
112 unshelving change 'default'
112 unshelving change 'default'
113 $ hg status
113 $ hg status
114 A d
114 A d
115 ? e
115 ? e
116 $ cat e
116 $ cat e
117 e
117 e
118
118
119 unshelve should keep a copy of unknown files
119 unshelve should keep a copy of unknown files
120
120
121 $ hg add e
121 $ hg add e
122 $ hg shelve
122 $ hg shelve
123 shelved as default
123 shelved as default
124 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
124 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
125 $ echo z > e
125 $ echo z > e
126 $ hg unshelve
126 $ hg unshelve
127 unshelving change 'default'
127 unshelving change 'default'
128 $ cat e
128 $ cat e
129 e
129 e
130 $ cat e.orig
130 $ cat e.orig
131 z
131 z
132 $ rm e.orig
132 $ rm e.orig
133
133
134 restores backup of unknown file to right directory
134 restores backup of unknown file to right directory
135
135
136 $ hg shelve
136 $ hg shelve
137 shelved as default
137 shelved as default
138 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
138 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
139 $ echo z > e
139 $ echo z > e
140 $ mkdir dir
140 $ mkdir dir
141 $ hg unshelve --cwd dir
141 $ hg unshelve --cwd dir
142 unshelving change 'default'
142 unshelving change 'default'
143 $ rmdir dir
143 $ rmdir dir
144 $ cat e
144 $ cat e
145 e
145 e
146 $ cat e.orig
146 $ cat e.orig
147 z
147 z
148
148
149 unshelve and conflicts with tracked and untracked files
149 unshelve and conflicts with tracked and untracked files
150
150
151 preparing:
151 preparing:
152
152
153 $ rm -f *.orig
153 $ rm -f *.orig
154 $ hg ci -qm 'commit stuff'
154 $ hg ci -qm 'commit stuff'
155 $ hg phase -p null:
155 $ hg phase -p null:
156
156
157 no other changes - no merge:
157 no other changes - no merge:
158
158
159 $ echo f > f
159 $ echo f > f
160 $ hg add f
160 $ hg add f
161 $ hg shelve
161 $ hg shelve
162 shelved as default
162 shelved as default
163 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
163 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
164 $ echo g > f
164 $ echo g > f
165 $ hg unshelve
165 $ hg unshelve
166 unshelving change 'default'
166 unshelving change 'default'
167 $ hg st
167 $ hg st
168 A f
168 A f
169 ? f.orig
169 ? f.orig
170 $ cat f
170 $ cat f
171 f
171 f
172 $ cat f.orig
172 $ cat f.orig
173 g
173 g
174
174
175 other uncommitted changes - merge:
175 other uncommitted changes - merge:
176
176
177 $ hg st
177 $ hg st
178 A f
178 A f
179 ? f.orig
179 ? f.orig
180 $ hg shelve
180 $ hg shelve
181 shelved as default
181 shelved as default
182 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
182 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
183 #if repobundlerepo
183 #if repobundlerepo
184 $ hg log -G --template '{rev} {desc|firstline} {author}' -R bundle://.hg/shelved/default.hg -r 'bundle()' --hidden
184 $ hg log -G --template '{rev} {desc|firstline} {author}' -R bundle://.hg/shelved/default.hg -r 'bundle()' --hidden
185 o [48] changes to: commit stuff shelve@localhost (re)
185 o [48] changes to: commit stuff shelve@localhost (re)
186 |
186 |
187 ~
187 ~
188 #endif
188 #endif
189 $ hg log -G --template '{rev} {desc|firstline} {author}'
189 $ hg log -G --template '{rev} {desc|firstline} {author}'
190 @ [37] commit stuff test (re)
190 @ [37] commit stuff test (re)
191 |
191 |
192 | o 2 c test
192 | o 2 c test
193 |/
193 |/
194 o 0 a test
194 o 0 a test
195
195
196 $ mv f.orig f
196 $ mv f.orig f
197 $ echo 1 > a
197 $ echo 1 > a
198 $ hg unshelve --date '1073741824 0'
198 $ hg unshelve --date '1073741824 0'
199 unshelving change 'default'
199 unshelving change 'default'
200 temporarily committing pending changes (restore with 'hg unshelve --abort')
200 temporarily committing pending changes (restore with 'hg unshelve --abort')
201 rebasing shelved changes
201 rebasing shelved changes
202 merging f
202 merging f
203 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
203 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
204 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
204 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
205 [1]
205 [1]
206
206
207 #if phasebased
207 #if phasebased
208 $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}'
208 $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}'
209 @ 9 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000
209 @ 9 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000
210 |
210 |
211 | @ 8 changes to: commit stuff shelve@localhost 1970-01-01 00:00 +0000
211 | @ 8 changes to: commit stuff shelve@localhost 1970-01-01 00:00 +0000
212 |/
212 |/
213 o 7 commit stuff test 1970-01-01 00:00 +0000
213 o 7 commit stuff test 1970-01-01 00:00 +0000
214 |
214 |
215 | o 2 c test 1970-01-01 00:00 +0000
215 | o 2 c test 1970-01-01 00:00 +0000
216 |/
216 |/
217 o 0 a test 1970-01-01 00:00 +0000
217 o 0 a test 1970-01-01 00:00 +0000
218
218
219 #endif
219 #endif
220
220
221 #if stripbased
221 #if stripbased
222 $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}'
222 $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}'
223 @ 5 changes to: commit stuff shelve@localhost 1970-01-01 00:00 +0000
223 @ 5 changes to: commit stuff shelve@localhost 1970-01-01 00:00 +0000
224 |
224 |
225 | @ 4 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000
225 | @ 4 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000
226 |/
226 |/
227 o 3 commit stuff test 1970-01-01 00:00 +0000
227 o 3 commit stuff test 1970-01-01 00:00 +0000
228 |
228 |
229 | o 2 c test 1970-01-01 00:00 +0000
229 | o 2 c test 1970-01-01 00:00 +0000
230 |/
230 |/
231 o 0 a test 1970-01-01 00:00 +0000
231 o 0 a test 1970-01-01 00:00 +0000
232
232
233 #endif
233 #endif
234
234
235 $ hg st
235 $ hg st
236 M f
236 M f
237 ? f.orig
237 ? f.orig
238 $ cat f
238 $ cat f
239 <<<<<<< shelve: d44eae5c3d33 - shelve: pending changes temporary commit
239 <<<<<<< shelve: d44eae5c3d33 - shelve: pending changes temporary commit
240 g
240 g
241 =======
241 =======
242 f
242 f
243 >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff
243 >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff
244 $ cat f.orig
244 $ cat f.orig
245 g
245 g
246 $ hg unshelve --abort -t false
246 $ hg unshelve --abort -t false
247 tool option will be ignored
247 tool option will be ignored
248 unshelve of 'default' aborted
248 unshelve of 'default' aborted
249 $ hg st
249 $ hg st
250 M a
250 M a
251 ? f.orig
251 ? f.orig
252 $ cat f.orig
252 $ cat f.orig
253 g
253 g
254 $ hg unshelve
254 $ hg unshelve
255 unshelving change 'default'
255 unshelving change 'default'
256 temporarily committing pending changes (restore with 'hg unshelve --abort')
256 temporarily committing pending changes (restore with 'hg unshelve --abort')
257 rebasing shelved changes
257 rebasing shelved changes
258 $ hg st
258 $ hg st
259 M a
259 M a
260 A f
260 A f
261 ? f.orig
261 ? f.orig
262
262
263 other committed changes - merge:
263 other committed changes - merge:
264
264
265 $ hg shelve f
265 $ hg shelve f
266 shelved as default
266 shelved as default
267 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
267 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
268 $ hg ci a -m 'intermediate other change'
268 $ hg ci a -m 'intermediate other change'
269 $ mv f.orig f
269 $ mv f.orig f
270 $ hg unshelve
270 $ hg unshelve
271 unshelving change 'default'
271 unshelving change 'default'
272 rebasing shelved changes
272 rebasing shelved changes
273 merging f
273 merging f
274 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
274 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
275 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
275 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
276 [1]
276 [1]
277 $ hg st
277 $ hg st
278 M f
278 M f
279 ? f.orig
279 ? f.orig
280 $ cat f
280 $ cat f
281 <<<<<<< shelve: 6b563750f973 - test: intermediate other change
281 <<<<<<< shelve: 6b563750f973 - test: intermediate other change
282 g
282 g
283 =======
283 =======
284 f
284 f
285 >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff
285 >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff
286 $ cat f.orig
286 $ cat f.orig
287 g
287 g
288 $ hg unshelve --abort
288 $ hg unshelve --abort
289 unshelve of 'default' aborted
289 unshelve of 'default' aborted
290 $ hg st
290 $ hg st
291 ? f.orig
291 ? f.orig
292 $ cat f.orig
292 $ cat f.orig
293 g
293 g
294 $ hg shelve --delete default
294 $ hg shelve --delete default
295 $ cd ..
295 $ cd ..
296
296
297 you shouldn't be able to ask for the patch/stats of the most recent shelve if
297 you shouldn't be able to ask for the patch/stats of the most recent shelve if
298 there are no shelves
298 there are no shelves
299
299
300 $ hg init noshelves
300 $ hg init noshelves
301 $ cd noshelves
301 $ cd noshelves
302
302
303 $ hg shelve --patch
303 $ hg shelve --patch
304 abort: there are no shelves to show
304 abort: there are no shelves to show
305 [255]
305 [255]
306 $ hg shelve --stat
306 $ hg shelve --stat
307 abort: there are no shelves to show
307 abort: there are no shelves to show
308 [255]
308 [255]
309
309
310 $ cd ..
310 $ cd ..
311
311
312 test .orig files go where the user wants them to
312 test .orig files go where the user wants them to
313 ---------------------------------------------------------------
313 ---------------------------------------------------------------
314 $ hg init salvage
314 $ hg init salvage
315 $ cd salvage
315 $ cd salvage
316 $ echo 'content' > root
316 $ echo 'content' > root
317 $ hg commit -A -m 'root' -q
317 $ hg commit -A -m 'root' -q
318 $ echo '' > root
318 $ echo '' > root
319 $ hg shelve -q
319 $ hg shelve -q
320 $ echo 'contADDent' > root
320 $ echo 'contADDent' > root
321 $ hg unshelve -q --config 'ui.origbackuppath=.hg/origbackups'
321 $ hg unshelve -q --config 'ui.origbackuppath=.hg/origbackups'
322 warning: conflicts while merging root! (edit, then use 'hg resolve --mark')
322 warning: conflicts while merging root! (edit, then use 'hg resolve --mark')
323 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
323 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
324 [1]
324 [1]
325 $ ls .hg/origbackups
325 $ ls .hg/origbackups
326 root
326 root
327 $ rm -rf .hg/origbackups
327 $ rm -rf .hg/origbackups
328
328
329 test Abort unshelve always gets user out of the unshelved state
329 test Abort unshelve always gets user out of the unshelved state
330 ---------------------------------------------------------------
330 ---------------------------------------------------------------
331
331
332 with a corrupted shelve state file
332 with a corrupted shelve state file
333 $ sed 's/ae8c668541e8/123456789012/' .hg/shelvedstate > ../corrupt-shelvedstate
333 $ sed 's/ae8c668541e8/123456789012/' .hg/shelvedstate > ../corrupt-shelvedstate
334 $ mv ../corrupt-shelvedstate .hg/shelvestate
334 $ mv ../corrupt-shelvedstate .hg/shelvestate
335 $ hg unshelve --abort 2>&1 | grep 'aborted'
335 $ hg unshelve --abort 2>&1 | grep 'aborted'
336 unshelve of 'default' aborted
336 unshelve of 'default' aborted
337 $ hg summary
337 $ hg summary
338 parent: 0:ae8c668541e8 tip
338 parent: 0:ae8c668541e8 tip
339 root
339 root
340 branch: default
340 branch: default
341 commit: 1 modified
341 commit: 1 modified
342 update: (current)
342 update: (current)
343 phases: 1 draft
343 phases: 1 draft
344 $ hg up -C .
344 $ hg up -C .
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346
346
347 $ cd ..
347 $ cd ..
348
348
349 Shelve and unshelve unknown files. For the purposes of unshelve, a shelved
349 Shelve and unshelve unknown files. For the purposes of unshelve, a shelved
350 unknown file is the same as a shelved added file, except that it will be in
350 unknown file is the same as a shelved added file, except that it will be in
351 unknown state after unshelve if and only if it was either absent or unknown
351 unknown state after unshelve if and only if it was either absent or unknown
352 before the unshelve operation.
352 before the unshelve operation.
353
353
354 $ hg init unknowns
354 $ hg init unknowns
355 $ cd unknowns
355 $ cd unknowns
356
356
357 The simplest case is if I simply have an unknown file that I shelve and unshelve
357 The simplest case is if I simply have an unknown file that I shelve and unshelve
358
358
359 $ echo unknown > unknown
359 $ echo unknown > unknown
360 $ hg status
360 $ hg status
361 ? unknown
361 ? unknown
362 $ hg shelve --unknown
362 $ hg shelve --unknown
363 shelved as default
363 shelved as default
364 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
364 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
365 $ hg status
365 $ hg status
366 $ hg unshelve
366 $ hg unshelve
367 unshelving change 'default'
367 unshelving change 'default'
368 $ hg status
368 $ hg status
369 ? unknown
369 ? unknown
370 $ rm unknown
370 $ rm unknown
371
371
372 If I shelve, add the file, and unshelve, does it stay added?
372 If I shelve, add the file, and unshelve, does it stay added?
373
373
374 $ echo unknown > unknown
374 $ echo unknown > unknown
375 $ hg shelve -u
375 $ hg shelve -u
376 shelved as default
376 shelved as default
377 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
377 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
378 $ hg status
378 $ hg status
379 $ touch unknown
379 $ touch unknown
380 $ hg add unknown
380 $ hg add unknown
381 $ hg status
381 $ hg status
382 A unknown
382 A unknown
383 $ hg unshelve
383 $ hg unshelve
384 unshelving change 'default'
384 unshelving change 'default'
385 temporarily committing pending changes (restore with 'hg unshelve --abort')
385 temporarily committing pending changes (restore with 'hg unshelve --abort')
386 rebasing shelved changes
386 rebasing shelved changes
387 merging unknown
387 merging unknown
388 $ hg status
388 $ hg status
389 A unknown
389 A unknown
390 $ hg forget unknown
390 $ hg forget unknown
391 $ rm unknown
391 $ rm unknown
392
392
393 And if I shelve, commit, then unshelve, does it become modified?
393 And if I shelve, commit, then unshelve, does it become modified?
394
394
395 $ echo unknown > unknown
395 $ echo unknown > unknown
396 $ hg shelve -u
396 $ hg shelve -u
397 shelved as default
397 shelved as default
398 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
398 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
399 $ hg status
399 $ hg status
400 $ touch unknown
400 $ touch unknown
401 $ hg add unknown
401 $ hg add unknown
402 $ hg commit -qm "Add unknown"
402 $ hg commit -qm "Add unknown"
403 $ hg status
403 $ hg status
404 $ hg unshelve
404 $ hg unshelve
405 unshelving change 'default'
405 unshelving change 'default'
406 rebasing shelved changes
406 rebasing shelved changes
407 merging unknown
407 merging unknown
408 $ hg status
408 $ hg status
409 M unknown
409 M unknown
410 $ hg remove --force unknown
410 $ hg remove --force unknown
411 $ hg commit -qm "Remove unknown"
411 $ hg commit -qm "Remove unknown"
412
412
413 $ cd ..
413 $ cd ..
414
414
415 We expects that non-bare shelve keeps newly created branch in
415 We expects that non-bare shelve keeps newly created branch in
416 working directory.
416 working directory.
417
417
418 $ hg init shelve-preserve-new-branch
418 $ hg init shelve-preserve-new-branch
419 $ cd shelve-preserve-new-branch
419 $ cd shelve-preserve-new-branch
420 $ echo "a" >> a
420 $ echo "a" >> a
421 $ hg add a
421 $ hg add a
422 $ echo "b" >> b
422 $ echo "b" >> b
423 $ hg add b
423 $ hg add b
424 $ hg commit -m "ab"
424 $ hg commit -m "ab"
425 $ echo "aa" >> a
425 $ echo "aa" >> a
426 $ echo "bb" >> b
426 $ echo "bb" >> b
427 $ hg branch new-branch
427 $ hg branch new-branch
428 marked working directory as branch new-branch
428 marked working directory as branch new-branch
429 (branches are permanent and global, did you want a bookmark?)
429 (branches are permanent and global, did you want a bookmark?)
430 $ hg status
430 $ hg status
431 M a
431 M a
432 M b
432 M b
433 $ hg branch
433 $ hg branch
434 new-branch
434 new-branch
435 $ hg shelve a
435 $ hg shelve a
436 shelved as default
436 shelved as default
437 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
438 $ hg branch
438 $ hg branch
439 new-branch
439 new-branch
440 $ hg status
440 $ hg status
441 M b
441 M b
442 $ touch "c" >> c
442 $ touch "c" >> c
443 $ hg add c
443 $ hg add c
444 $ hg status
444 $ hg status
445 M b
445 M b
446 A c
446 A c
447 $ hg shelve --exclude c
447 $ hg shelve --exclude c
448 shelved as default-01
448 shelved as default-01
449 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 $ hg branch
450 $ hg branch
451 new-branch
451 new-branch
452 $ hg status
452 $ hg status
453 A c
453 A c
454 $ hg shelve --include c
454 $ hg shelve --include c
455 shelved as default-02
455 shelved as default-02
456 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
456 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
457 $ hg branch
457 $ hg branch
458 new-branch
458 new-branch
459 $ hg status
459 $ hg status
460 $ echo "d" >> d
460 $ echo "d" >> d
461 $ hg add d
461 $ hg add d
462 $ hg status
462 $ hg status
463 A d
463 A d
464
464
465 We expect that bare-shelve will not keep branch in current working directory.
465 We expect that bare-shelve will not keep branch in current working directory.
466
466
467 $ hg shelve
467 $ hg shelve
468 shelved as default-03
468 shelved as default-03
469 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
469 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
470 $ hg branch
470 $ hg branch
471 default
471 default
472 $ cd ..
472 $ cd ..
473
473
474 When i shelve commit on newly created branch i expect
474 When i shelve commit on newly created branch i expect
475 that after unshelve newly created branch will be preserved.
475 that after unshelve newly created branch will be preserved.
476
476
477 $ hg init shelve_on_new_branch_simple
477 $ hg init shelve_on_new_branch_simple
478 $ cd shelve_on_new_branch_simple
478 $ cd shelve_on_new_branch_simple
479 $ echo "aaa" >> a
479 $ echo "aaa" >> a
480 $ hg commit -A -m "a"
480 $ hg commit -A -m "a"
481 adding a
481 adding a
482 $ hg branch
482 $ hg branch
483 default
483 default
484 $ hg branch test
484 $ hg branch test
485 marked working directory as branch test
485 marked working directory as branch test
486 (branches are permanent and global, did you want a bookmark?)
486 (branches are permanent and global, did you want a bookmark?)
487 $ echo "bbb" >> a
487 $ echo "bbb" >> a
488 $ hg status
488 $ hg status
489 M a
489 M a
490 $ hg shelve
490 $ hg shelve
491 shelved as default
491 shelved as default
492 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
492 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
493 $ hg branch
493 $ hg branch
494 default
494 default
495 $ echo "bbb" >> b
495 $ echo "bbb" >> b
496 $ hg status
496 $ hg status
497 ? b
497 ? b
498 $ hg unshelve
498 $ hg unshelve
499 unshelving change 'default'
499 unshelving change 'default'
500 marked working directory as branch test
500 marked working directory as branch test
501 $ hg status
501 $ hg status
502 M a
502 M a
503 ? b
503 ? b
504 $ hg branch
504 $ hg branch
505 test
505 test
506 $ cd ..
506 $ cd ..
507
507
508 When i shelve commit on newly created branch, make
508 When i shelve commit on newly created branch, make
509 some changes, unshelve it and running into merge
509 some changes, unshelve it and running into merge
510 conflicts i expect that after fixing them and
510 conflicts i expect that after fixing them and
511 running unshelve --continue newly created branch
511 running unshelve --continue newly created branch
512 will be preserved.
512 will be preserved.
513
513
514 $ hg init shelve_on_new_branch_conflict
514 $ hg init shelve_on_new_branch_conflict
515 $ cd shelve_on_new_branch_conflict
515 $ cd shelve_on_new_branch_conflict
516 $ echo "aaa" >> a
516 $ echo "aaa" >> a
517 $ hg commit -A -m "a"
517 $ hg commit -A -m "a"
518 adding a
518 adding a
519 $ hg branch
519 $ hg branch
520 default
520 default
521 $ hg branch test
521 $ hg branch test
522 marked working directory as branch test
522 marked working directory as branch test
523 (branches are permanent and global, did you want a bookmark?)
523 (branches are permanent and global, did you want a bookmark?)
524 $ echo "bbb" >> a
524 $ echo "bbb" >> a
525 $ hg status
525 $ hg status
526 M a
526 M a
527 $ hg shelve
527 $ hg shelve
528 shelved as default
528 shelved as default
529 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
529 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
530 $ hg branch
530 $ hg branch
531 default
531 default
532 $ echo "ccc" >> a
532 $ echo "ccc" >> a
533 $ hg status
533 $ hg status
534 M a
534 M a
535 $ hg unshelve
535 $ hg unshelve
536 unshelving change 'default'
536 unshelving change 'default'
537 temporarily committing pending changes (restore with 'hg unshelve --abort')
537 temporarily committing pending changes (restore with 'hg unshelve --abort')
538 rebasing shelved changes
538 rebasing shelved changes
539 merging a
539 merging a
540 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
540 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
541 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
541 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
542 [1]
542 [1]
543 $ echo "aaabbbccc" > a
543 $ echo "aaabbbccc" > a
544 $ rm a.orig
544 $ rm a.orig
545 $ hg resolve --mark a
545 $ hg resolve --mark a
546 (no more unresolved files)
546 (no more unresolved files)
547 continue: hg unshelve --continue
547 continue: hg unshelve --continue
548 $ hg unshelve --continue
548 $ hg unshelve --continue
549 marked working directory as branch test
549 marked working directory as branch test
550 unshelve of 'default' complete
550 unshelve of 'default' complete
551 $ cat a
551 $ cat a
552 aaabbbccc
552 aaabbbccc
553 $ hg status
553 $ hg status
554 M a
554 M a
555 $ hg branch
555 $ hg branch
556 test
556 test
557 $ hg commit -m "test-commit"
557 $ hg commit -m "test-commit"
558
558
559 When i shelve on test branch, update to default branch
559 When i shelve on test branch, update to default branch
560 and unshelve i expect that it will not preserve previous
560 and unshelve i expect that it will not preserve previous
561 test branch.
561 test branch.
562
562
563 $ echo "xxx" > b
563 $ echo "xxx" > b
564 $ hg add b
564 $ hg add b
565 $ hg shelve
565 $ hg shelve
566 shelved as test
566 shelved as test
567 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
567 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
568 $ hg update -r 7049e48789d7
568 $ hg update -r 7049e48789d7
569 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
569 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
570 $ hg unshelve
570 $ hg unshelve
571 unshelving change 'test'
571 unshelving change 'test'
572 rebasing shelved changes
572 rebasing shelved changes
573 $ hg status
573 $ hg status
574 A b
574 A b
575 $ hg branch
575 $ hg branch
576 default
576 default
577 $ cd ..
577 $ cd ..
578
578
579 When i unshelve resulting in merge conflicts and makes saved
579 When i unshelve resulting in merge conflicts and makes saved
580 file shelvedstate looks like in previous versions in
580 file shelvedstate looks like in previous versions in
581 mercurial(without restore branch information in 7th line) i
581 mercurial(without restore branch information in 7th line) i
582 expect that after resolving conflicts and successfully
582 expect that after resolving conflicts and successfully
583 running 'shelve --continue' the branch information won't be
583 running 'shelve --continue' the branch information won't be
584 restored and branch will be unchanged.
584 restored and branch will be unchanged.
585
585
586 shelve on new branch, conflict with previous shelvedstate
586 shelve on new branch, conflict with previous shelvedstate
587
587
588 $ hg init conflict
588 $ hg init conflict
589 $ cd conflict
589 $ cd conflict
590 $ echo "aaa" >> a
590 $ echo "aaa" >> a
591 $ hg commit -A -m "a"
591 $ hg commit -A -m "a"
592 adding a
592 adding a
593 $ hg branch
593 $ hg branch
594 default
594 default
595 $ hg branch test
595 $ hg branch test
596 marked working directory as branch test
596 marked working directory as branch test
597 (branches are permanent and global, did you want a bookmark?)
597 (branches are permanent and global, did you want a bookmark?)
598 $ echo "bbb" >> a
598 $ echo "bbb" >> a
599 $ hg status
599 $ hg status
600 M a
600 M a
601 $ hg shelve
601 $ hg shelve
602 shelved as default
602 shelved as default
603 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
603 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
604 $ hg branch
604 $ hg branch
605 default
605 default
606 $ echo "ccc" >> a
606 $ echo "ccc" >> a
607 $ hg status
607 $ hg status
608 M a
608 M a
609 $ hg unshelve
609 $ hg unshelve
610 unshelving change 'default'
610 unshelving change 'default'
611 temporarily committing pending changes (restore with 'hg unshelve --abort')
611 temporarily committing pending changes (restore with 'hg unshelve --abort')
612 rebasing shelved changes
612 rebasing shelved changes
613 merging a
613 merging a
614 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
614 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
615 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
615 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
616 [1]
616 [1]
617
617
618 Removing restore branch information from shelvedstate file(making it looks like
618 Removing restore branch information from shelvedstate file(making it looks like
619 in previous versions) and running unshelve --continue
619 in previous versions) and running unshelve --continue
620
620
621 $ cp .hg/shelvedstate .hg/shelvedstate_old
621 $ cp .hg/shelvedstate .hg/shelvedstate_old
622 $ cat .hg/shelvedstate_old | grep -v 'branchtorestore' > .hg/shelvedstate
622 $ cat .hg/shelvedstate_old | grep -v 'branchtorestore' > .hg/shelvedstate
623
623
624 $ echo "aaabbbccc" > a
624 $ echo "aaabbbccc" > a
625 $ rm a.orig
625 $ rm a.orig
626 $ hg resolve --mark a
626 $ hg resolve --mark a
627 (no more unresolved files)
627 (no more unresolved files)
628 continue: hg unshelve --continue
628 continue: hg unshelve --continue
629 $ hg unshelve --continue
629 $ hg unshelve --continue
630 unshelve of 'default' complete
630 unshelve of 'default' complete
631 $ cat a
631 $ cat a
632 aaabbbccc
632 aaabbbccc
633 $ hg status
633 $ hg status
634 M a
634 M a
635 $ hg branch
635 $ hg branch
636 default
636 default
637 $ cd ..
637 $ cd ..
638
638
639 On non bare shelve the branch information shouldn't be restored
639 On non bare shelve the branch information shouldn't be restored
640
640
641 $ hg init bare_shelve_on_new_branch
641 $ hg init bare_shelve_on_new_branch
642 $ cd bare_shelve_on_new_branch
642 $ cd bare_shelve_on_new_branch
643 $ echo "aaa" >> a
643 $ echo "aaa" >> a
644 $ hg commit -A -m "a"
644 $ hg commit -A -m "a"
645 adding a
645 adding a
646 $ hg branch
646 $ hg branch
647 default
647 default
648 $ hg branch test
648 $ hg branch test
649 marked working directory as branch test
649 marked working directory as branch test
650 (branches are permanent and global, did you want a bookmark?)
650 (branches are permanent and global, did you want a bookmark?)
651 $ echo "bbb" >> a
651 $ echo "bbb" >> a
652 $ hg status
652 $ hg status
653 M a
653 M a
654 $ hg shelve a
654 $ hg shelve a
655 shelved as default
655 shelved as default
656 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
656 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
657 $ hg branch
657 $ hg branch
658 test
658 test
659 $ hg branch default
659 $ hg branch default
660 marked working directory as branch default
660 marked working directory as branch default
661 (branches are permanent and global, did you want a bookmark?)
661 (branches are permanent and global, did you want a bookmark?)
662 $ echo "bbb" >> b
662 $ echo "bbb" >> b
663 $ hg status
663 $ hg status
664 ? b
664 ? b
665 $ hg unshelve
665 $ hg unshelve
666 unshelving change 'default'
666 unshelving change 'default'
667 $ hg status
667 $ hg status
668 M a
668 M a
669 ? b
669 ? b
670 $ hg branch
670 $ hg branch
671 default
671 default
672 $ cd ..
672 $ cd ..
673
673
674 Prepare unshelve with a corrupted shelvedstate
674 Prepare unshelve with a corrupted shelvedstate
675 $ hg init r1 && cd r1
675 $ hg init r1 && cd r1
676 $ echo text1 > file && hg add file
676 $ echo text1 > file && hg add file
677 $ hg shelve
677 $ hg shelve
678 shelved as default
678 shelved as default
679 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
679 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
680 $ echo text2 > file && hg ci -Am text1
680 $ echo text2 > file && hg ci -Am text1
681 adding file
681 adding file
682 $ hg unshelve
682 $ hg unshelve
683 unshelving change 'default'
683 unshelving change 'default'
684 rebasing shelved changes
684 rebasing shelved changes
685 merging file
685 merging file
686 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
686 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
687 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
687 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
688 [1]
688 [1]
689 $ echo somethingsomething > .hg/shelvedstate
689 $ echo somethingsomething > .hg/shelvedstate
690
690
691 Unshelve --continue fails with appropriate message if shelvedstate is corrupted
691 Unshelve --continue fails with appropriate message if shelvedstate is corrupted
692 $ hg unshelve --continue
692 $ hg unshelve --continue
693 abort: corrupted shelved state file
693 abort: corrupted shelved state file
694 (please run hg unshelve --abort to abort unshelve operation)
694 (please run hg unshelve --abort to abort unshelve operation)
695 [255]
695 [255]
696
696
697 Unshelve --abort works with a corrupted shelvedstate
697 Unshelve --abort works with a corrupted shelvedstate
698 $ hg unshelve --abort
698 $ hg unshelve --abort
699 could not read shelved state file, your working copy may be in an unexpected state
699 could not read shelved state file, your working copy may be in an unexpected state
700 please update to some commit
700 please update to some commit
701
701
702 Unshelve --abort fails with appropriate message if there's no unshelve in
702 Unshelve --abort fails with appropriate message if there's no unshelve in
703 progress
703 progress
704 $ hg unshelve --abort
704 $ hg unshelve --abort
705 abort: no unshelve in progress
705 abort: no unshelve in progress
706 [255]
706 [255]
707 $ cd ..
707 $ cd ..
708
708
709 Unshelve respects --keep even if user intervention is needed
709 Unshelve respects --keep even if user intervention is needed
710 $ hg init unshelvekeep && cd unshelvekeep
710 $ hg init unshelvekeep && cd unshelvekeep
711 $ echo 1 > file && hg ci -Am 1
711 $ echo 1 > file && hg ci -Am 1
712 adding file
712 adding file
713 $ echo 2 >> file
713 $ echo 2 >> file
714 $ hg shelve
714 $ hg shelve
715 shelved as default
715 shelved as default
716 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
716 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
717 $ echo 3 >> file && hg ci -Am 13
717 $ echo 3 >> file && hg ci -Am 13
718 $ hg shelve --list
718 $ hg shelve --list
719 default (*s ago) * changes to: 1 (glob)
719 default (*s ago) * changes to: 1 (glob)
720 $ hg unshelve --keep
720 $ hg unshelve --keep
721 unshelving change 'default'
721 unshelving change 'default'
722 rebasing shelved changes
722 rebasing shelved changes
723 merging file
723 merging file
724 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
724 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
725 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
725 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
726 [1]
726 [1]
727 $ hg resolve --mark file
727 $ hg resolve --mark file
728 (no more unresolved files)
728 (no more unresolved files)
729 continue: hg unshelve --continue
729 continue: hg unshelve --continue
730 $ hg unshelve --continue
730 $ hg unshelve --continue
731 unshelve of 'default' complete
731 unshelve of 'default' complete
732 $ hg shelve --list
732 $ hg shelve --list
733 default (*s ago) * changes to: 1 (glob)
733 default (*s ago) * changes to: 1 (glob)
734 $ cd ..
734 $ cd ..
735
735
736 Unshelving when there are deleted files does not crash (issue4176)
736 Unshelving when there are deleted files does not crash (issue4176)
737 $ hg init unshelve-deleted-file && cd unshelve-deleted-file
737 $ hg init unshelve-deleted-file && cd unshelve-deleted-file
738 $ echo a > a && echo b > b && hg ci -Am ab
738 $ echo a > a && echo b > b && hg ci -Am ab
739 adding a
739 adding a
740 adding b
740 adding b
741 $ echo aa > a && hg shelve
741 $ echo aa > a && hg shelve
742 shelved as default
742 shelved as default
743 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
743 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
744 $ rm b
744 $ rm b
745 $ hg st
745 $ hg st
746 ! b
746 ! b
747 $ hg unshelve
747 $ hg unshelve
748 unshelving change 'default'
748 unshelving change 'default'
749 $ hg shelve
749 $ hg shelve
750 shelved as default
750 shelved as default
751 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
751 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
752 $ rm a && echo b > b
752 $ rm a && echo b > b
753 $ hg st
753 $ hg st
754 ! a
754 ! a
755 $ hg unshelve
755 $ hg unshelve
756 unshelving change 'default'
756 unshelving change 'default'
757 abort: shelved change touches missing files
757 abort: shelved change touches missing files
758 (run hg status to see which files are missing)
758 (run hg status to see which files are missing)
759 [255]
759 [255]
760 $ hg st
760 $ hg st
761 ! a
761 ! a
762 $ cd ..
762 $ cd ..
763
763
764 New versions of Mercurial know how to read onld shelvedstate files
764 New versions of Mercurial know how to read onld shelvedstate files
765 $ hg init oldshelvedstate
765 $ hg init oldshelvedstate
766 $ cd oldshelvedstate
766 $ cd oldshelvedstate
767 $ echo root > root && hg ci -Am root
767 $ echo root > root && hg ci -Am root
768 adding root
768 adding root
769 $ echo 1 > a
769 $ echo 1 > a
770 $ hg add a
770 $ hg add a
771 $ hg shelve --name ashelve
771 $ hg shelve --name ashelve
772 shelved as ashelve
772 shelved as ashelve
773 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
773 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
774 $ echo 2 > a
774 $ echo 2 > a
775 $ hg ci -Am a
775 $ hg ci -Am a
776 adding a
776 adding a
777 $ hg unshelve
777 $ hg unshelve
778 unshelving change 'ashelve'
778 unshelving change 'ashelve'
779 rebasing shelved changes
779 rebasing shelved changes
780 merging a
780 merging a
781 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
781 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
782 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
782 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
783 [1]
783 [1]
784 putting v1 shelvedstate file in place of a created v2
784 putting v1 shelvedstate file in place of a created v2
785 $ cat << EOF > .hg/shelvedstate
785 $ cat << EOF > .hg/shelvedstate
786 > 1
786 > 1
787 > ashelve
787 > ashelve
788 > 8b058dae057a5a78f393f4535d9e363dd5efac9d
788 > 8b058dae057a5a78f393f4535d9e363dd5efac9d
789 > 8b058dae057a5a78f393f4535d9e363dd5efac9d
789 > 8b058dae057a5a78f393f4535d9e363dd5efac9d
790 > 8b058dae057a5a78f393f4535d9e363dd5efac9d f543b27db2cdb41737e2e0008dc524c471da1446
790 > 8b058dae057a5a78f393f4535d9e363dd5efac9d f543b27db2cdb41737e2e0008dc524c471da1446
791 > f543b27db2cdb41737e2e0008dc524c471da1446
791 > f543b27db2cdb41737e2e0008dc524c471da1446
792 >
792 >
793 > nokeep
793 > nokeep
794 > :no-active-bookmark
794 > :no-active-bookmark
795 > EOF
795 > EOF
796 $ echo 1 > a
796 $ echo 1 > a
797 $ hg resolve --mark a
797 $ hg resolve --mark a
798 (no more unresolved files)
798 (no more unresolved files)
799 continue: hg unshelve --continue
799 continue: hg unshelve --continue
800 mercurial does not crash
800 mercurial does not crash
801 $ hg unshelve --continue
801 $ hg unshelve --continue
802 unshelve of 'ashelve' complete
802 unshelve of 'ashelve' complete
803
803
804 #if phasebased
804 #if phasebased
805
805
806 Unshelve with some metadata file missing
806 Unshelve with some metadata file missing
807 ----------------------------------------
807 ----------------------------------------
808
808
809 $ hg shelve
809 $ hg shelve
810 shelved as default
810 shelved as default
811 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
811 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
812 $ echo 3 > a
812 $ echo 3 > a
813
813
814 Test with the `.shelve` missing, but the changeset still in the repo (non-natural case)
814 Test with the `.shelve` missing, but the changeset still in the repo (non-natural case)
815
815
816 $ rm .hg/shelved/default.shelve
816 $ rm .hg/shelved/default.shelve
817 $ hg unshelve
817 $ hg unshelve
818 unshelving change 'default'
818 unshelving change 'default'
819 temporarily committing pending changes (restore with 'hg unshelve --abort')
819 temporarily committing pending changes (restore with 'hg unshelve --abort')
820 rebasing shelved changes
820 rebasing shelved changes
821 merging a
821 merging a
822 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
822 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
823 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
823 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
824 [1]
824 [1]
825 $ hg unshelve --abort
825 $ hg unshelve --abort
826 unshelve of 'default' aborted
826 unshelve of 'default' aborted
827
827
828 Unshelve without .shelve metadata (can happen when upgrading a repository with old shelve)
828 Unshelve without .shelve metadata (can happen when upgrading a repository with old shelve)
829
829
830 $ cat .hg/shelved/default.shelve
830 $ cat .hg/shelved/default.shelve
831 node=82e0cb9893247d12667017593ce1e5655860f1ac
831 node=82e0cb9893247d12667017593ce1e5655860f1ac
832 $ hg strip --hidden --rev 82e0cb989324 --no-backup
832 $ hg strip --hidden --rev 82e0cb989324 --no-backup
833 $ rm .hg/shelved/default.shelve
833 $ rm .hg/shelved/default.shelve
834 $ hg unshelve
834 $ hg unshelve
835 unshelving change 'default'
835 unshelving change 'default'
836 temporarily committing pending changes (restore with 'hg unshelve --abort')
836 temporarily committing pending changes (restore with 'hg unshelve --abort')
837 rebasing shelved changes
837 rebasing shelved changes
838 merging a
838 merging a
839 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
839 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
840 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
840 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
841 [1]
841 [1]
842 $ cat .hg/shelved/default.shelve
842 $ cat .hg/shelved/default.shelve
843 node=82e0cb9893247d12667017593ce1e5655860f1ac
843 node=82e0cb9893247d12667017593ce1e5655860f1ac
844 $ hg unshelve --abort
844 $ hg unshelve --abort
845 unshelve of 'default' aborted
845 unshelve of 'default' aborted
846
846
847 #endif
847 #endif
848
848
849 $ cd ..
849 $ cd ..
850
851 Block merge abort when unshelve in progress(issue6160)
852 ------------------------------------------------------
853
854 $ hg init a
855 $ cd a
856 $ echo foo > a ; hg commit -qAm "initial commit"
857 $ echo bar > a
858 $ hg shelve
859 shelved as default
860 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
861 $ echo foobar > a
862 $ hg unshelve
863 unshelving change 'default'
864 temporarily committing pending changes (restore with 'hg unshelve --abort')
865 rebasing shelved changes
866 merging a
867 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
868 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
869 [1]
870
871 $ hg log --template '{desc|firstline} {author} {date|isodate} \n' -r .
872 pending changes temporary commit shelve@localhost 1970-01-01 00:00 +0000
873 $ hg merge --abort
874 abort: cannot abort merge with unshelve in progress
875 (use 'hg unshelve --continue' or 'hg unshelve --abort')
876 [255]
877
878 $ hg unshelve --abort
879 unshelve of 'default' aborted
880
881 $ hg log -G --template '{desc|firstline} {author} {date|isodate} \n' -r .
882 @ initial commit test 1970-01-01 00:00 +0000
883
884 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now